diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7d895d0fda..657dc7c772 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: pull_request: { } push: - branches: [ main, develop ] + branches: [ develop ] # Enrich gradle.properties for CI/CD env: @@ -34,13 +34,15 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.6.1 + uses: gradle/gradle-build-action@v2.7.0 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Assemble debug APK env: ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }} - run: ./gradlew assembleDebug $CI_GRADLE_ARG_PROPERTIES + ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }} + ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }} + run: ./gradlew assembleDebug -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES - name: Upload debug APKs uses: actions/upload-artifact@v3 with: @@ -70,8 +72,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Compile release sources - run: ./gradlew compileReleaseSources $CI_GRADLE_ARG_PROPERTIES + run: ./gradlew compileReleaseSources -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES - name: Compile nightly sources - run: ./gradlew compileNightlySources $CI_GRADLE_ARG_PROPERTIES + run: ./gradlew compileNightlySources -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES - name: Compile samples minimal run: ./gradlew :samples:minimal:assemble $CI_GRADLE_ARG_PROPERTIES diff --git a/.github/workflows/maestro.yml b/.github/workflows/maestro.yml index 0349e373bb..d3274d0b14 100644 --- a/.github/workflows/maestro.yml +++ b/.github/workflows/maestro.yml @@ -38,6 +38,8 @@ jobs: run: ./gradlew assembleDebug $CI_GRADLE_ARG_PROPERTIES env: ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }} + ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }} + ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }} - name: Upload debug APKs uses: actions/upload-artifact@v3 with: @@ -47,7 +49,9 @@ jobs: - uses: mobile-dev-inc/action-maestro-cloud@v1.4.1 with: api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }} - app-file: app/build/outputs/apk/debug/app-universal-debug.apk + # Doc says (https://github.com/mobile-dev-inc/action-maestro-cloud#android): + # app-file should point to an x86 compatible APK file, so upload the x86_64 one (much smaller than the universal APK). + app-file: app/build/outputs/apk/debug/app-x86_64-debug.apk env: | USERNAME=maestroelement PASSWORD=${{ secrets.MATRIX_MAESTRO_ACCOUNT_PASSWORD }} diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 95c2deb8eb..85c9ed1422 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,4 +1,4 @@ -name: Build and release nightly APK +name: Build and release nightly application on: workflow_dispatch: @@ -12,7 +12,7 @@ env: jobs: nightly: - name: Build and publish nightly APK to Firebase + name: Build and publish nightly bundle to Firebase runs-on: ubuntu-latest if: ${{ github.repository == 'vector-im/element-x-android' }} steps: @@ -31,18 +31,21 @@ jobs: sed 's/CHANGES\.md/CHANGES_NIGHTLY\.md/' towncrier.toml.bak > towncrier.toml rm towncrier.toml.bak yes n | towncrier build --version nightly - - name: Build and upload Nightly APK + - name: Build and upload Nightly application run: | ./gradlew assembleNightly appDistributionUploadNightly $CI_GRADLE_ARG_PROPERTIES env: ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }} + ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }} + ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }} ELEMENT_ANDROID_NIGHTLY_KEYID: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYID }} ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD }} ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD }} FIREBASE_TOKEN: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_FIREBASE_TOKEN }} - name: Additionally upload Nightly APK to browserstack for testing continue-on-error: true # don't block anything by this upload failing (for now) - run: curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_PASSWORD" -X POST "https://api-cloud.browserstack.com/app-automate/upload" -F "file=@app/build/outputs/apk/nightly/app-universal-nightly.apk" -F "custom_id=element-x-android-nightly" + run: | + curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_PASSWORD" -X POST "https://api-cloud.browserstack.com/app-automate/upload" -F "file=@app/build/outputs/apk/nightly/app-universal-nightly.apk" -F "custom_id=element-x-android-nightly" env: BROWSERSTACK_USERNAME: ${{ secrets.ELEMENT_ANDROID_BROWSERSTACK_USERNAME }} BROWSERSTACK_PASSWORD: ${{ secrets.ELEMENT_ANDROID_BROWSERSTACK_ACCESS_KEY }} diff --git a/.github/workflows/nightlyReports.yml b/.github/workflows/nightlyReports.yml index ce7b763ef1..5c70a3d385 100644 --- a/.github/workflows/nightlyReports.yml +++ b/.github/workflows/nightlyReports.yml @@ -62,7 +62,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.6.1 + uses: gradle/gradle-build-action@v2.7.0 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Dependency analysis diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 94b8b7ff4e..ecc40760be 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -39,7 +39,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.6.1 + uses: gradle/gradle-build-action@v2.7.0 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Run code quality check suite diff --git a/.github/workflows/recordScreenshots.yml b/.github/workflows/recordScreenshots.yml index d088b3ad94..54e35bbaff 100644 --- a/.github/workflows/recordScreenshots.yml +++ b/.github/workflows/recordScreenshots.yml @@ -24,7 +24,7 @@ jobs: java-version: '17' # Add gradle cache, this should speed up the process - name: Configure gradle - uses: gradle/gradle-build-action@v2.6.1 + uses: gradle/gradle-build-action@v2.7.0 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - name: Record screenshots diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..8ab294bc72 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,40 @@ +name: Create release App Bundle + +on: + workflow_dispatch: + push: + branches: [ main ] + +# Enrich gradle.properties for CI/CD +env: + GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false + CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon + +jobs: + release: + name: Create App Bundle + runs-on: ubuntu-latest + concurrency: + group: ${{ github.ref == 'refs/head/main' && format('build-release-main-{0}', github.sha) }} + cancel-in-progress: true + steps: + - uses: actions/checkout@v3 + - name: Use JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' # See 'Supported distributions' for available options + java-version: '17' + - name: Configure gradle + uses: gradle/gradle-build-action@v2.7.0 + - name: Create app bundle + env: + ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }} + ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }} + ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }} + run: ./gradlew bundleRelease $CI_GRADLE_ARG_PROPERTIES + - name: Upload bundle as artifact + uses: actions/upload-artifact@v3 + with: + name: elementx-app-bundle-unsigned + path: | + app/build/outputs/bundle/release/app-release.aab diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 04fd393e25..e4f2300b4c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,7 +33,7 @@ jobs: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' - name: Configure gradle - uses: gradle/gradle-build-action@v2.6.1 + uses: gradle/gradle-build-action@v2.7.0 with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} diff --git a/.gitignore b/.gitignore index 70599626ce..0b61f0aaaf 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ captures/ # IntelliJ *.iml .idea/.name +.idea/androidTestResultsUserPreferences.xml .idea/assetWizardSettings.xml .idea/compiler.xml .idea/deploymentTargetDropDown.xml diff --git a/.idea/dictionaries/shared.xml b/.idea/dictionaries/shared.xml index aafe02a2c8..2fc10f455b 100644 --- a/.idea/dictionaries/shared.xml +++ b/.idea/dictionaries/shared.xml @@ -8,6 +8,7 @@ measurables onboarding placeables + posthog showkase snackbar swipeable diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6a28adecf1..19bb2ea84b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -129,6 +129,8 @@ android { // "App Distribution found more than 1 output file for this variant. // Please contact firebase-support@google.com for help using APK splits with App Distribution." artifactPath = "$rootDir/app/build/outputs/apk/nightly/app-universal-nightly.apk" + // artifactType = "AAB" + // artifactPath = "$rootDir/app/build/outputs/bundle/nightly/app-nightly.aab" // This file will be generated by the GitHub action releaseNotesFile = "CHANGES_NIGHTLY.md" groups = "external-testers" diff --git a/appnav/src/main/kotlin/io/element/android/appnav/intent/IntentResolver.kt b/appnav/src/main/kotlin/io/element/android/appnav/intent/IntentResolver.kt index 6a3d8ff9dd..b567395c1e 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/intent/IntentResolver.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/intent/IntentResolver.kt @@ -21,8 +21,6 @@ import io.element.android.features.login.api.oidc.OidcAction import io.element.android.features.login.api.oidc.OidcIntentResolver import io.element.android.libraries.deeplink.DeeplinkData import io.element.android.libraries.deeplink.DeeplinkParser -import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.core.SessionId import timber.log.Timber import javax.inject.Inject diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt index 73a8579b07..24ec9795f7 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt @@ -20,6 +20,7 @@ import android.os.Parcelable import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.Modifier +import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.lifecycle.subscribe @@ -161,13 +162,16 @@ class RoomLoadedFlowNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { - // Rely on the View Lifecycle instead of the Node Lifecycle, + // Rely on the View Lifecycle in addition to the Node Lifecycle, // because this node enters 'onDestroy' before his children, so it can leads to // using the room in a child node where it's already closed. DisposableEffect(Unit) { - inputs.room.open() + inputs.room.subscribeToSync() onDispose { - inputs.room.close() + inputs.room.unsubscribeFromSync() + if (lifecycle.currentState == Lifecycle.State.DESTROYED) { + inputs.room.destroy() + } } } Children( 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 0efa9e7f3b..dad9365921 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt @@ -16,7 +16,7 @@ package io.element.android.appnav -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -38,7 +38,7 @@ class RootPresenterTest { @Test fun `present - initial state`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -54,7 +54,7 @@ class RootPresenterTest { showError("Bad news", "Something bad happened") } ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) 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 83bda0ad82..a811d0283d 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 @@ -16,7 +16,7 @@ package io.element.android.appnav.loggedin -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -34,7 +34,7 @@ class LoggedInPresenterTest { @Test fun `present - initial state`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() diff --git a/build.gradle.kts b/build.gradle.kts index c03881144e..9272514899 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -90,6 +90,14 @@ allprojects { apply { plugin("org.owasp.dependencycheck") } + + tasks.withType { + // Warnings are potential errors, so stop ignoring them + // This is disabled by default, but the CI will enforce this. + // You can override by passing `-PallWarningsAsErrors=true` in the command line + // Or add a line with "allWarningsAsErrors=true" in your ~/.gradle/gradle.properties file + kotlinOptions.allWarningsAsErrors = project.properties["allWarningsAsErrors"] == "true" + } } // To run a sonar analysis: diff --git a/docs/maps.md b/docs/maps.md index cc00905986..789d455f22 100644 --- a/docs/maps.md +++ b/docs/maps.md @@ -27,16 +27,21 @@ Place your API key in `local.properties` with the key services.maptiler.apikey=abCd3fGhijK1mN0pQr5t ``` +Optionally you can also place your custom MapTyler style ids for light and dark maps +in the `local.properties` with the keys `services.maptiler.lightMapId` and +`services.maptiler.darkMapId`. If you don't specify these, the default MapTiler "basic-v2" +styles will be used. + ## Making releasable builds with MapTiler To insert the MapTiler API key when building an APK, set the `ELEMENT_ANDROID_MAPTILER_API_KEY` environment variable in your build -environment. +environment. +If you've added custom styles also set the `ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID` +and `ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID` environment variables accordingly. ## Using other map sources or MapTiler styles -If you wish to use an alternative map provider, or custom MapTiler styles, -you can customise the functions in -`features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapUrls.kt`. -We've kept this file small and self contained to minimise the chances of merge -collisions in forks. +If you wish to use an alternative map provider, you can provide your own implementations of +`TileServerStyleUriBuilder` and `StaticMapUrlBuilder` in +`features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/`. diff --git a/features/analytics/impl/build.gradle.kts b/features/analytics/impl/build.gradle.kts index 3bf58ab636..8356bb38bb 100644 --- a/features/analytics/impl/build.gradle.kts +++ b/features/analytics/impl/build.gradle.kts @@ -52,6 +52,4 @@ dependencies { testImplementation(projects.libraries.matrix.test) testImplementation(projects.features.analytics.test) testImplementation(projects.features.analytics.impl) - - androidTestImplementation(libs.test.junitext) } 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 a8e42ceb01..541e5858bd 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 @@ -16,7 +16,7 @@ package io.element.android.features.analytics.impl -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -35,7 +35,7 @@ class AnalyticsOptInPresenterTest { aBuildMeta(), analyticsService ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -53,7 +53,7 @@ class AnalyticsOptInPresenterTest { aBuildMeta(), analyticsService ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 8469abe769..d0914932bb 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 @@ -16,7 +16,7 @@ package io.element.android.features.analytics.impl.preferences -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -33,7 +33,7 @@ class AnalyticsPreferencesPresenterTest { FakeAnalyticsService(isEnabled = true, didAskUserConsent = true), aBuildMeta() ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -48,7 +48,7 @@ class AnalyticsPreferencesPresenterTest { FakeAnalyticsService(isEnabled = false, didAskUserConsent = false), aBuildMeta() ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -62,7 +62,7 @@ class AnalyticsPreferencesPresenterTest { FakeAnalyticsService(isEnabled = true, didAskUserConsent = true), aBuildMeta() ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) diff --git a/features/analytics/test/src/main/kotlin/io/element/android/features/analytics/test/FakeAnalyticsService.kt b/features/analytics/test/src/main/kotlin/io/element/android/features/analytics/test/FakeAnalyticsService.kt index 6e84c58d2a..9e3fe8bb59 100644 --- a/features/analytics/test/src/main/kotlin/io/element/android/features/analytics/test/FakeAnalyticsService.kt +++ b/features/analytics/test/src/main/kotlin/io/element/android/features/analytics/test/FakeAnalyticsService.kt @@ -33,7 +33,7 @@ class FakeAnalyticsService( private val didAskUserConsentFlow = MutableStateFlow(didAskUserConsent) val capturedEvents = mutableListOf() - override fun getAvailableAnalyticsProviders(): List = emptyList() + override fun getAvailableAnalyticsProviders(): Set = emptySet() override fun getUserConsent(): Flow = isEnabledFlow diff --git a/features/createroom/impl/build.gradle.kts b/features/createroom/impl/build.gradle.kts index 8bb343c10a..fe2970dd80 100644 --- a/features/createroom/impl/build.gradle.kts +++ b/features/createroom/impl/build.gradle.kts @@ -66,7 +66,5 @@ dependencies { testImplementation(projects.libraries.mediaupload.test) testImplementation(projects.libraries.usersearch.test) - androidTestImplementation(libs.test.junitext) - ksp(libs.showkase.processor) } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomPrivacyOption.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomPrivacyOption.kt index ee664673f8..1d900cea8c 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomPrivacyOption.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomPrivacyOption.kt @@ -30,7 +30,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.Role import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import io.element.android.features.createroom.impl.configureroom.RoomPrivacyItem import io.element.android.features.createroom.impl.configureroom.roomPrivacyItems import io.element.android.libraries.designsystem.preview.ElementPreviewDark 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 fe8c7b7462..43cf6cff4d 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 @@ -16,7 +16,7 @@ package io.element.android.features.createroom.impl.addpeople -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -43,7 +43,7 @@ class AddPeoplePresenterTests { @Test fun `present - initial state`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { // TODO This doesn't actually test anything... 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 9b6ac2e067..1703cf4142 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 @@ -17,7 +17,7 @@ package io.element.android.features.createroom.impl.configureroom import android.net.Uri -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -93,7 +93,7 @@ class ConfigureRoomPresenterTests { @Test fun `present - initial state`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -108,7 +108,7 @@ class ConfigureRoomPresenterTests { @Test fun `present - create room button is enabled only if the required fields are completed`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -133,7 +133,7 @@ class ConfigureRoomPresenterTests { @Test fun `present - state is updated when fields are changed`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -203,7 +203,7 @@ class ConfigureRoomPresenterTests { @Test fun `present - trigger create room action`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -221,7 +221,7 @@ class ConfigureRoomPresenterTests { @Test fun `present - record analytics when creating room`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -240,7 +240,7 @@ class ConfigureRoomPresenterTests { @Test fun `present - trigger create room with upload error and retry`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -265,7 +265,7 @@ class ConfigureRoomPresenterTests { @Test fun `present - trigger retry and cancel actions`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 8976c77b8e..002e8c77fd 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 @@ -16,7 +16,7 @@ package io.element.android.features.createroom.impl.root -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -27,8 +27,6 @@ import io.element.android.features.createroom.impl.userlist.FakeUserListPresente import io.element.android.features.createroom.impl.userlist.UserListDataStore import io.element.android.features.createroom.impl.userlist.aUserListState import io.element.android.libraries.architecture.Async -import io.element.android.libraries.core.meta.BuildMeta -import io.element.android.libraries.core.meta.BuildType 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.user.MatrixUser @@ -68,7 +66,7 @@ class CreateRoomRootPresenterTests { @Test fun `present - initial state`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -82,7 +80,7 @@ class CreateRoomRootPresenterTests { @Test fun `present - trigger create DM action`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -102,7 +100,7 @@ class CreateRoomRootPresenterTests { @Test fun `present - creating a DM records analytics event`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -123,7 +121,7 @@ class CreateRoomRootPresenterTests { @Test fun `present - trigger retrieve DM action`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -142,7 +140,7 @@ class CreateRoomRootPresenterTests { @Test fun `present - trigger retry create DM action`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 745bdf74f9..3561387dcf 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 @@ -16,7 +16,7 @@ package io.element.android.features.createroom.impl.userlist -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -41,7 +41,7 @@ class DefaultUserListPresenterTests { userRepository, UserListDataStore(), ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -62,7 +62,7 @@ class DefaultUserListPresenterTests { userRepository, UserListDataStore(), ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -83,7 +83,7 @@ class DefaultUserListPresenterTests { userRepository, UserListDataStore(), ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -119,7 +119,7 @@ class DefaultUserListPresenterTests { userRepository, UserListDataStore(), ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -158,7 +158,7 @@ class DefaultUserListPresenterTests { userRepository, UserListDataStore(), ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -183,7 +183,7 @@ class DefaultUserListPresenterTests { userRepository, UserListDataStore(), ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) diff --git a/features/ftue/impl/src/main/res/values-de/translations.xml b/features/ftue/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..0f2efc1c57 --- /dev/null +++ b/features/ftue/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,9 @@ + + + "Anrufe, Standortfreigabe, Suche und mehr werden später in diesem Jahr hinzugefügt." + "Der Nachrichtenverlauf für verschlüsselte Räume wird in diesem Update nicht verfügbar sein." + "Wir würden uns freuen, wenn du uns über die Einstellungsseite deine Meinung mitteilst." + "Los geht\'s!" + "Folgendes musst du wissen:" + "Willkommen bei %1$s!" + diff --git a/features/ftue/impl/src/main/res/values-fr/translations.xml b/features/ftue/impl/src/main/res/values-fr/translations.xml new file mode 100644 index 0000000000..313a6a533f --- /dev/null +++ b/features/ftue/impl/src/main/res/values-fr/translations.xml @@ -0,0 +1,8 @@ + + + "L’historique des messages pour les salons chiffrés ne sera pas disponible dans cette mise à jour." + "Nous serions ravis d’avoir votre avis, n’hésitez pas à nous le partager via la page des paramètres." + "C’est parti !" + "Voici ce qu’il faut savoir :" + "Bienvenue sur %1$s !" + diff --git a/features/ftue/impl/src/main/res/values-sk/translations.xml b/features/ftue/impl/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..1c22039a26 --- /dev/null +++ b/features/ftue/impl/src/main/res/values-sk/translations.xml @@ -0,0 +1,9 @@ + + + "Hovory, zdieľanie polohy, vyhľadávanie a ďalšie funkcie pribudnú neskôr v tomto roku." + "História správ pre zašifrované miestnosti nebude v tejto aktualizácii k dispozícii." + "Radi by sme od vás počuli, dajte nám vedieť, čo si myslíte, prostredníctvom stránky nastavení." + "Poďme na to!" + "Tu je to, čo potrebujete vedieť:" + "Vitajte v %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 17999e7158..05cc72034e 100644 --- a/features/ftue/impl/src/main/res/values/localazy.xml +++ b/features/ftue/impl/src/main/res/values/localazy.xml @@ -1,6 +1,6 @@ - "Calls, location sharing, search and more will be added later this year." + "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." "Let\'s go!" 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 ce1683e8e5..cfd489ea2a 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 @@ -25,7 +25,6 @@ import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test 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 1dd9068a1f..03e0f46de8 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 @@ -16,7 +16,7 @@ package io.element.android.features.invitelist.impl -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth @@ -55,7 +55,7 @@ class InviteListPresenterTests { val presenter = createPresenter( FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -76,7 +76,7 @@ class InviteListPresenterTests { val presenter = createPresenter( FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val withInviteState = awaitItem() @@ -102,7 +102,7 @@ class InviteListPresenterTests { val presenter = createPresenter( FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val withInviteState = awaitItem() @@ -131,7 +131,7 @@ class InviteListPresenterTests { FakeAnalyticsService(), FakeNotificationDrawerManager() ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val originalState = awaitItem() @@ -152,7 +152,7 @@ class InviteListPresenterTests { val presenter = createPresenter( FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val originalState = awaitItem() @@ -173,7 +173,7 @@ class InviteListPresenterTests { val presenter = createPresenter( FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val originalState = awaitItem() @@ -199,7 +199,7 @@ class InviteListPresenterTests { val presenter = createPresenter(client = client, notificationDrawerManager = fakeNotificationDrawerManager) client.givenGetRoomResult(A_ROOM_ID, room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val originalState = awaitItem() @@ -227,7 +227,7 @@ class InviteListPresenterTests { room.givenLeaveRoomError(ex) client.givenGetRoomResult(A_ROOM_ID, room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val originalState = awaitItem() @@ -257,7 +257,7 @@ class InviteListPresenterTests { room.givenLeaveRoomError(ex) client.givenGetRoomResult(A_ROOM_ID, room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val originalState = awaitItem() @@ -288,7 +288,7 @@ class InviteListPresenterTests { val presenter = createPresenter(client = client, notificationDrawerManager = fakeNotificationDrawerManager) client.givenGetRoomResult(A_ROOM_ID, room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val originalState = awaitItem() @@ -313,7 +313,7 @@ class InviteListPresenterTests { room.givenJoinRoomResult(Result.failure(ex)) client.givenGetRoomResult(A_ROOM_ID, room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val originalState = awaitItem() @@ -335,7 +335,7 @@ class InviteListPresenterTests { room.givenJoinRoomResult(Result.failure(ex)) client.givenGetRoomResult(A_ROOM_ID, room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val originalState = awaitItem() @@ -362,7 +362,7 @@ class InviteListPresenterTests { FakeAnalyticsService(), FakeNotificationDrawerManager() ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { awaitItem() @@ -400,7 +400,7 @@ class InviteListPresenterTests { FakeAnalyticsService(), FakeNotificationDrawerManager() ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { awaitItem() 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 03eeb3adc6..3075d3c686 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 @@ -16,7 +16,7 @@ package io.element.android.features.leaveroom.impl -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -40,7 +40,7 @@ class LeaveRoomPresenterImplTest { @Test fun `present - initial state hides all dialogs`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -60,7 +60,7 @@ class LeaveRoomPresenterImplTest { ) } ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -80,7 +80,7 @@ class LeaveRoomPresenterImplTest { ) } ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -100,7 +100,7 @@ class LeaveRoomPresenterImplTest { ) } ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -122,7 +122,7 @@ class LeaveRoomPresenterImplTest { }, roomMembershipObserver = roomMembershipObserver ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -145,7 +145,7 @@ class LeaveRoomPresenterImplTest { ) } ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -167,7 +167,7 @@ class LeaveRoomPresenterImplTest { ) } ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -191,7 +191,7 @@ class LeaveRoomPresenterImplTest { ) } ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() diff --git a/features/location/api/build.gradle.kts b/features/location/api/build.gradle.kts index 6de297fe77..bef0e77b67 100644 --- a/features/location/api/build.gradle.kts +++ b/features/location/api/build.gradle.kts @@ -22,12 +22,12 @@ plugins { id("kotlin-parcelize") } -fun readLocalProperty(name: String) = Properties().apply { +fun readLocalProperty(name: String): String? = Properties().apply { try { load(rootProject.file("local.properties").reader()) } catch (ignored: java.io.IOException) { } -}[name] +}.getProperty(name) android { namespace = "io.element.android.features.location.api" @@ -37,9 +37,23 @@ android { type = "string", name = "maptiler_api_key", value = System.getenv("ELEMENT_ANDROID_MAPTILER_API_KEY") - ?: readLocalProperty("services.maptiler.apikey") as? String + ?: readLocalProperty("services.maptiler.apikey") ?: "" ) + resValue( + type = "string", + name = "maptiler_light_map_id", + value = System.getenv("ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID") + ?: readLocalProperty("services.maptiler.lightMapId") + ?: "basic-v2" // fall back to maptiler's default light map. + ) + resValue( + type = "string", + name = "maptiler_dark_map_id", + value = System.getenv("ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID") + ?: readLocalProperty("services.maptiler.darkMapId") + ?: "basic-v2-dark" // fall back to maptiler's default dark map. + ) } } diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt index 03aa21b4c8..39b3e8a9a1 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt @@ -29,16 +29,16 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import coil.compose.AsyncImagePainter import coil.compose.rememberAsyncImagePainter import coil.request.ImageRequest import io.element.android.features.location.api.internal.StaticMapPlaceholder +import io.element.android.features.location.api.internal.StaticMapUrlBuilder import io.element.android.features.location.api.internal.centerBottomEdge -import io.element.android.features.location.api.internal.staticMapUrl import io.element.android.libraries.designsystem.preview.DayNightPreviews import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.text.toDp import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.theme.ElementTheme import timber.log.Timber @@ -65,23 +65,22 @@ fun StaticMapView( ) { val context = LocalContext.current var retryHash by remember { mutableStateOf(0) } + val builder = remember { StaticMapUrlBuilder(context) } val painter = rememberAsyncImagePainter( model = if (constraints.isZero) { // Avoid building a URL if any of the size constraints is zero (else it will thrown an exception). null } else { - ImageRequest.Builder(LocalContext.current) + ImageRequest.Builder(context) .data( - staticMapUrl( - context = context, + builder.build( lat = lat, lon = lon, zoom = zoom, darkMode = darkMode, - // Size the map based on DP rather than pixels, as otherwise the features and attribution - // end up being illegibly tiny on high density displays. - width = constraints.maxWidth.toDp().value.toInt(), - height = constraints.maxHeight.toDp().value.toInt(), + width = constraints.maxWidth, + height = constraints.maxHeight, + density = LocalDensity.current.density, ) ) .size(width = constraints.maxWidth, height = constraints.maxHeight) diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerConfig.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerConfig.kt new file mode 100644 index 0000000000..9e20d5179b --- /dev/null +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerConfig.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.features.location.api.internal + +import android.content.Context +import io.element.android.features.location.api.R + +internal const val MAPTILER_BASE_URL = "https://api.maptiler.com/maps" + +internal fun Context.mapId(darkMode: Boolean) = when (darkMode) { + true -> getString(R.string.maptiler_dark_map_id) + false -> getString(R.string.maptiler_light_map_id) +} + +internal val Context.apiKey: String + get() = getString(R.string.maptiler_api_key) diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt new file mode 100644 index 0000000000..927e890248 --- /dev/null +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt @@ -0,0 +1,93 @@ +/* + * 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.location.api.internal + +import android.content.Context +import kotlin.math.roundToInt + +/** + * Builds an URL for MapTiler's Static Maps API. + * + * https://docs.maptiler.com/cloud/api/static-maps/ + */ +internal class MapTilerStaticMapUrlBuilder( + private val apiKey: String, + private val lightMapId: String, + private val darkMapId: String, +) : StaticMapUrlBuilder { + + constructor(context: Context) : this( + apiKey = context.apiKey, + lightMapId = context.mapId(darkMode = false), + darkMapId = context.mapId(darkMode = true), + ) + + override fun build( + lat: Double, + lon: Double, + zoom: Double, + darkMode: Boolean, + width: Int, + height: Int, + density: Float + ): String { + val mapId = if (darkMode) darkMapId else lightMapId + val finalZoom = zoom.coerceIn(zoomRange) + + // Request @2x density for xhdpi and above (xhdpi == 320dpi == 2x density). + val is2x = density >= 2 + + // Scale requested width/height according to the reported display density. + val (finalWidth, finalHeight) = coerceWidthAndHeight( + width = (width / density).roundToInt(), + height = (height / density).roundToInt(), + is2x = is2x, + ) + + val scale = if (is2x) "@2x" else "" + + // Since Maptiler doesn't support arbitrary dpi scaling, we stick to 2x sized + // images even on displays with density higher than 2x, thereby yielding an + // image smaller than the available space in pixels. + // The resulting image will have to be scaled to fit the available space in order + // to keep the perceived content size constant at the expense of sharpness. + return "$MAPTILER_BASE_URL/${mapId}/static/${lon},${lat},${finalZoom}/${finalWidth}x${finalHeight}${scale}.webp?key=${apiKey}&attribution=bottomleft" + } +} + +private fun coerceWidthAndHeight(width: Int, height: Int, is2x: Boolean): Pair { + if (width <= 0 || height <= 0) { + // This effectively yields an URL which asks for a 0x0 image which will result in an HTTP error, + // but it's better than e.g. asking for a 1x1 image which would be unreadable and increase usage costs. + return 0 to 0 + } + val aspectRatio = width.toDouble() / height.toDouble() + val range = if (is2x) widthHeightRange2x else widthHeightRange + return if (width >= height) { + width.coerceIn(range).let { coercedWidth -> + coercedWidth to (coercedWidth / aspectRatio).roundToInt() + } + } else { + height.coerceIn(range).let { coercedHeight -> + (coercedHeight * aspectRatio).roundToInt() to coercedHeight + } + } +} + +private val widthHeightRange = 1..2048 // API will error if outside 1-2048 range @1x. +private val widthHeightRange2x = 1..1024 // API will error if outside 1-1024 range @2x. +private val zoomRange = 0.0..22.0 // API will error if outside 0-22 range. diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt new file mode 100644 index 0000000000..6972e45330 --- /dev/null +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.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. + */ + +@file:JvmName("TileServerStyleUriBuilderKt") + +package io.element.android.features.location.api.internal + +import android.content.Context + +internal class MapTilerTileServerStyleUriBuilder( + private val apiKey: String, + private val lightMapId: String, + private val darkMapId: String, +) : TileServerStyleUriBuilder { + + constructor(context: Context) : this( + apiKey = context.apiKey, + lightMapId = context.mapId(darkMode = false), + darkMapId = context.mapId(darkMode = true), + ) + + override fun build(darkMode: Boolean): String { + val mapId = if (darkMode) darkMapId else lightMapId + return "${MAPTILER_BASE_URL}/${mapId}/style.json?key=${apiKey}" + } +} diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapUrls.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapUrls.kt deleted file mode 100644 index b6f21a4512..0000000000 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapUrls.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2023 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.features.location.api.internal - -import android.content.Context -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalContext -import io.element.android.features.location.api.R -import io.element.android.libraries.theme.ElementTheme - -/** - * Provides the URL to an image that contains a statically-generated map of the given location. - */ -fun staticMapUrl( - context: Context, - lat: Double, - lon: Double, - zoom: Double, - width: Int, - height: Int, - darkMode: Boolean, -): String { - return "${baseUrl(darkMode)}/static/${lon},${lat},${zoom}/${width}x${height}@2x.webp?key=${context.apiKey}&attribution=bottomleft" -} - -/** - * Utility function to remember the tile server URL based on the current theme. - */ -@Composable -fun rememberTileStyleUrl(): String { - val context = LocalContext.current - val darkMode = !ElementTheme.isLightTheme - return remember(darkMode) { - tileStyleUrl( - context = context, - darkMode = darkMode - ) - } -} - -/** - * Provides the URL to a MapLibre style document, used for rendering dynamic maps. - */ -private fun tileStyleUrl( - context: Context, - darkMode: Boolean, -): String { - return "${baseUrl(darkMode)}/style.json?key=${context.apiKey}" -} - -private fun baseUrl(darkMode: Boolean) = - "https://api.maptiler.com/maps/" + - if (darkMode) - "dea61faf-292b-4774-9660-58fcef89a7f3" - else - "9bc819c8-e627-474a-a348-ec144fe3d810" - -private val Context.apiKey: String - get() = getString(R.string.maptiler_api_key) diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/ModifierCenterBottomEdge.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/ModifierCenterBottomEdge.kt index 7ad299adae..736f24e609 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/ModifierCenterBottomEdge.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/ModifierCenterBottomEdge.kt @@ -29,7 +29,7 @@ fun Modifier.centerBottomEdge(scope: BoxScope): Modifier = with(scope) { Modifier.align { size, space, _ -> IntOffset( x = (space.width - size.width) / 2, - y = (space.height / 2) - size.height, + y = space.height / 2 - size.height, ) } ) diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapUrlBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapUrlBuilder.kt new file mode 100644 index 0000000000..8c29b5c13d --- /dev/null +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapUrlBuilder.kt @@ -0,0 +1,36 @@ +/* + * 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.location.api.internal + +import android.content.Context + +/** + * Builds an URL for a 3rd party service provider static maps API. + */ +interface StaticMapUrlBuilder { + fun build( + lat: Double, + lon: Double, + zoom: Double, + darkMode: Boolean, + width: Int, + height: Int, + density: Float, + ): String +} + +fun StaticMapUrlBuilder(context: Context): StaticMapUrlBuilder = MapTilerStaticMapUrlBuilder(context = context) diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/TileServerStyleUriBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/TileServerStyleUriBuilder.kt new file mode 100644 index 0000000000..ece32cbf5a --- /dev/null +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/TileServerStyleUriBuilder.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.features.location.api.internal + +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import io.element.android.libraries.theme.ElementTheme + +/** + * Builds a style URI for a MapLibre compatible tile server. + * + * Used for rendering dynamic maps. + */ +interface TileServerStyleUriBuilder { + fun build( + darkMode: Boolean, + ): String +} + +fun TileServerStyleUriBuilder(context: Context): TileServerStyleUriBuilder = MapTilerTileServerStyleUriBuilder(context = context) + +/** + * Provides and remembers a style URI for a MapLibre compatible tile server. + * + * Used for rendering dynamic maps. + */ +@Composable +fun rememberTileStyleUrl(): String { + val context = LocalContext.current + val darkMode = !ElementTheme.isLightTheme + return remember(darkMode) { + TileServerStyleUriBuilder(context).build(darkMode) + } +} diff --git a/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilderTest.kt b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilderTest.kt new file mode 100644 index 0000000000..c359777ee7 --- /dev/null +++ b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilderTest.kt @@ -0,0 +1,191 @@ +/* + * 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.location.api.internal + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class MapTilerStaticMapUrlBuilderTest { + + private val builder = MapTilerStaticMapUrlBuilder( + apiKey = "anApiKey", + lightMapId = "aLightMapId", + darkMapId = "aDarkMapId", + ) + + @Test + fun `static map 1x density`() { + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 800, + height = 600, + density = 1f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/800x600.webp?key=anApiKey&attribution=bottomleft") + } + + @Test + fun `static map 1,5x density`() { + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 1200, + height = 900, + density = 1.5f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/800x600.webp?key=anApiKey&attribution=bottomleft") + } + + @Test + fun `static map 2x density`() { + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 1600, + height = 1200, + density = 2f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/800x600@2x.webp?key=anApiKey&attribution=bottomleft") + } + + @Test + fun `static map 3x density`() { + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 2400, + height = 1800, + density = 3f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/800x600@2x.webp?key=anApiKey&attribution=bottomleft") + } + + @Test + fun `too big image is coerced keeping aspect ratio`() { + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 4096, + height = 2048, + density = 1f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/2048x1024.webp?key=anApiKey&attribution=bottomleft") + + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 2048, + height = 4096, + density = 1f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/1024x2048.webp?key=anApiKey&attribution=bottomleft") + + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 4096, + height = 2048, + density = 2f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/1024x512@2x.webp?key=anApiKey&attribution=bottomleft") + + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 2048, + height = 4096, + density = 2f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/512x1024@2x.webp?key=anApiKey&attribution=bottomleft") + + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = Int.MAX_VALUE, + height = Int.MAX_VALUE, + density = 2f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/1024x1024@2x.webp?key=anApiKey&attribution=bottomleft") + } + + @Test + fun `too small image is coerced to 0x0`() { + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 0, + height = 0, + density = 1f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/0x0.webp?key=anApiKey&attribution=bottomleft") + + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = 0, + height = 0, + density = 2f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/0x0@2x.webp?key=anApiKey&attribution=bottomleft") + + assertThat( + builder.build( + lat = 1.23, + lon = -4.56, + zoom = 7.8, + darkMode = false, + width = Int.MIN_VALUE, + height = Int.MIN_VALUE, + density = 1f, + ) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/static/-4.56,1.23,7.8/0x0.webp?key=anApiKey&attribution=bottomleft") + } +} diff --git a/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt new file mode 100644 index 0000000000..abff83c582 --- /dev/null +++ b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt @@ -0,0 +1,43 @@ +/* + * 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.location.api.internal + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class MapTilerTileServerStyleUriBuilderTest { + + private val builder = MapTilerTileServerStyleUriBuilder( + apiKey = "anApiKey", + lightMapId = "aLightMapId", + darkMapId = "aDarkMapId", + ) + + @Test + fun `light map uri`() { + assertThat( + builder.build(darkMode = false) + ).isEqualTo("https://api.maptiler.com/maps/aLightMapId/style.json?key=anApiKey") + } + + @Test + fun `dark map uri`() { + assertThat( + builder.build(darkMode = true) + ).isEqualTo("https://api.maptiler.com/maps/aDarkMapId/style.json?key=anApiKey") + } +} diff --git a/features/location/impl/build.gradle.kts b/features/location/impl/build.gradle.kts index 1158b5f152..e808eed11c 100644 --- a/features/location/impl/build.gradle.kts +++ b/features/location/impl/build.gradle.kts @@ -45,7 +45,6 @@ dependencies { implementation(projects.libraries.uiStrings) implementation(libs.dagger) implementation(projects.anvilannotations) - implementation(projects.services.toolbox.api) anvil(projects.anvilcodegen) ksp(libs.showkase.processor) diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/MapDefaults.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/MapDefaults.kt index ac99be1f59..4709c78538 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/MapDefaults.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/MapDefaults.kt @@ -20,6 +20,7 @@ import android.Manifest import android.view.Gravity import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.ui.graphics.Color import com.mapbox.mapboxsdk.camera.CameraPosition import com.mapbox.mapboxsdk.geometry.LatLng import io.element.android.libraries.maplibre.compose.MapLocationSettings @@ -53,6 +54,13 @@ object MapDefaults { val locationSettings: MapLocationSettings get() = MapLocationSettings( locationEnabled = false, + backgroundTintColor = Color.White, + foregroundTintColor = Color.Black, + backgroundStaleTintColor = Color.White, + foregroundStaleTintColor = Color.Black, + accuracyColor = Color.Black, + pulseEnabled = true, + pulseColor = Color.Black, ) val centerCameraPosition = CameraPosition.Builder() 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 d06124539c..595e26e32e 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 @@ -36,12 +36,7 @@ import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.services.analytics.api.AnalyticsService -import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.coroutines.launch -import java.time.Instant -import java.time.ZoneOffset -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter import javax.inject.Inject class SendLocationPresenter @Inject constructor( @@ -50,7 +45,6 @@ class SendLocationPresenter @Inject constructor( private val analyticsService: AnalyticsService, private val messageComposerContext: MessageComposerContext, private val locationActions: LocationActions, - private val systemClock: SystemClock, private val buildMeta: BuildMeta, ) : Presenter { @@ -115,7 +109,7 @@ class SendLocationPresenter @Inject constructor( SendLocationState.Mode.PinLocation -> { val geoUri = event.cameraPosition.toGeoUri() room.sendLocation( - body = generateBody(geoUri, systemClock.epochMillis()), + body = generateBody(geoUri), geoUri = geoUri, description = null, zoomLevel = MapDefaults.DEFAULT_ZOOM.toInt(), @@ -134,7 +128,7 @@ class SendLocationPresenter @Inject constructor( SendLocationState.Mode.SenderLocation -> { val geoUri = event.toGeoUri() room.sendLocation( - body = generateBody(geoUri, systemClock.epochMillis()), + body = generateBody(geoUri), geoUri = geoUri, description = null, zoomLevel = MapDefaults.DEFAULT_ZOOM.toInt(), @@ -158,7 +152,4 @@ private fun SendLocationEvents.SendLocation.toGeoUri(): String = location?.toGeo private fun SendLocationEvents.SendLocation.CameraPosition.toGeoUri(): String = "geo:$lat,$lon" -private fun generateBody(uri: String, epochMillis: Long): String { - val timestamp = ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochMillis), ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT) - return "Location was shared at $uri as of $timestamp" -} +private fun generateBody(uri: String): String = "Location was shared at $uri" diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationView.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationView.kt index d0e9dfd5aa..cdaca94b04 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationView.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationView.kt @@ -28,7 +28,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.LocationOn import androidx.compose.material.icons.filled.LocationSearching import androidx.compose.material.icons.filled.MyLocation import androidx.compose.material3.ExperimentalMaterial3Api @@ -49,6 +48,7 @@ import io.element.android.features.location.api.Location import io.element.android.features.location.api.internal.centerBottomEdge import io.element.android.features.location.api.internal.rememberTileStyleUrl import io.element.android.features.location.impl.MapDefaults +import io.element.android.features.location.impl.R import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.preview.DayNightPreviews @@ -156,7 +156,11 @@ fun SendLocationView( navigateUp() }, leadingContent = { - Icon(Icons.Default.LocationOn, null) + Icon( + resourceId = R.drawable.pin_small, + contentDescription = null, + tint = Color.Unspecified, + ) }, ) Spacer(modifier = Modifier.height(16.dp + navBarPadding)) diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationStateProvider.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationStateProvider.kt index 1865c6b9a4..73bb2d37a4 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationStateProvider.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationStateProvider.kt @@ -50,6 +50,13 @@ class ShowLocationStateProvider : PreviewParameterProvider { isTrackMyLocation = false, eventSink = {}, ), + ShowLocationState( + Location(1.23, 2.34, 4f), + description = "For some reason I decided to to write a small essay that wraps at just two lines!", + hasLocationPermission = false, + isTrackMyLocation = false, + eventSink = {}, + ), ShowLocationState( Location(1.23, 2.34, 4f), description = "For some reason I decided to write a small essay in the location description. " + diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationView.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationView.kt index 7726bbf9cc..0e114a43b9 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationView.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationView.kt @@ -40,7 +40,6 @@ import com.mapbox.mapboxsdk.camera.CameraPosition import com.mapbox.mapboxsdk.geometry.LatLng import io.element.android.features.location.api.internal.rememberTileStyleUrl import io.element.android.features.location.impl.MapDefaults -import io.element.android.features.location.impl.send.SendLocationState import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight diff --git a/features/location/impl/src/main/res/drawable-night/pin_small.xml b/features/location/impl/src/main/res/drawable-night/pin_small.xml new file mode 100644 index 0000000000..2e8a54b70e --- /dev/null +++ b/features/location/impl/src/main/res/drawable-night/pin_small.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/features/location/impl/src/main/res/drawable/pin_small.xml b/features/location/impl/src/main/res/drawable/pin_small.xml new file mode 100644 index 0000000000..0e277a1ed2 --- /dev/null +++ b/features/location/impl/src/main/res/drawable/pin_small.xml @@ -0,0 +1,19 @@ + + + + + + + + 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 45c99b556a..0aa89e89ba 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 @@ -16,7 +16,7 @@ package io.element.android.features.location.impl.send -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth @@ -34,7 +34,6 @@ import io.element.android.libraries.matrix.test.core.aBuildMeta 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.toolbox.api.systemclock.SystemClock import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest import org.junit.Test @@ -46,7 +45,6 @@ class SendLocationPresenterTest { private val fakeAnalyticsService = FakeAnalyticsService() private val messageComposerContextFake = MessageComposerContextFake() private val fakeLocationActions = FakeLocationActions() - private val fakeSystemClock = SystemClock { 0L } private val fakeBuildMeta = aBuildMeta(applicationName = "app name") private val sendLocationPresenter: SendLocationPresenter = SendLocationPresenter( permissionsPresenterFactory = object : PermissionsPresenter.Factory { @@ -56,7 +54,6 @@ class SendLocationPresenterTest { analyticsService = fakeAnalyticsService, messageComposerContext = messageComposerContextFake, locationActions = fakeLocationActions, - systemClock = fakeSystemClock, buildMeta = fakeBuildMeta, ) @@ -69,7 +66,7 @@ class SendLocationPresenterTest { ) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { sendLocationPresenter.present() }.test { @@ -96,7 +93,7 @@ class SendLocationPresenterTest { ) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { sendLocationPresenter.present() }.test { @@ -123,7 +120,7 @@ class SendLocationPresenterTest { ) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { sendLocationPresenter.present() }.test { val initialState = awaitItem() @@ -149,7 +146,7 @@ class SendLocationPresenterTest { ) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { sendLocationPresenter.present() }.test { val initialState = awaitItem() @@ -175,7 +172,7 @@ class SendLocationPresenterTest { ) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { sendLocationPresenter.present() }.test { // Skip initial state @@ -206,7 +203,7 @@ class SendLocationPresenterTest { ) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { sendLocationPresenter.present() }.test { // Skip initial state @@ -234,7 +231,7 @@ class SendLocationPresenterTest { ) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { sendLocationPresenter.present() }.test { // Skip initial state @@ -265,7 +262,7 @@ class SendLocationPresenterTest { ) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { sendLocationPresenter.present() }.test { // Skip initial state @@ -292,7 +289,7 @@ class SendLocationPresenterTest { Truth.assertThat(fakeMatrixRoom.sentLocations.size).isEqualTo(1) Truth.assertThat(fakeMatrixRoom.sentLocations.last()).isEqualTo( SendLocationInvocation( - body = "Location was shared at geo:3.0,4.0;u=5.0 as of 1970-01-01T00:00:00Z", + body = "Location was shared at geo:3.0,4.0;u=5.0", geoUri = "geo:3.0,4.0;u=5.0", description = null, zoomLevel = 15, @@ -322,7 +319,7 @@ class SendLocationPresenterTest { ) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { sendLocationPresenter.present() }.test { // Skip initial state @@ -349,7 +346,7 @@ class SendLocationPresenterTest { Truth.assertThat(fakeMatrixRoom.sentLocations.size).isEqualTo(1) Truth.assertThat(fakeMatrixRoom.sentLocations.last()).isEqualTo( SendLocationInvocation( - body = "Location was shared at geo:0.0,1.0 as of 1970-01-01T00:00:00Z", + body = "Location was shared at geo:0.0,1.0", geoUri = "geo:0.0,1.0", description = null, zoomLevel = 15, @@ -384,7 +381,7 @@ class SendLocationPresenterTest { ) } - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { sendLocationPresenter.present() }.test { // Skip initial state @@ -431,7 +428,7 @@ class SendLocationPresenterTest { ) } - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { sendLocationPresenter.present() }.test { // Skip initial state @@ -451,7 +448,7 @@ class SendLocationPresenterTest { @Test fun `application name is in state`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { sendLocationPresenter.present() }.test { val initialState = awaitItem() 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 47f0e2ccea..7fff766a9f 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 @@ -16,7 +16,7 @@ package io.element.android.features.location.impl.show -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth @@ -44,7 +44,7 @@ class ShowLocationPresenterTest { @Test fun `emits initial state with no location permission`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -59,7 +59,7 @@ class ShowLocationPresenterTest { fun `emits initial state with location permission`() = runTest { permissionsPresenterFake.givenState(PermissionsState(permissions = PermissionsState.Permissions.AllGranted)) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -74,7 +74,7 @@ class ShowLocationPresenterTest { fun `emits initial state with partial location permission`() = runTest { permissionsPresenterFake.givenState(PermissionsState(permissions = PermissionsState.Permissions.SomeGranted)) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -87,7 +87,7 @@ class ShowLocationPresenterTest { @Test fun `uses action to share location`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -102,7 +102,7 @@ class ShowLocationPresenterTest { fun `centers on user location`() = runTest { permissionsPresenterFake.givenState(PermissionsState(permissions = PermissionsState.Permissions.AllGranted)) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() diff --git a/features/login/impl/build.gradle.kts b/features/login/impl/build.gradle.kts index f9bbb390e6..4a4c6756aa 100644 --- a/features/login/impl/build.gradle.kts +++ b/features/login/impl/build.gradle.kts @@ -61,6 +61,4 @@ dependencies { testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) testImplementation(projects.tests.testutils) - - androidTestImplementation(libs.test.junitext) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt index 3362beac40..378859225e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt @@ -30,7 +30,6 @@ 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.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordState.kt index c8fa2f4ad3..cf4e0d8ee5 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordState.kt @@ -30,7 +30,8 @@ data class LoginPasswordState( ) { val submitEnabled: Boolean get() = loginAction !is Async.Failure && - ((formState.login.isNotEmpty() && formState.password.isNotEmpty())) + formState.login.isNotEmpty() && + formState.password.isNotEmpty() } @Parcelize diff --git a/features/login/impl/src/main/res/values-sk/translations.xml b/features/login/impl/src/main/res/values-sk/translations.xml index 2cdaf596e1..2969e4ecb0 100644 --- a/features/login/impl/src/main/res/values-sk/translations.xml +++ b/features/login/impl/src/main/res/values-sk/translations.xml @@ -6,9 +6,9 @@ "Zadajte hľadaný výraz alebo adresu domény." "Vyhľadať spoločnosť, komunitu alebo súkromný server." "Nájsť poskytovateľa účtu" - "Tu budú žiť vaše konverzácie - podobne ako používate poskytovateľa e-mailových služieb na uchovávanie e-mailov." + "Tu budú žiť vaše konverzácie — podobne ako používate poskytovateľa e-mailových služieb na uchovávanie e-mailov." "Chystáte sa prihlásiť do %s" - "Tu budú žiť vaše konverzácie - podobne ako používate poskytovateľa e-mailových služieb na uchovávanie e-mailov." + "Tu budú žiť vaše konverzácie — podobne ako používate poskytovateľa e-mailových služieb na uchovávanie e-mailov." "Chystáte sa vytvoriť účet na %s" "Matrix.org je otvorená sieť pre bezpečnú a decentralizovanú komunikáciu." "Iný" 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 9aefafb382..009ab31dcd 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 @@ -16,7 +16,7 @@ package io.element.android.features.login.impl.changeserver -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -36,7 +36,7 @@ class ChangeServerPresenterTest { FakeAuthenticationService(), AccountProviderDataSource() ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -51,7 +51,7 @@ class ChangeServerPresenterTest { authenticationService, AccountProviderDataSource() ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -72,7 +72,7 @@ class ChangeServerPresenterTest { authenticationService, AccountProviderDataSource() ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 5756cd13d2..32d1c6918a 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 @@ -18,7 +18,7 @@ package io.element.android.features.login.impl.oidc.webview -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -38,7 +38,7 @@ class OidcPresenterTest { A_OIDC_DATA, FakeAuthenticationService(), ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -53,7 +53,7 @@ class OidcPresenterTest { A_OIDC_DATA, FakeAuthenticationService(), ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -73,7 +73,7 @@ class OidcPresenterTest { authenticationService, ) authenticationService.givenOidcCancelError(A_THROWABLE) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -92,7 +92,7 @@ class OidcPresenterTest { A_OIDC_DATA, FakeAuthenticationService(), ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -110,7 +110,7 @@ class OidcPresenterTest { A_OIDC_DATA, FakeAuthenticationService(), ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -129,7 +129,7 @@ class OidcPresenterTest { authenticationService, ) authenticationService.givenLoginError(A_THROWABLE) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 086428257a..f807355cb1 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 @@ -16,7 +16,7 @@ package io.element.android.features.login.impl.screens.changeaccountprovider -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -37,7 +37,7 @@ class ChangeAccountProviderPresenterTest { val presenter = ChangeAccountProviderPresenter( changeServerPresenter ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 131d0d9298..76a3ad3d22 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 @@ -16,7 +16,7 @@ package io.element.android.features.login.impl.screens.confirmaccountprovider -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -38,7 +38,7 @@ class ConfirmAccountProviderPresenterTest { AccountProviderDataSource(), FakeAuthenticationService(), ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -58,7 +58,7 @@ class ConfirmAccountProviderPresenterTest { authServer, ) authServer.givenHomeserver(A_HOMESERVER) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -82,7 +82,7 @@ class ConfirmAccountProviderPresenterTest { authServer, ) authServer.givenHomeserver(A_HOMESERVER_OIDC) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -105,7 +105,7 @@ class ConfirmAccountProviderPresenterTest { AccountProviderDataSource(), authServer, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -126,7 +126,7 @@ class ConfirmAccountProviderPresenterTest { AccountProviderDataSource(), authenticationService, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 afd4b542e4..421eb869b0 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 @@ -16,7 +16,7 @@ package io.element.android.features.login.impl.screens.loginpassword -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -45,7 +45,7 @@ class LoginPasswordPresenterTest { accountProviderDataSource, loginUserStory, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -67,7 +67,7 @@ class LoginPasswordPresenterTest { loginUserStory, ) authenticationService.givenHomeserver(A_HOMESERVER) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -93,7 +93,7 @@ class LoginPasswordPresenterTest { loginUserStory, ) authenticationService.givenHomeserver(A_HOMESERVER) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { assertThat(loginUserStory.loginFlowIsDone.value).isFalse() @@ -122,7 +122,7 @@ class LoginPasswordPresenterTest { loginUserStory, ) authenticationService.givenHomeserver(A_HOMESERVER) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -150,7 +150,7 @@ class LoginPasswordPresenterTest { loginUserStory, ) authenticationService.givenHomeserver(A_HOMESERVER) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 9163f247f5..ae6ae4d344 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 @@ -16,7 +16,7 @@ package io.element.android.features.login.impl.screens.searchaccountprovider -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -46,7 +46,7 @@ class SearchAccountProviderPresenterTest { HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), changeServerPresenter ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -66,7 +66,7 @@ class SearchAccountProviderPresenterTest { HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), changeServerPresenter ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -90,7 +90,7 @@ class SearchAccountProviderPresenterTest { HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), changeServerPresenter ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -125,7 +125,7 @@ class SearchAccountProviderPresenterTest { HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), changeServerPresenter ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -160,7 +160,7 @@ class SearchAccountProviderPresenterTest { HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), changeServerPresenter ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 389ac52176..507e8cec8b 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 @@ -16,7 +16,7 @@ package io.element.android.features.login.impl.screens.waitlistscreen -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -46,7 +46,7 @@ class WaitListPresenterTest { authenticationService, loginUserStory, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -68,7 +68,7 @@ class WaitListPresenterTest { authenticationService, loginUserStory, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -97,7 +97,7 @@ class WaitListPresenterTest { authenticationService, loginUserStory, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { assertThat(loginUserStory.loginFlowIsDone.value).isFalse() diff --git a/features/logout/impl/build.gradle.kts b/features/logout/impl/build.gradle.kts index 88d8282875..464695e169 100644 --- a/features/logout/impl/build.gradle.kts +++ b/features/logout/impl/build.gradle.kts @@ -48,6 +48,4 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) - - androidTestImplementation(libs.test.junitext) } 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 bed33006d6..29df521cb1 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 @@ -16,7 +16,7 @@ package io.element.android.features.logout.impl -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -34,7 +34,7 @@ class LogoutPreferencePresenterTest { val presenter = DefaultLogoutPreferencePresenter( FakeMatrixClient(), ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -47,7 +47,7 @@ class LogoutPreferencePresenterTest { val presenter = DefaultLogoutPreferencePresenter( FakeMatrixClient(), ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -65,7 +65,7 @@ class LogoutPreferencePresenterTest { val presenter = DefaultLogoutPreferencePresenter( matrixClient, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 7488820f7d..b5edaec78e 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -78,6 +78,5 @@ dependencies { testImplementation(projects.libraries.mediapickers.test) testImplementation(libs.test.mockk) - androidTestImplementation(libs.test.junitext) ksp(libs.showkase.processor) } 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 56e9f48dde..2d18018746 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 @@ -30,7 +30,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.canBeCopied import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.meta.BuildMeta -import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesView.kt index 230965312e..467a963088 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesView.kt @@ -82,6 +82,7 @@ fun ForwardMessagesView( return } + @Suppress("UNUSED_PARAMETER") fun onRoomRemoved(roomSummaryDetails: RoomSummaryDetails) { // TODO toggle selection when multi-selection is enabled state.eventSink(ForwardMessagesEvents.RemoveSelectedRoom) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerView.kt index 5964c2ced7..5fb14a6ca0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/media/viewer/MediaViewerView.kt @@ -57,7 +57,6 @@ import io.element.android.features.messages.impl.media.local.LocalMediaView import io.element.android.features.messages.impl.media.local.MediaInfo import io.element.android.features.messages.impl.media.local.rememberLocalMediaViewState import io.element.android.libraries.architecture.Async -import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.dialogs.RetryDialog import io.element.android.libraries.designsystem.preview.ElementPreviewDark 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 c8324ec677..b554ef98f4 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 @@ -83,7 +83,7 @@ internal fun AttachmentsBottomSheet( onDismissRequest = { isVisible = false } ) { AttachmentSourcePickerMenu( - eventSink = state.eventSink, + state = state, onSendLocationClicked = onSendLocationClicked, ) } @@ -93,7 +93,7 @@ internal fun AttachmentsBottomSheet( @OptIn(ExperimentalMaterialApi::class) @Composable internal fun AttachmentSourcePickerMenu( - eventSink: (MessageComposerEvents) -> Unit, + state: MessageComposerState, onSendLocationClicked: () -> Unit, modifier: Modifier = Modifier, ) { @@ -102,33 +102,35 @@ internal fun AttachmentSourcePickerMenu( // .navigationBarsPadding() - FIXME after https://issuetracker.google.com/issues/275849044 ) { ListItem( - modifier = Modifier.clickable { eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) }, + modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) }, icon = { Icon(Icons.Default.Collections, null) }, text = { Text(stringResource(R.string.screen_room_attachment_source_gallery)) }, ) ListItem( - modifier = Modifier.clickable { eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) }, + modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) }, icon = { Icon(Icons.Default.AttachFile, null) }, text = { Text(stringResource(R.string.screen_room_attachment_source_files)) }, ) ListItem( - modifier = Modifier.clickable { eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera) }, + modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera) }, icon = { Icon(Icons.Default.PhotoCamera, null) }, text = { Text(stringResource(R.string.screen_room_attachment_source_camera_photo)) }, ) ListItem( - modifier = Modifier.clickable { eventSink(MessageComposerEvents.PickAttachmentSource.VideoFromCamera) }, + modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.VideoFromCamera) }, icon = { Icon(Icons.Default.Videocam, null) }, text = { Text(stringResource(R.string.screen_room_attachment_source_camera_video)) }, ) - ListItem( - modifier = Modifier.clickable { - eventSink(MessageComposerEvents.PickAttachmentSource.Location) - onSendLocationClicked() - }, - icon = { Icon(Icons.Default.LocationOn, null) }, - text = { Text(stringResource(R.string.screen_room_attachment_source_location)) }, - ) + if (state.canShareLocation) { + ListItem( + modifier = Modifier.clickable { + state.eventSink(MessageComposerEvents.PickAttachmentSource.Location) + onSendLocationClicked() + }, + icon = { Icon(Icons.Default.LocationOn, null) }, + text = { Text(stringResource(R.string.screen_room_attachment_source_location)) }, + ) + } } } @@ -136,7 +138,9 @@ internal fun AttachmentSourcePickerMenu( @Composable internal fun AttachmentSourcePickerMenuPreview() = ElementPreview { AttachmentSourcePickerMenu( - eventSink = {}, + state = aMessageComposerState( + canShareLocation = true, + ), onSendLocationClicked = {}, ) } 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 020236e890..ec16d3342d 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 @@ -74,6 +74,11 @@ class MessageComposerPresenter @Inject constructor( mutableStateOf(AttachmentsState.None) } + val canShareLocation = remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + canShareLocation.value = featureFlagService.isFeatureEnabled(FeatureFlags.LocationSharing) + } + val galleryMediaPicker = mediaPickerProvider.registerGalleryPicker { uri, mimeType -> handlePickedMedia(attachmentsState, uri, mimeType) } @@ -140,23 +145,23 @@ class MessageComposerPresenter @Inject constructor( ) ) } - MessageComposerEvents.AddAttachment -> localCoroutineScope.launchIfMediaPickerEnabled { + MessageComposerEvents.AddAttachment -> localCoroutineScope.launch { showAttachmentSourcePicker = true } MessageComposerEvents.DismissAttachmentMenu -> showAttachmentSourcePicker = false - MessageComposerEvents.PickAttachmentSource.FromGallery -> localCoroutineScope.launchIfMediaPickerEnabled { + MessageComposerEvents.PickAttachmentSource.FromGallery -> localCoroutineScope.launch { showAttachmentSourcePicker = false galleryMediaPicker.launch() } - MessageComposerEvents.PickAttachmentSource.FromFiles -> localCoroutineScope.launchIfMediaPickerEnabled { + MessageComposerEvents.PickAttachmentSource.FromFiles -> localCoroutineScope.launch { showAttachmentSourcePicker = false filesPicker.launch() } - MessageComposerEvents.PickAttachmentSource.PhotoFromCamera -> localCoroutineScope.launchIfMediaPickerEnabled { + MessageComposerEvents.PickAttachmentSource.PhotoFromCamera -> localCoroutineScope.launch { showAttachmentSourcePicker = false cameraPhotoPicker.launch() } - MessageComposerEvents.PickAttachmentSource.VideoFromCamera -> localCoroutineScope.launchIfMediaPickerEnabled { + MessageComposerEvents.PickAttachmentSource.VideoFromCamera -> localCoroutineScope.launch { showAttachmentSourcePicker = false cameraVideoPicker.launch() } @@ -173,17 +178,12 @@ class MessageComposerPresenter @Inject constructor( hasFocus = hasFocus.value, mode = messageComposerContext.composerMode, showAttachmentSourcePicker = showAttachmentSourcePicker, + canShareLocation = canShareLocation.value, attachmentsState = attachmentsState.value, eventSink = ::handleEvents ) } - private fun CoroutineScope.launchIfMediaPickerEnabled(action: suspend () -> Unit) = launch { - if (featureFlagService.isFeatureEnabled(FeatureFlags.ShowMediaUploadingFlow)) { - action() - } - } - private fun CoroutineScope.sendMessage( text: String, updateComposerMode: (newComposerMode: MessageComposerMode) -> Unit, 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 28ec14ffeb..32faaf9d81 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 @@ -28,6 +28,7 @@ data class MessageComposerState( val hasFocus: Boolean, val mode: MessageComposerMode, val showAttachmentSourcePicker: Boolean, + val canShareLocation: Boolean, val attachmentsState: AttachmentsState, val eventSink: (MessageComposerEvents) -> Unit ) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt index 1934154824..a1fbb7ffa0 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 @@ -26,12 +26,21 @@ open class MessageComposerStateProvider : PreviewParameterProvider Unit, + addMoreButton: @Composable () -> Unit, + modifier: Modifier = Modifier, + itemSpacing: Dp = 0.dp, + rowSpacing: Dp = 0.dp, + expanded: Boolean = false, + rowsBeforeCollapsible: Int? = 2, + reactions: @Composable () -> Unit, +) { + SubcomposeLayout(modifier) { constraints -> + // Given the placeables and returns a structure representing + // how they should wrap on to multiple rows given the constraints max width. + fun calculateRows(measurables: List): List> { + val rows = mutableListOf>() + var currentRow = mutableListOf() + var rowX = 0 + + measurables.forEach { placeable -> + val horizontalSpacing = if (currentRow.isEmpty()) 0 else itemSpacing.toPx().toInt() + // If the current view does not fit on this row bump to the next + if (rowX + placeable.width > constraints.maxWidth) { + rows.add(currentRow) + currentRow = mutableListOf() + rowX = 0 + } + rowX += horizontalSpacing + placeable.width + currentRow.add(placeable) + } + // If there are items in the current row remember to append it to the returned value + if (currentRow.size > 0) { + rows.add(currentRow) + } + return rows + } + + // Used to render the collapsed state, this takes the rows inputted and adds the extra button to the last row, + // removing only as many trailing reactions as needed to make space for it. + fun replaceTrailingItemsWithButtons(rowsIn: List>, expandButton: Placeable, addMoreButton: Placeable): List> { + val rows = rowsIn.toMutableList() + val lastRow = rows.last() + val buttonsWidth = expandButton.width + itemSpacing.toPx().toInt() + addMoreButton.width + var rowX = 0 + lastRow.forEachIndexed { i, placeable -> + val horizontalSpacing = if (i == 0) 0 else itemSpacing.toPx().toInt() + rowX += placeable.width + horizontalSpacing + if (rowX > constraints.maxWidth - (buttonsWidth + horizontalSpacing)) { + val lastRowWithButton = lastRow.take(i) + listOf(expandButton, addMoreButton) + rows[rows.size - 1] = lastRowWithButton + return rows + } + } + val lastRowWithButton = lastRow + listOf(expandButton, addMoreButton) + rows[rows.size - 1] = lastRowWithButton + return rows + } + + // To prevent the add more and expand buttons from wrapping on to separate lines. + // If there is one item on the last line, it moves the expand button down. + fun ensureCollapseAndAddMoreButtonsAreOnTheSameRow(rowsIn: List>): List> { + val lastRow = rowsIn.last().toMutableList() + if (lastRow.size != 1) { + return rowsIn + } + val rows = rowsIn.toMutableList() + val secondLastRow = rows[rows.size - 2].toMutableList() + val expandButtonPlaceable = secondLastRow.removeLast() + lastRow.add(0, expandButtonPlaceable) + rows[rows.size - 2] = secondLastRow + rows[rows.size - 1] = lastRow + return rows + } + + /// Given a list of rows place them in the layout. + fun layoutRows(rows: List>): MeasureResult { + var width = 0 + var height = 0 + val placeables = rows.mapIndexed { i, row -> + var rowX = 0 + var rowHeight = 0 + val verticalSpacing = if (i == 0) 0 else rowSpacing.toPx().toInt() + val rowWithPoints = row.mapIndexed { j, placeable -> + val horizontalSpacing = if (j == 0) 0 else itemSpacing.toPx().toInt() + val point = IntOffset(rowX + horizontalSpacing, height + verticalSpacing) + rowX += placeable.width + horizontalSpacing + rowHeight = maxOf(rowHeight, placeable.height) + Pair(placeable, point) + } + height += rowHeight + verticalSpacing + width = maxOf(width, rowX) + rowWithPoints + }.flatten() + + return layout(width = width, height = height) { + placeables.forEach { + val (placeable, origin) = it + placeable.placeRelative(origin.x, origin.y) + } + } + } + + val reactionsPlaceables = subcompose(0, reactions).map { it.measure(constraints) } + if (reactionsPlaceables.isEmpty()) { + return@SubcomposeLayout layoutRows(listOf()) + } + val addMorePlaceable = subcompose(1, addMoreButton).first().measure(constraints) + val expandPlaceable = subcompose(2, expandButton).first().measure(constraints) + + // Calculate the layout of the rows with the reactions button and add more button + val reactionsAndAddMore = calculateRows(reactionsPlaceables + listOf(addMorePlaceable)) + // If we have extended beyond the defined number of rows we are showing the expand/collapse ui + if (rowsBeforeCollapsible?.let { reactionsAndAddMore.size > it } == true) { + if (expanded) { + // Show all subviews with the add more button at the end + var reactionsAndButtons = calculateRows(reactionsPlaceables + listOf(expandPlaceable, addMorePlaceable)) + reactionsAndButtons = ensureCollapseAndAddMoreButtonsAreOnTheSameRow(reactionsAndButtons) + layoutRows(reactionsAndButtons) + } else { + // Truncate to `rowsBeforeCollapsible` number of rows and replace the reactions at the end of the last row with the buttons + val collapsedRows = reactionsAndAddMore.take(rowsBeforeCollapsible) + val collapsedRowsWithButtons = replaceTrailingItemsWithButtons(collapsedRows, expandPlaceable, addMorePlaceable) + layoutRows(collapsedRowsWithButtons) + } + } else { + // Otherwise we are just showing all items without the expand button + layoutRows(reactionsAndAddMore) + } + } +} + +@DayNightPreviews +@Composable +internal fun TimelineItemReactionsLayoutPreview() = ElementPreview { + TimelineItemReactionsLayout( + expanded = false, + expandButton = { + MessagesReactionButton( + content = MessagesReactionsButtonContent.Text( + text = stringResource(id = R.string.screen_room_timeline_less_reactions) + ), + onClick = { }, + ) + }, + addMoreButton = { + MessagesReactionButton( + content = MessagesReactionsButtonContent.Icon(Icons.Outlined.AddReaction), + onClick = {} + ) + }, + reactions = { + io.element.android.features.messages.impl.timeline.aTimelineItemReactions(count = 18).reactions.forEach { + MessagesReactionButton( + content = MessagesReactionsButtonContent.Reaction( + it + ), + onClick = {} + ) + } + } + ) +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt index ca0a58da70..962d3a2b2b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsView.kt @@ -19,18 +19,16 @@ package io.element.android.features.messages.impl.timeline.components import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AddReaction import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp -import com.google.accompanist.flowlayout.FlowMainAxisAlignment -import com.google.accompanist.flowlayout.FlowRow import io.element.android.features.messages.impl.R import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.features.messages.impl.timeline.model.AggregatedReaction @@ -38,162 +36,119 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItemReac import io.element.android.libraries.designsystem.preview.DayNightPreviews import io.element.android.libraries.designsystem.preview.ElementPreview import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toPersistentList - -/** - * The maximum number of items that can be displayed before some items will be hidden - * - * TODO The threshold should be based on the number of rows, rather than items. - * Once items would spill onto a third row, they should be hidden. - * Note this could be particularly worthwhile to handle reactions that are - * longer than a single character (as annotation keys are free text). - */ -private const val COLLAPSE_ITEMS_THRESHOLD = 8 @Composable fun TimelineItemReactions( reactionsState: TimelineItemReactions, - mainAxisAlignment: FlowMainAxisAlignment, + isOutgoing: Boolean, onReactionClicked: (emoji: String) -> Unit, onMoreReactionsClicked: () -> Unit, modifier: Modifier = Modifier, ) { var expanded: Boolean by rememberSaveable { mutableStateOf(false) } - val reactions by remember(reactionsState, expanded) { - derivedStateOf { - val numToDisplay = if (expanded) { - reactionsState.reactions.count() - } else { - COLLAPSE_ITEMS_THRESHOLD - } - reactionsState.reactions.take(numToDisplay).toPersistentList() - } + // In LTR languages we want an incoming message's reactions to be LRT and outgoing to be RTL. + // For RTL languages it should be the opposite. + val reactionsLayoutDirection = if (!isOutgoing) LocalLayoutDirection.current + else if (LocalLayoutDirection.current == LayoutDirection.Ltr) + LayoutDirection.Rtl + else + LayoutDirection.Ltr + + CompositionLocalProvider(LocalLayoutDirection provides reactionsLayoutDirection) { + TimelineItemReactionsView( + modifier = modifier, + reactions = reactionsState.reactions, + expanded = expanded, + onReactionClick = onReactionClicked, + onMoreReactionsClick = onMoreReactionsClicked, + onToggleExpandClick = { expanded = !expanded }, + ) } - - val expandableState by remember { - derivedStateOf { - if (expanded) { - ExpandableState.Expanded - } else { - val hiddenItems = reactionsState.reactions.count() - reactions.count() - if (hiddenItems > 0) { - ExpandableState.Collapsed(hidden = hiddenItems) - } else { - ExpandableState.None - } - } - } - } - - TimelineItemReactionsView( - modifier = modifier, - reactions = reactions, - expandableState = expandableState, - mainAxisAlignment = mainAxisAlignment, - onReactionClick = onReactionClicked, - onMoreReactionsClick = onMoreReactionsClicked, - onExpandClick = { expanded = true }, - onCollapseClick = { expanded = false } - ) -} - -private sealed class ExpandableState { - object None: ExpandableState() - data class Collapsed(val hidden: Int): ExpandableState() - object Expanded : ExpandableState() } @Composable private fun TimelineItemReactionsView( reactions: ImmutableList, - expandableState: ExpandableState, - mainAxisAlignment: FlowMainAxisAlignment, + expanded: Boolean, onReactionClick: (emoji: String) -> Unit, onMoreReactionsClick: () -> Unit, - onExpandClick: () -> Unit, - onCollapseClick: () -> Unit, + onToggleExpandClick: () -> Unit, modifier: Modifier = Modifier -) = - FlowRow( - modifier = modifier, - mainAxisSpacing = 4.dp, - crossAxisSpacing = 4.dp, - mainAxisAlignment = mainAxisAlignment, - ) { +) = TimelineItemReactionsLayout( + modifier = modifier, + itemSpacing = 4.dp, + rowSpacing = 4.dp, + expanded = expanded, + expandButton = { + MessagesReactionButton( + content = MessagesReactionsButtonContent.Text( + text = stringResource(id = if (expanded) R.string.screen_room_reactions_show_less else R.string.screen_room_reactions_show_more) + ), + onClick = onToggleExpandClick, + ) + }, + addMoreButton = { + MessagesReactionButton( + content = MessagesReactionsButtonContent.Icon(Icons.Outlined.AddReaction), + onClick = onMoreReactionsClick + ) + }, + reactions = { reactions.forEach { reaction -> MessagesReactionButton( content = MessagesReactionsButtonContent.Reaction(reaction = reaction), onClick = { onReactionClick(reaction.key) } ) } - when (expandableState) { - ExpandableState.Expanded -> - MessagesReactionButton( - content = MessagesReactionsButtonContent.Text( - text = stringResource(id = R.string.screen_room_timeline_less_reactions) - ), - onClick = onCollapseClick, - ) - is ExpandableState.Collapsed -> { - val hidden = expandableState.hidden - MessagesReactionButton( - content = MessagesReactionsButtonContent.Text( - text = pluralStringResource(id = R.plurals.screen_room_timeline_more_reactions, hidden, hidden) - ), - onClick = onExpandClick, - ) - } - ExpandableState.None -> { - // No expand or collapse action available - } - } - MessagesReactionButton( - content = MessagesReactionsButtonContent.Icon(Icons.Outlined.AddReaction), - onClick = onMoreReactionsClick - ) } +) @DayNightPreviews @Composable fun TimelineItemReactionsViewPreview() = ElementPreview { ContentToPreview( - reactions = aTimelineItemReactions(count = 1).reactions, - expandableState = ExpandableState.None, + reactions = aTimelineItemReactions(count = 1).reactions ) } @DayNightPreviews @Composable -fun TimelineItemReactionsViewCollapsedPreview() = ElementPreview { +fun TimelineItemReactionsViewFewPreview() = ElementPreview { ContentToPreview( - reactions = aTimelineItemReactions(count = 3).reactions, - expandableState = ExpandableState.Collapsed(hidden = 7), + reactions = aTimelineItemReactions(count = 3).reactions ) } @DayNightPreviews @Composable -fun TimelineItemReactionsViewExpandedPreview() = ElementPreview { +fun TimelineItemReactionsViewIncomingPreview() = ElementPreview { ContentToPreview( - reactions = aTimelineItemReactions(count = 10).reactions, - expandableState = ExpandableState.Expanded, + reactions = aTimelineItemReactions(count = 18).reactions + ) +} + +@DayNightPreviews +@Composable +fun TimelineItemReactionsViewOutgoingPreview() = ElementPreview { + ContentToPreview( + reactions = aTimelineItemReactions(count = 18).reactions, + isOutgoing = true ) } @Composable private fun ContentToPreview( reactions: ImmutableList, - expandableState: ExpandableState + isOutgoing: Boolean = false ) { - TimelineItemReactionsView( - reactions = reactions, - expandableState = expandableState, - mainAxisAlignment = FlowMainAxisAlignment.Center, - onReactionClick = {}, - onMoreReactionsClick = {}, - onExpandClick = {}, - onCollapseClick = {} + TimelineItemReactions( + reactionsState = TimelineItemReactions( + reactions + ), + isOutgoing = isOutgoing, + onReactionClicked = {}, + onMoreReactionsClicked = {}, ) } 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 d941b8a814..65101183d9 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 @@ -65,7 +65,7 @@ fun TimelineItem.Event.toExtraPadding(): ExtraPadding { fun ExtraPadding.getStr(fontSize: TextUnit): String { if (nbChars == 0) return "" val timestampFontSize = ElementTheme.typography.fontBodyXsRegular.fontSize // 11.sp - val nbOfSpaces = ((timestampFontSize.value / fontSize.value) * nbChars).toInt() + 1 + val nbOfSpaces = (timestampFontSize.value / fontSize.value * nbChars).toInt() + 1 // A space and some unbreakable spaces return " " + "\u00A0".repeat(nbOfSpaces) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt index 9755377b39..cf481a0aec 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt @@ -30,7 +30,7 @@ import io.element.android.libraries.ui.strings.CommonStrings @Composable fun TimelineItemEncryptedView( - content: TimelineItemEncryptedContent, + @Suppress("UNUSED_PARAMETER") content: TimelineItemEncryptedContent, extraPadding: ExtraPadding, modifier: Modifier = Modifier ) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemRedactedView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemRedactedView.kt index 44917c7d5b..749151b55f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemRedactedView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemRedactedView.kt @@ -29,7 +29,7 @@ import io.element.android.libraries.ui.strings.CommonStrings @Composable fun TimelineItemRedactedView( - content: TimelineItemRedactedContent, + @Suppress("UNUSED_PARAMETER") content: TimelineItemRedactedContent, extraPadding: ExtraPadding, modifier: Modifier = Modifier ) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemUnknownView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemUnknownView.kt index 852428cb92..f053c5a6a0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemUnknownView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemUnknownView.kt @@ -29,7 +29,7 @@ import io.element.android.libraries.ui.strings.CommonStrings @Composable fun TimelineItemUnknownView( - content: TimelineItemUnknownContent, + @Suppress("UNUSED_PARAMETER") content: TimelineItemUnknownContent, extraPadding: ExtraPadding, modifier: Modifier = Modifier ) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt index aeb2e7145e..8f8d767835 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt @@ -18,7 +18,6 @@ package io.element.android.features.messages.impl.timeline.components.event import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.runtime.Composable diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/group/GroupHeaderView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/group/GroupHeaderView.kt index b2d5dbc7f6..a631e34266 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/group/GroupHeaderView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/group/GroupHeaderView.kt @@ -35,7 +35,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview 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.theme.components.Icon 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 cb0264cf5d..ecb81a4367 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 @@ -61,7 +61,7 @@ import org.jsoup.nodes.Element import org.jsoup.nodes.Node import org.jsoup.nodes.TextNode -private const val chipId = "chip" +private const val CHIP_ID = "chip" @Composable fun HtmlDocument( @@ -351,7 +351,7 @@ private fun HtmlMxReply( Surface( modifier = modifier .padding(bottom = 4.dp) - .offset(x = -(8.dp)), + .offset(x = (-8).dp), color = MaterialTheme.colorScheme.background, shape = shape, ) { @@ -544,17 +544,27 @@ private fun AnnotatedString.Builder.appendLink(link: Element) { pop() } is PermalinkData.RoomEmailInviteLink -> { - appendInlineContent(chipId, link.ownText()) + safeAppendInlineContent(CHIP_ID, link.ownText()) } is PermalinkData.RoomLink -> { - appendInlineContent(chipId, link.ownText()) + safeAppendInlineContent(CHIP_ID, link.ownText()) } is PermalinkData.UserLink -> { - appendInlineContent(chipId, link.ownText()) + safeAppendInlineContent(CHIP_ID, link.ownText()) } } } +fun AnnotatedString.Builder.safeAppendInlineContent(chipId: String, ownText: String) { + if (ownText.isEmpty()) { + // alternateText cannot be empty and default parameter value is private, + // so just omit the second param here. + appendInlineContent(chipId) + } else { + appendInlineContent(chipId, ownText) + } +} + @Composable private fun HtmlText( text: AnnotatedString, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenu.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenu.kt index d344a5e995..cae55de241 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenu.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/retrysendmenu/RetrySendMessageMenu.kt @@ -31,7 +31,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp @@ -165,6 +164,7 @@ internal fun RetrySendMessageMenuPreviewDark(@PreviewParameter(RetrySendMenuStat } } +@Suppress("UNUSED_PARAMETER") @OptIn(ExperimentalMaterial3Api::class) @Composable private fun ContentToPreview(state: RetrySendMenuState) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineEncryptedHistoryBannerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineEncryptedHistoryBannerView.kt index 055e4bb876..65af6bdbf5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineEncryptedHistoryBannerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineEncryptedHistoryBannerView.kt @@ -30,7 +30,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.element.android.features.messages.impl.R import io.element.android.libraries.designsystem.preview.DayNightPreviews diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemDaySeparatorView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemDaySeparatorView.kt index c76d59806d..23e393ae8f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemDaySeparatorView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemDaySeparatorView.kt @@ -24,7 +24,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseMessageFactory.kt index 4dacb76523..e764cfa28f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseMessageFactory.kt @@ -23,7 +23,7 @@ import javax.inject.Inject class TimelineItemContentFailedToParseMessageFactory @Inject constructor() { - fun create(failedToParseMessageLike: FailedToParseMessageLikeContent): TimelineItemEventContent { + fun create(@Suppress("UNUSED_PARAMETER") failedToParseMessageLike: FailedToParseMessageLikeContent): TimelineItemEventContent { return TimelineItemUnknownContent } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseStateFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseStateFactory.kt index 7e2bf90050..f7d3c10483 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseStateFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFailedToParseStateFactory.kt @@ -23,6 +23,7 @@ import javax.inject.Inject class TimelineItemContentFailedToParseStateFactory @Inject constructor() { + @Suppress("UNUSED_PARAMETER") fun create(failedToParseState: FailedToParseStateContent): TimelineItemEventContent { return TimelineItemUnknownContent } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRedactedFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRedactedFactory.kt index dfbbd233c7..9419c7c31d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRedactedFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRedactedFactory.kt @@ -23,7 +23,7 @@ import javax.inject.Inject class TimelineItemContentRedactedFactory @Inject constructor() { - fun create(content: RedactedContent): TimelineItemEventContent { + fun create(@Suppress("UNUSED_PARAMETER") content: RedactedContent): TimelineItemEventContent { return TimelineItemRedactedContent } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStickerFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStickerFactory.kt index b823791912..1607bb77c4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStickerFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStickerFactory.kt @@ -23,7 +23,7 @@ import javax.inject.Inject class TimelineItemContentStickerFactory @Inject constructor() { - fun create(content: StickerContent): TimelineItemEventContent { + fun create(@Suppress("UNUSED_PARAMETER") content: StickerContent): TimelineItemEventContent { return TimelineItemUnknownContent } } 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 03dcaffac7..276fc30c94 100644 --- a/features/messages/impl/src/main/res/values-fr/translations.xml +++ b/features/messages/impl/src/main/res/values-fr/translations.xml @@ -5,7 +5,7 @@ "%1$d changements dans la conversation" - + "1 de plus" "%1$d de plus" "Appareil photo" 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 39b7e974be..07e62e9a4d 100644 --- a/features/messages/impl/src/main/res/values-sk/translations.xml +++ b/features/messages/impl/src/main/res/values-sk/translations.xml @@ -6,7 +6,7 @@ "%1$d zmien miestnosti" - + "%1$d ďalší" "%1$d ďalšie" "%1$d ďalších" @@ -22,6 +22,17 @@ "V tomto rozhovore ste sami" "Správa skopírovaná" "Nemáte povolenie uverejňovať príspevky v tejto miestnosti" + "Povoliť vlastné nastavenie" + "Zapnutím tohto nastavenia sa prepíše vaše predvolené nastavenie" + "Upozorniť ma v tejto konverzácii na" + "Môžete to zmeniť vo svojich %1$s." + "všeobecných nastaveniach" + "Predvolené nastavenie" + "Pri načítavaní nastavení oznámení došlo k chybe." + "Nepodarilo sa obnoviť predvolený režim, skúste to prosím znova." + "Nepodarilo sa nastaviť režim, skúste to prosím znova." + "Všetky správy" + "Iba zmienky a kľúčové slová" "Zobraziť menej" "Zobraziť viac" "Odoslať znova" 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 8e68da1d12..2ff244621d 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 @@ -17,10 +17,9 @@ package io.element.android.features.messages import android.net.Uri -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test -import com.google.common.collect.Iterables.skip import com.google.common.truth.Truth.assertThat import io.element.android.features.analytics.test.FakeAnalyticsService import io.element.android.features.messages.fixtures.aMessageEvent @@ -46,8 +45,6 @@ import io.element.android.features.messages.utils.messagesummary.FakeMessageSumm import io.element.android.features.networkmonitor.test.FakeNetworkMonitor import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.core.meta.BuildMeta -import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.designsystem.utils.SnackbarDispatcher import io.element.android.libraries.featureflag.test.FakeFeatureFlagService @@ -81,10 +78,10 @@ class MessagesPresenterTest { @Test fun `present - initial state`() = runTest { val presenter = createMessagePresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - + skipItems(1) val initialState = awaitItem() assertThat(initialState.roomId).isEqualTo(A_ROOM_ID) } @@ -95,14 +92,13 @@ class MessagesPresenterTest { val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) val room = FakeMatrixRoom() val presenter = createMessagePresenter(matrixRoom = room, coroutineDispatchers = coroutineDispatchers) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - + skipItems(1) val initialState = awaitItem() initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID)) assertThat(room.myReactions.count()).isEqualTo(1) - // No crashes when sending a reaction failed room.givenToggleReactionResult(Result.failure(IllegalStateException("Failed to send reaction"))) initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID)) @@ -116,10 +112,10 @@ class MessagesPresenterTest { val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) val room = FakeMatrixRoom() val presenter = createMessagePresenter(matrixRoom = room, coroutineDispatchers = coroutineDispatchers) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - + skipItems(1) val initialState = awaitItem() initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID)) assertThat(room.myReactions.count()).isEqualTo(1) @@ -133,10 +129,10 @@ class MessagesPresenterTest { fun `present - handle action forward`() = runTest { val navigator = FakeMessagesNavigator() val presenter = createMessagePresenter(navigator = navigator) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - + skipItems(1) val initialState = awaitItem() initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Forward, aMessageEvent())) assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None) @@ -149,10 +145,10 @@ class MessagesPresenterTest { val clipboardHelper = FakeClipboardHelper() val event = aMessageEvent() val presenter = createMessagePresenter(clipboardHelper = clipboardHelper) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - + skipItems(1) val initialState = awaitItem() initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Copy, event)) assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None) @@ -163,13 +159,12 @@ class MessagesPresenterTest { @Test fun `present - handle action reply`() = runTest { val presenter = createMessagePresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - + skipItems(1) val initialState = awaitItem() initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent())) - val finalState = awaitItem() assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java) assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None) @@ -179,10 +174,10 @@ class MessagesPresenterTest { @Test fun `present - handle action reply to an event with no id does nothing`() = runTest { val presenter = createMessagePresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - + skipItems(1) val initialState = awaitItem() initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent(eventId = null))) assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None) @@ -194,10 +189,10 @@ class MessagesPresenterTest { @Test fun `present - handle action reply to an image media message`() = runTest { val presenter = createMessagePresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - + skipItems(1) val initialState = awaitItem() val mediaMessage = aMessageEvent( content = TimelineItemImageContent( @@ -214,7 +209,6 @@ class MessagesPresenterTest { ) ) initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage)) - val finalState = awaitItem() assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java) val replyMode = finalState.composerState.mode as MessageComposerMode.Reply @@ -226,10 +220,10 @@ class MessagesPresenterTest { @Test fun `present - handle action reply to a video media message`() = runTest { val presenter = createMessagePresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - + skipItems(1) val initialState = awaitItem() val mediaMessage = aMessageEvent( content = TimelineItemVideoContent( @@ -247,7 +241,6 @@ class MessagesPresenterTest { ) ) initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage)) - val finalState = awaitItem() assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java) val replyMode = finalState.composerState.mode as MessageComposerMode.Reply @@ -259,10 +252,10 @@ class MessagesPresenterTest { @Test fun `present - handle action reply to a file media message`() = runTest { val presenter = createMessagePresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - + skipItems(1) val initialState = awaitItem() val mediaMessage = aMessageEvent( content = TimelineItemFileContent( @@ -275,7 +268,6 @@ class MessagesPresenterTest { ) ) initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage)) - val finalState = awaitItem() assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java) val replyMode = finalState.composerState.mode as MessageComposerMode.Reply @@ -287,13 +279,12 @@ class MessagesPresenterTest { @Test fun `present - handle action edit`() = runTest { val presenter = createMessagePresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - + skipItems(1) val initialState = awaitItem() initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent())) - val finalState = awaitItem() assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Edit::class.java) assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None) @@ -305,10 +296,10 @@ class MessagesPresenterTest { val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) val matrixRoom = FakeMatrixRoom() val presenter = createMessagePresenter(matrixRoom = matrixRoom, coroutineDispatchers = coroutineDispatchers) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - + skipItems(1) val initialState = awaitItem() initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Redact, aMessageEvent())) assertThat(matrixRoom.redactEventEventIdParam).isEqualTo(AN_EVENT_ID) @@ -320,10 +311,10 @@ class MessagesPresenterTest { fun `present - handle action report content`() = runTest { val navigator = FakeMessagesNavigator() val presenter = createMessagePresenter(navigator = navigator) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - + skipItems(1) val initialState = awaitItem() initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.ReportContent, aMessageEvent())) assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None) @@ -334,10 +325,10 @@ class MessagesPresenterTest { @Test fun `present - handle dismiss action`() = runTest { val presenter = createMessagePresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - + skipItems(1) val initialState = awaitItem() initialState.eventSink.invoke(MessagesEvents.Dismiss) assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None) @@ -348,10 +339,10 @@ class MessagesPresenterTest { fun `present - handle action show developer info`() = runTest { val navigator = FakeMessagesNavigator() val presenter = createMessagePresenter(navigator = navigator) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - + skipItems(1) val initialState = awaitItem() initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Developer, aMessageEvent())) assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None) @@ -363,20 +354,17 @@ class MessagesPresenterTest { fun `present - shows prompt to reinvite users in DM`() = runTest { val room = FakeMatrixRoom(sessionId = A_SESSION_ID, isDirect = true, activeMemberCount = 1L) val presenter = createMessagePresenter(matrixRoom = room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - + skipItems(1) val initialState = awaitItem() - // 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 = awaitItem() assertThat(focusedState.showReinvitePrompt).isTrue() - // If it's dismissed then we stop showing the alert initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Cancel)) val dismissedState = awaitItem() @@ -388,13 +376,12 @@ class MessagesPresenterTest { fun `present - doesn't show reinvite prompt in non-direct room`() = runTest { val room = FakeMatrixRoom(sessionId = A_SESSION_ID, isDirect = false, activeMemberCount = 1L) val presenter = createMessagePresenter(matrixRoom = room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - + skipItems(1) val initialState = awaitItem() assertThat(initialState.showReinvitePrompt).isFalse() - initialState.composerState.eventSink(MessageComposerEvents.FocusChanged(true)) val focusedState = awaitItem() assertThat(focusedState.showReinvitePrompt).isFalse() @@ -405,13 +392,12 @@ class MessagesPresenterTest { fun `present - doesn't show reinvite prompt if other party is present`() = runTest { val room = FakeMatrixRoom(sessionId = A_SESSION_ID, isDirect = true, activeMemberCount = 2L) val presenter = createMessagePresenter(matrixRoom = room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - + skipItems(1) val initialState = awaitItem() assertThat(initialState.showReinvitePrompt).isFalse() - initialState.composerState.eventSink(MessageComposerEvents.FocusChanged(true)) val focusedState = awaitItem() assertThat(focusedState.showReinvitePrompt).isFalse() @@ -430,17 +416,16 @@ class MessagesPresenterTest { ) ) val presenter = createMessagePresenter(matrixRoom = room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() skipItems(1) initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite)) - skipItems(1) val loadingState = awaitItem() assertThat(loadingState.inviteProgress.isLoading()).isTrue() - val newState = awaitItem() assertThat(newState.inviteProgress.isSuccess()).isTrue() assertThat(room.invitedUserId).isEqualTo(A_SESSION_ID_2) @@ -460,17 +445,16 @@ class MessagesPresenterTest { ) ) val presenter = createMessagePresenter(matrixRoom = room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() skipItems(1) initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite)) - skipItems(1) val loadingState = awaitItem() assertThat(loadingState.inviteProgress.isLoading()).isTrue() - val newState = awaitItem() assertThat(newState.inviteProgress.isSuccess()).isTrue() assertThat(room.invitedUserId).isEqualTo(A_SESSION_ID_2) @@ -482,17 +466,16 @@ class MessagesPresenterTest { val room = FakeMatrixRoom(sessionId = A_SESSION_ID) room.givenRoomMembersState(MatrixRoomMembersState.Unknown) val presenter = createMessagePresenter(matrixRoom = room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() skipItems(1) initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite)) - skipItems(1) val loadingState = awaitItem() assertThat(loadingState.inviteProgress.isLoading()).isTrue() - val newState = awaitItem() assertThat(newState.inviteProgress.isFailure()).isTrue() } @@ -511,16 +494,16 @@ class MessagesPresenterTest { ) room.givenInviteUserResult(Result.failure(Throwable("Oops!"))) val presenter = createMessagePresenter(matrixRoom = room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() skipItems(1) initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite)) skipItems(1) val loadingState = awaitItem() assertThat(loadingState.inviteProgress.isLoading()).isTrue() - val newState = awaitItem() assertThat(newState.inviteProgress.isFailure()).isTrue() } @@ -531,10 +514,10 @@ class MessagesPresenterTest { val matrixRoom = FakeMatrixRoom() matrixRoom.givenCanSendEventResult(MessageEventType.ROOM_MESSAGE, Result.success(true)) val presenter = createMessagePresenter(matrixRoom = matrixRoom) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - + skipItems(1) assertThat(awaitItem().userHasPermissionToSendMessage).isTrue() } } @@ -544,12 +527,12 @@ class MessagesPresenterTest { val matrixRoom = FakeMatrixRoom() matrixRoom.givenCanSendEventResult(MessageEventType.ROOM_MESSAGE, Result.success(false)) val presenter = createMessagePresenter(matrixRoom = matrixRoom) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { // Default value assertThat(awaitItem().userHasPermissionToSendMessage).isTrue() - skipItems(1) + skipItems(2) assertThat(awaitItem().userHasPermissionToSendMessage).isFalse() } } 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 0aafa68100..a2baa9dff7 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 @@ -16,7 +16,7 @@ package io.element.android.features.messages.actionlist -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -30,7 +30,6 @@ 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.aTimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent -import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.test.A_MESSAGE import io.element.android.libraries.matrix.test.core.aBuildMeta import kotlinx.collections.immutable.persistentListOf @@ -41,7 +40,7 @@ class ActionListPresenterTest { @Test fun `present - initial state`() = runTest { val presenter = anActionListPresenter(isBuildDebuggable = true) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -52,7 +51,7 @@ class ActionListPresenterTest { @Test fun `present - compute for message from me redacted`() = runTest { val presenter = anActionListPresenter(isBuildDebuggable = true) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -77,7 +76,7 @@ class ActionListPresenterTest { @Test fun `present - compute for message from others redacted`() = runTest { val presenter = anActionListPresenter(isBuildDebuggable = true) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -102,7 +101,7 @@ class ActionListPresenterTest { @Test fun `present - compute for others message`() = runTest { val presenter = anActionListPresenter(isBuildDebuggable = true) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -134,7 +133,7 @@ class ActionListPresenterTest { @Test fun `present - compute for my message`() = runTest { val presenter = anActionListPresenter(isBuildDebuggable = true) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -167,7 +166,7 @@ class ActionListPresenterTest { @Test fun `present - compute for a media item`() = runTest { val presenter = anActionListPresenter(isBuildDebuggable = true) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -198,7 +197,7 @@ class ActionListPresenterTest { @Test fun `present - compute for a state item in debug build`() = runTest { val presenter = anActionListPresenter(isBuildDebuggable = true) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -227,7 +226,7 @@ class ActionListPresenterTest { @Test fun `present - compute for a state item in non-debuggable build`() = runTest { val presenter = anActionListPresenter(isBuildDebuggable = false) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -255,7 +254,7 @@ class ActionListPresenterTest { @Test fun `present - compute message in non-debuggable build`() = runTest { val presenter = anActionListPresenter(isBuildDebuggable = false) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -287,7 +286,7 @@ class ActionListPresenterTest { @Test fun `present - compute message with no actions`() = runTest { val presenter = anActionListPresenter(isBuildDebuggable = false) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -314,7 +313,7 @@ class ActionListPresenterTest { @Test fun `present - compute not sent message`() = runTest { val presenter = anActionListPresenter(isBuildDebuggable = false) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 db202569ee..fb8b3b1948 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 @@ -19,7 +19,7 @@ package io.element.android.features.messages.attachments import android.net.Uri -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -55,7 +55,7 @@ class AttachmentsPreviewPresenterTest { ) ) val presenter = anAttachmentsPreviewPresenter(room = room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -77,7 +77,7 @@ class AttachmentsPreviewPresenterTest { val failure = MediaPreProcessor.Failure(null) room.givenSendMediaResult(Result.failure(failure)) val presenter = anAttachmentsPreviewPresenter(room = room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -86,7 +86,7 @@ class AttachmentsPreviewPresenterTest { val loadingState = awaitItem() assertThat(loadingState.sendActionState).isEqualTo(SendActionState.Sending.Processing) val failureState = awaitItem() - assertThat(failureState.sendActionState).isEqualTo((SendActionState.Failure(failure))) + assertThat(failureState.sendActionState).isEqualTo(SendActionState.Failure(failure)) assertThat(room.sendMediaCount).isEqualTo(0) failureState.eventSink(AttachmentsPreviewEvents.ClearSendState) val clearedState = awaitItem() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/media.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/media.kt index 1357c05913..00701c0a75 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/media.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/media.kt @@ -21,7 +21,6 @@ import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.media.local.LocalMedia import io.element.android.features.messages.impl.media.local.MediaInfo import io.element.android.features.messages.impl.media.local.anImageInfo -import io.element.android.libraries.core.mimetype.MimeTypes fun aLocalMedia( uri: Uri, 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 502305ab1d..9d19932716 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 @@ -16,7 +16,7 @@ package io.element.android.features.messages.forward -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -40,7 +40,7 @@ class ForwardMessagesPresenterTests { @Test fun `present - initial state`() = runTest { val presenter = aPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -60,7 +60,7 @@ class ForwardMessagesPresenterTests { @Test fun `present - toggle search active`() = runTest { val presenter = aPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -81,7 +81,7 @@ class ForwardMessagesPresenterTests { } val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource) val presenter = aPresenter(client = client) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -96,7 +96,7 @@ class ForwardMessagesPresenterTests { @Test fun `present - select a room and forward successful`() = runTest { val presenter = aPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -123,7 +123,7 @@ class ForwardMessagesPresenterTests { fun `present - select a room and forward failed, then clear`() = runTest { val room = FakeMatrixRoom() val presenter = aPresenter(fakeMatrixRoom = room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -151,7 +151,7 @@ class ForwardMessagesPresenterTests { @Test fun `present - select and remove a room`() = runTest { val presenter = aPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 2b66d2cf9b..3c31fa49d3 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 @@ -19,7 +19,7 @@ package io.element.android.features.messages.media.viewer import android.net.Uri -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -50,7 +50,7 @@ class MediaViewerPresenterTest { val mediaLoader = FakeMediaLoader() val mediaActions = FakeLocalMediaActions() val presenter = aMediaViewerPresenter(mediaLoader, mediaActions) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { var state = awaitItem() @@ -71,7 +71,7 @@ class MediaViewerPresenterTest { val mediaActions = FakeLocalMediaActions() val snackbarDispatcher = SnackbarDispatcher() val presenter = aMediaViewerPresenter(mediaLoader, mediaActions, snackbarDispatcher) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { var state = awaitItem() @@ -117,7 +117,7 @@ class MediaViewerPresenterTest { val mediaLoader = FakeMediaLoader() val mediaActions = FakeLocalMediaActions() val presenter = aMediaViewerPresenter(mediaLoader, mediaActions) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { mediaLoader.shouldFail = true 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 090dd36dbe..e6b0dee0c9 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 @@ -16,7 +16,7 @@ package io.element.android.features.messages.report -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -36,7 +36,7 @@ class ReportMessagePresenterTests { @Test fun `presenter - initial state`() = runTest { val presenter = aPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -49,7 +49,7 @@ class ReportMessagePresenterTests { @Test fun `presenter - update reason`() = runTest { val presenter = aPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -63,7 +63,7 @@ class ReportMessagePresenterTests { @Test fun `presenter - toggle block user`() = runTest { val presenter = aPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -81,7 +81,7 @@ class ReportMessagePresenterTests { fun `presenter - handle successful report and block user`() = runTest { val room = FakeMatrixRoom() val presenter = aPresenter(matrixRoom = room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -98,7 +98,7 @@ class ReportMessagePresenterTests { fun `presenter - handle successful report`() = runTest { val room = FakeMatrixRoom() val presenter = aPresenter(matrixRoom = room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -115,7 +115,7 @@ class ReportMessagePresenterTests { givenReportContentResult(Result.failure(Exception("Failed to report content"))) } val presenter = aPresenter(matrixRoom = room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 d3fda7b881..5c75c963e5 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 @@ -19,7 +19,7 @@ package io.element.android.features.messages.textcomposer import android.net.Uri -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.ReceiveTurbine import app.cash.turbine.test @@ -68,7 +68,7 @@ class MessageComposerPresenterTest { givenResult(mockk()) // Uri is not available in JVM, so the only way to have a non-null Uri is using Mockk } private val featureFlagService = FakeFeatureFlagService( - mapOf(FeatureFlags.ShowMediaUploadingFlow.key to true) + mapOf(FeatureFlags.LocationSharing.key to true) ) private val mediaPreProcessor = FakeMediaPreProcessor() private val snackbarDispatcher = SnackbarDispatcher() @@ -78,14 +78,16 @@ class MessageComposerPresenterTest { @Test fun `present - initial state`() = runTest { val presenter = createPresenter(this) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() assertThat(initialState.isFullScreen).isFalse() assertThat(initialState.text).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() } @@ -94,9 +96,10 @@ class MessageComposerPresenterTest { @Test fun `present - toggle fullscreen`() = runTest { val presenter = createPresenter(this) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() initialState.eventSink.invoke(MessageComposerEvents.ToggleFullScreenState) val fullscreenState = awaitItem() @@ -110,9 +113,10 @@ class MessageComposerPresenterTest { @Test fun `present - change message`() = runTest { val presenter = createPresenter(this) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() initialState.eventSink.invoke(MessageComposerEvents.UpdateText(A_MESSAGE)) val withMessageState = awaitItem() @@ -128,9 +132,10 @@ class MessageComposerPresenterTest { @Test fun `present - change mode to edit`() = runTest { val presenter = createPresenter(this) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) var state = awaitItem() val mode = anEditMode() state.eventSink.invoke(MessageComposerEvents.SetMode(mode)) @@ -146,9 +151,10 @@ class MessageComposerPresenterTest { @Test fun `present - change mode to reply`() = runTest { val presenter = createPresenter(this) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) var state = awaitItem() val mode = aReplyMode() state.eventSink.invoke(MessageComposerEvents.SetMode(mode)) @@ -163,9 +169,10 @@ class MessageComposerPresenterTest { @Test fun `present - change mode to quote`() = runTest { val presenter = createPresenter(this) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) var state = awaitItem() val mode = aQuoteMode() state.eventSink.invoke(MessageComposerEvents.SetMode(mode)) @@ -180,9 +187,10 @@ class MessageComposerPresenterTest { @Test fun `present - send message`() = runTest { val presenter = createPresenter(this) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() initialState.eventSink.invoke(MessageComposerEvents.UpdateText(A_MESSAGE)) val withMessageState = awaitItem() @@ -202,9 +210,10 @@ class MessageComposerPresenterTest { this, fakeMatrixRoom, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() assertThat(initialState.text).isEqualTo("") val mode = anEditMode() @@ -233,9 +242,10 @@ class MessageComposerPresenterTest { this, fakeMatrixRoom, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() assertThat(initialState.text).isEqualTo("") val mode = anEditMode(eventId = null, transactionId = A_TRANSACTION_ID) @@ -264,9 +274,10 @@ class MessageComposerPresenterTest { this, fakeMatrixRoom, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() assertThat(initialState.text).isEqualTo("") val mode = aReplyMode() @@ -291,9 +302,10 @@ class MessageComposerPresenterTest { @Test fun `present - Open attachments menu`() = runTest { val presenter = createPresenter(this) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() assertThat(initialState.showAttachmentSourcePicker).isEqualTo(false) initialState.eventSink(MessageComposerEvents.AddAttachment) @@ -304,9 +316,10 @@ class MessageComposerPresenterTest { @Test fun `present - Dismiss attachments menu`() = runTest { val presenter = createPresenter(this) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() initialState.eventSink(MessageComposerEvents.AddAttachment) skipItems(1) @@ -338,9 +351,10 @@ class MessageComposerPresenterTest { ) ) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) val previewingState = awaitItem() @@ -372,9 +386,10 @@ class MessageComposerPresenterTest { ) ) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) val previewingState = awaitItem() @@ -390,9 +405,10 @@ class MessageComposerPresenterTest { givenResult(null) // Simulate a user canceling the flow givenMimeType(MimeTypes.Images) } - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) // No crashes here, otherwise it fails @@ -410,9 +426,10 @@ class MessageComposerPresenterTest { ) ) val presenter = createPresenter(this, room = room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) val sendingState = awaitItem() @@ -431,9 +448,10 @@ class MessageComposerPresenterTest { fun `present - Take photo`() = runTest { val room = FakeMatrixRoom() val presenter = createPresenter(this, room = room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() initialState.eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera) val previewingState = awaitItem() @@ -447,9 +465,10 @@ class MessageComposerPresenterTest { fun `present - Record video`() = runTest { val room = FakeMatrixRoom() val presenter = createPresenter(this, room = room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() initialState.eventSink(MessageComposerEvents.PickAttachmentSource.VideoFromCamera) val previewingState = awaitItem() @@ -464,9 +483,10 @@ class MessageComposerPresenterTest { givenSendMediaResult(Result.failure(Exception())) } val presenter = createPresenter(this, room = room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { + skipItems(1) val initialState = awaitItem() initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) val sendingState = awaitItem() 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 c1d414c633..0dfe6fd53c 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 @@ -16,7 +16,7 @@ package io.element.android.features.messages.timeline -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -42,7 +42,7 @@ class TimelinePresenterTest { @Test fun `present - initial state`() = runTest { val presenter = createTimelinePresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -55,7 +55,7 @@ class TimelinePresenterTest { @Test fun `present - load more`() = runTest { val presenter = createTimelinePresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -74,7 +74,7 @@ class TimelinePresenterTest { @Test fun `present - set highlighted event`() = runTest { val presenter = createTimelinePresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -97,7 +97,7 @@ class TimelinePresenterTest { ) ) val presenter = createTimelinePresenter(timeline) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { assertThat(timeline.sendReadReceiptCount).isEqualTo(0) @@ -121,7 +121,7 @@ class TimelinePresenterTest { ) ) val presenter = createTimelinePresenter(timeline) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { assertThat(timeline.sendReadReceiptCount).isEqualTo(0) @@ -145,7 +145,7 @@ class TimelinePresenterTest { ) ) val presenter = createTimelinePresenter(timeline) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { assertThat(timeline.sendReadReceiptCount).isEqualTo(0) @@ -165,7 +165,7 @@ class TimelinePresenterTest { fun `present - covers hasNewItems scenarios`() = runTest { val timeline = FakeMatrixTimeline() val presenter = createTimelinePresenter(timeline) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 237cb81d38..1c40483ffe 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 @@ -16,7 +16,7 @@ package io.element.android.features.messages.timeline.components.customreaction -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -32,7 +32,7 @@ class CustomReactionPresenterTests { @Test fun `present - handle selecting and de-selecting an event`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 1e467b82af..4f4f0a0ee4 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 @@ -16,7 +16,7 @@ package io.element.android.features.messages.timeline.components.retrysendmenu -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -35,7 +35,7 @@ class RetrySendMenuPresenterTests { @Test fun `present - handle event selected`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -48,7 +48,7 @@ class RetrySendMenuPresenterTests { @Test fun `present - handle dismiss`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -63,7 +63,7 @@ class RetrySendMenuPresenterTests { @Test fun `present - handle resend with transactionId`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -79,7 +79,7 @@ class RetrySendMenuPresenterTests { @Test fun `present - handle resend without transactionId`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -96,7 +96,7 @@ class RetrySendMenuPresenterTests { @Test fun `present - handle resend with error`() = runTest { room.givenRetrySendMessageResult(Result.failure(IllegalStateException("An error"))) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -112,7 +112,7 @@ class RetrySendMenuPresenterTests { @Test fun `present - handle remove failed message with transactionId`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -128,7 +128,7 @@ class RetrySendMenuPresenterTests { @Test fun `present - handle remove failed message without transactionId`() = runTest { - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -145,7 +145,7 @@ class RetrySendMenuPresenterTests { @Test fun `present - handle remove failed message with error`() = runTest { room.givenRetrySendMessageResult(Result.failure(IllegalStateException("An error"))) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 7d01d668c9..bf05dbc5f5 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 @@ -42,7 +42,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.element.android.libraries.designsystem.preview.ElementPreviewDark diff --git a/features/onboarding/impl/build.gradle.kts b/features/onboarding/impl/build.gradle.kts index 0952835d46..0f97a0ffeb 100644 --- a/features/onboarding/impl/build.gradle.kts +++ b/features/onboarding/impl/build.gradle.kts @@ -48,6 +48,4 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) - - androidTestImplementation(libs.test.junitext) } 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 f415cd795f..d336e5b466 100644 --- a/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt +++ b/features/onboarding/impl/src/test/kotlin/io/element/android/features/onboarding/impl/OnBoardingPresenterTest.kt @@ -16,7 +16,7 @@ package io.element.android.features.onboarding.impl -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -27,7 +27,7 @@ class OnBoardingPresenterTest { @Test fun `present - initial state`() = runTest { val presenter = OnBoardingPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts index f183f7f1fa..4ffca9d239 100644 --- a/features/preferences/impl/build.gradle.kts +++ b/features/preferences/impl/build.gradle.kts @@ -47,6 +47,7 @@ dependencies { implementation(projects.features.ftue.api) implementation(projects.libraries.matrixui) implementation(projects.features.logout.api) + implementation(projects.services.analytics.api) implementation(projects.services.toolbox.api) implementation(libs.datetime) implementation(libs.accompanist.placeholder) @@ -68,6 +69,4 @@ dependencies { testImplementation(projects.features.analytics.test) testImplementation(projects.features.analytics.impl) testImplementation(projects.tests.testutils) - - androidTestImplementation(libs.test.junitext) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/ElementLegal.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/ElementLegal.kt index 81af611716..e09e0df8f8 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/ElementLegal.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/ElementLegal.kt @@ -19,17 +19,17 @@ package io.element.android.features.preferences.impl.about import androidx.annotation.StringRes import io.element.android.libraries.ui.strings.CommonStrings -private const val CopyrightUrl = "https://element.io/copyright" -private const val UsePolicyUrl = "https://element.io/acceptable-use-policy-terms" -private const val PrivacyUrl = "https://element.io/privacy" +private const val COPYRIGHT_URL = "https://element.io/copyright" +private const val USE_POLICY_URL = "https://element.io/acceptable-use-policy-terms" +private const val PRIVACY_URL = "https://element.io/privacy" sealed class ElementLegal( @StringRes val titleRes: Int, val url: String, ) { - object Copyright : ElementLegal(CommonStrings.common_copyright, CopyrightUrl) - object AcceptableUsePolicy : ElementLegal(CommonStrings.common_acceptable_use_policy, UsePolicyUrl) - object PrivacyPolicy : ElementLegal(CommonStrings.common_privacy_policy, PrivacyUrl) + object Copyright : ElementLegal(CommonStrings.common_copyright, COPYRIGHT_URL) + object AcceptableUsePolicy : ElementLegal(CommonStrings.common_acceptable_use_policy, USE_POLICY_URL) + object PrivacyPolicy : ElementLegal(CommonStrings.common_privacy_policy, PRIVACY_URL) } fun getAllLegals(): List { 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 1ead19154d..7c5b5d91c8 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 @@ -56,6 +56,12 @@ fun DeveloperSettingsView( RageshakePreferencesView( state = state.rageshakeState, ) + PreferenceCategory(title = "Crash", showDivider = false) { + PreferenceText( + title = "Crash the app 💥", + onClick = { error("This crash is a test.") } + ) + } val cache = state.cacheSize PreferenceCategory(title = "Cache", showDivider = false) { PreferenceText( diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt index 66ff62ee2b..0cd2e7f7db 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt @@ -35,6 +35,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.getCurrentUser import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus +import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import javax.inject.Inject @@ -43,6 +44,7 @@ class PreferencesRootPresenter @Inject constructor( private val logoutPresenter: LogoutPreferencePresenter, private val matrixClient: MatrixClient, private val sessionVerificationService: SessionVerificationService, + private val analyticsService: AnalyticsService, private val buildType: BuildType, private val versionFormatter: VersionFormatter, private val snackbarDispatcher: SnackbarDispatcher, @@ -58,6 +60,7 @@ class PreferencesRootPresenter @Inject constructor( } val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() + val hasAnalyticsProviders = remember { analyticsService.getAvailableAnalyticsProviders().isNotEmpty() } // Session verification status (unknown, not verified, verified) val sessionVerifiedStatus by sessionVerificationService.sessionVerifiedStatus.collectAsState() @@ -72,6 +75,7 @@ class PreferencesRootPresenter @Inject constructor( myUser = matrixUser.value, version = versionFormatter.get(), showCompleteVerification = sessionIsNotVerified, + showAnalyticsSettings = hasAnalyticsProviders, showDeveloperSettings = showDeveloperSettings, snackbarMessage = snackbarMessage, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt index 2b0963c53c..540c470815 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt @@ -25,6 +25,7 @@ data class PreferencesRootState( val myUser: MatrixUser?, val version: String, val showCompleteVerification: Boolean, + val showAnalyticsSettings: Boolean, val showDeveloperSettings: Boolean, val snackbarMessage: SnackbarMessage?, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt index 9dbd54ffff..e8c148267f 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt @@ -25,6 +25,7 @@ fun aPreferencesRootState() = PreferencesRootState( myUser = null, version = "Version 1.1 (1)", showCompleteVerification = true, + showAnalyticsSettings = true, showDeveloperSettings = true, snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete), ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt index 01d790f8b9..90eade31ab 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt @@ -82,11 +82,13 @@ fun PreferencesRootView( ) Divider() } - PreferenceText( - title = stringResource(id = CommonStrings.common_analytics), - icon = Icons.Outlined.InsertChart, - onClick = onOpenAnalytics, - ) + if (state.showAnalyticsSettings) { + PreferenceText( + title = stringResource(id = CommonStrings.common_analytics), + icon = Icons.Outlined.InsertChart, + onClick = onOpenAnalytics, + ) + } PreferenceText( title = stringResource(id = CommonStrings.action_report_bug), icon = Icons.Outlined.BugReport, 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 97fa158d09..4b025c10ad 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 @@ -16,7 +16,7 @@ package io.element.android.features.preferences.impl.about -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -27,7 +27,7 @@ class AboutPresenterTest { @Test fun `present - initial state`() = runTest { val presenter = AboutPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsAnalyticsSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsAnalyticsSettingsPresenterTest.kt index 5382ad0b37..29cc25e5be 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsAnalyticsSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsAnalyticsSettingsPresenterTest.kt @@ -16,7 +16,7 @@ package io.element.android.features.preferences.impl.analytics -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -33,7 +33,7 @@ class AnalyticsAnalyticsSettingsPresenterTest { val presenter = AnalyticsSettingsPresenter( analyticsPresenter, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 87a556621c..9b1bda3631 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 @@ -16,7 +16,7 @@ package io.element.android.features.preferences.impl.developer -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -41,7 +41,7 @@ class DeveloperSettingsPresenterTest { FakeClearCacheUseCase(), rageshakePresenter ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -65,7 +65,7 @@ class DeveloperSettingsPresenterTest { FakeClearCacheUseCase(), rageshakePresenter, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -84,7 +84,7 @@ class DeveloperSettingsPresenterTest { FakeClearCacheUseCase(), rageshakePresenter, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -109,7 +109,7 @@ class DeveloperSettingsPresenterTest { clearCacheUseCase, rageshakePresenter, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) 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 f3cf23599f..580426fcfa 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 @@ -16,10 +16,11 @@ package io.element.android.features.preferences.impl.root -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import io.element.android.features.analytics.test.FakeAnalyticsService import io.element.android.features.logout.impl.DefaultLogoutPreferencePresenter import io.element.android.libraries.architecture.Async import io.element.android.libraries.core.meta.BuildType @@ -41,11 +42,12 @@ class PreferencesRootPresenterTest { logoutPresenter, matrixClient, FakeSessionVerificationService(), + FakeAnalyticsService(), BuildType.DEBUG, FakeVersionFormatter(), SnackbarDispatcher(), ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -61,6 +63,7 @@ class PreferencesRootPresenterTest { ) ) assertThat(loadedState.showDeveloperSettings).isEqualTo(true) + assertThat(loadedState.showAnalyticsSettings).isEqualTo(false) } } } diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionState.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionState.kt index 5d6df8bac9..04f38597ed 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionState.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionState.kt @@ -17,7 +17,6 @@ package io.element.android.features.rageshake.api.detection import androidx.compose.runtime.Immutable -import androidx.compose.runtime.Stable import io.element.android.features.rageshake.api.preferences.RageshakePreferencesState @Immutable diff --git a/features/rageshake/impl/build.gradle.kts b/features/rageshake/impl/build.gradle.kts index 137a3bd070..3283e3f37a 100644 --- a/features/rageshake/impl/build.gradle.kts +++ b/features/rageshake/impl/build.gradle.kts @@ -56,6 +56,4 @@ dependencies { testImplementation(libs.test.mockk) testImplementation(projects.libraries.matrix.test) testImplementation(projects.features.rageshake.test) - - androidTestImplementation(libs.test.junitext) } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/VectorFileLogger.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/VectorFileLogger.kt index eea6c1dbbf..6b5d7c9096 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/VectorFileLogger.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/VectorFileLogger.kt @@ -48,7 +48,7 @@ class VectorFileLogger( } private const val SIZE_20MB = 20 * 1024 * 1024 - private const val SIZE_50MB = 50 * 1024 * 1024 + // private const val SIZE_50MB = 50 * 1024 * 1024 } /* diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt index 5695596650..65dc48aca8 100755 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt @@ -87,7 +87,7 @@ class DefaultBugReporter @Inject constructor( // filenames private const val LOG_CAT_ERROR_FILENAME = "logcatError.log" private const val LOG_CAT_FILENAME = "logcat.log" - private const val KEY_REQUESTS_FILENAME = "keyRequests.log" + // private const val KEY_REQUESTS_FILENAME = "keyRequests.log" private const val BUFFER_SIZE = 1024 * 1024 * 50 } @@ -103,7 +103,7 @@ class DefaultBugReporter @Inject constructor( .adapter(Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java)) */ - private val LOGCAT_CMD_ERROR = arrayOf( + private val logcatCommandError = arrayOf( "logcat", // /< Run 'logcat' command "-d", // /< Dump the log rather than continue outputting it "-v", // formatting @@ -114,7 +114,7 @@ class DefaultBugReporter @Inject constructor( "*:S" // /< Everything else silent, so don't pick it.. ) - private val LOGCAT_CMD_DEBUG = arrayOf("logcat", "-d", "-v", "threadtime", "*:*") + private val logcatCommandDebug = arrayOf("logcat", "-d", "-v", "threadtime", "*:*") /** * Send a bug report. @@ -500,7 +500,7 @@ class DefaultBugReporter @Inject constructor( val logcatProc: Process try { - logcatProc = Runtime.getRuntime().exec(if (isErrorLogCat) LOGCAT_CMD_ERROR else LOGCAT_CMD_DEBUG) + logcatProc = Runtime.getRuntime().exec(if (isErrorLogCat) logcatCommandError else logcatCommandDebug) } catch (e1: IOException) { return } diff --git a/features/rageshake/impl/src/main/res/values-sk/translations.xml b/features/rageshake/impl/src/main/res/values-sk/translations.xml index cb530d1712..51222367a6 100644 --- a/features/rageshake/impl/src/main/res/values-sk/translations.xml +++ b/features/rageshake/impl/src/main/res/values-sk/translations.xml @@ -1,7 +1,7 @@ "Priložiť snímku obrazovky" - "V prípade ďalších otázok ma môžete kontaktovať" + "V prípade ďalších otázok ma môžete kontaktovať." "Kontaktujte ma" "Upraviť snímku obrazovky" "Popíšte prosím chybu. Čo ste urobili? Čo ste očakávali, že sa stane? Čo sa skutočne stalo. Prosím, uveďte čo najviac podrobností." 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 9b868a637d..c0418783dd 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 @@ -16,7 +16,7 @@ package io.element.android.features.rageshake.impl.bugreport -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -41,7 +41,7 @@ class BugReportPresenterTest { FakeScreenshotHolder(), this, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -62,7 +62,7 @@ class BugReportPresenterTest { FakeScreenshotHolder(), this, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -81,7 +81,7 @@ class BugReportPresenterTest { FakeScreenshotHolder(), this, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -100,7 +100,7 @@ class BugReportPresenterTest { FakeScreenshotHolder(), this, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -120,7 +120,7 @@ class BugReportPresenterTest { FakeScreenshotHolder(), this, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -139,7 +139,7 @@ class BugReportPresenterTest { FakeScreenshotHolder(screenshotUri = A_SCREENSHOT_URI), this, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -161,7 +161,7 @@ class BugReportPresenterTest { FakeScreenshotHolder(screenshotUri = A_SCREENSHOT_URI), this, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -186,7 +186,7 @@ class BugReportPresenterTest { FakeScreenshotHolder(screenshotUri = A_SCREENSHOT_URI), this, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -215,7 +215,7 @@ class BugReportPresenterTest { FakeScreenshotHolder(screenshotUri = A_SCREENSHOT_URI), this, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 2d9834607f..b8b8c4b6d0 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 @@ -16,7 +16,7 @@ package io.element.android.features.rageshake.impl.crash.ui -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -33,7 +33,7 @@ class CrashDetectionPresenterTest { val presenter = DefaultCrashDetectionPresenter( FakeCrashDataStore() ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -46,7 +46,7 @@ class CrashDetectionPresenterTest { val presenter = DefaultCrashDetectionPresenter( FakeCrashDataStore(appHasCrashed = true) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -61,7 +61,7 @@ class CrashDetectionPresenterTest { val presenter = DefaultCrashDetectionPresenter( FakeCrashDataStore(appHasCrashed = true) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -77,7 +77,7 @@ class CrashDetectionPresenterTest { val presenter = DefaultCrashDetectionPresenter( FakeCrashDataStore(appHasCrashed = true, crashData = A_CRASH_DATA) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) 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 eb49eb450e..02a0fc0794 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 @@ -17,7 +17,7 @@ package io.element.android.features.rageshake.impl.detection import android.graphics.Bitmap -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -59,7 +59,7 @@ class RageshakeDetectionPresenterTest { rageshakeDataStore = rageshakeDataStore, ) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -83,7 +83,7 @@ class RageshakeDetectionPresenterTest { rageshakeDataStore = rageshakeDataStore, ) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -108,7 +108,7 @@ class RageshakeDetectionPresenterTest { rageshakeDataStore = rageshakeDataStore, ) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -142,7 +142,7 @@ class RageshakeDetectionPresenterTest { rageshakeDataStore = rageshakeDataStore, ) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -176,7 +176,7 @@ class RageshakeDetectionPresenterTest { rageshakeDataStore = rageshakeDataStore, ) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) 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 b01ce22645..56759c360c 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 @@ -16,7 +16,7 @@ package io.element.android.features.rageshake.impl.preferences -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -34,7 +34,7 @@ class RageshakePreferencesPresenterTest { FakeRageShake(isAvailableValue = true), FakeRageshakeDataStore(isEnabled = true) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -50,7 +50,7 @@ class RageshakePreferencesPresenterTest { FakeRageShake(isAvailableValue = false), FakeRageshakeDataStore(isEnabled = true) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -66,7 +66,7 @@ class RageshakePreferencesPresenterTest { FakeRageShake(isAvailableValue = true), FakeRageshakeDataStore(isEnabled = true) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -85,7 +85,7 @@ class RageshakePreferencesPresenterTest { FakeRageShake(isAvailableValue = true), FakeRageshakeDataStore(isEnabled = true) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) 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 9aa8ea41c3..63868c8227 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 @@ -28,7 +28,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenter.kt index 00cb04b118..cc0886c3af 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenter.kt @@ -93,7 +93,7 @@ class RoomInviteMembersPresenter @Inject constructor( value = if (value.contains(user)) { value.filterNot { it == user } } else { - (value + user) + value + user }.toImmutableList() } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt index 0787563aed..49ae479d40 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt @@ -18,7 +18,6 @@ package io.element.android.features.roomdetails.impl.members import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -31,7 +30,6 @@ import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.room.MatrixRoom -import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.api.room.powerlevels.canInvite import kotlinx.collections.immutable.toImmutableList @@ -101,6 +99,5 @@ class RoomMemberListPresenter @Inject constructor( }, ) } - } 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 ccd1476c3a..08d6a58535 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt @@ -16,7 +16,7 @@ package io.element.android.features.roomdetails -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -60,7 +60,7 @@ class RoomDetailsPresenterTests { fun `present - initial state is created from room info`() = runTest { val room = aMatrixRoom() val presenter = aRoomDetailsPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -79,7 +79,7 @@ class RoomDetailsPresenterTests { fun `present - initial state with no room name`() = runTest { val room = aMatrixRoom(name = null) val presenter = aRoomDetailsPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -101,7 +101,7 @@ class RoomDetailsPresenterTests { givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers)) } val presenter = aRoomDetailsPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -117,7 +117,7 @@ class RoomDetailsPresenterTests { givenCanInviteResult(Result.success(true)) } val presenter = aRoomDetailsPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { // Initially false @@ -135,7 +135,7 @@ class RoomDetailsPresenterTests { givenCanInviteResult(Result.success(false)) } val presenter = aRoomDetailsPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { assertThat(awaitItem().canInvite).isFalse() @@ -148,7 +148,7 @@ class RoomDetailsPresenterTests { givenCanInviteResult(Result.failure(Throwable("Whoops"))) } val presenter = aRoomDetailsPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { assertThat(awaitItem().canInvite).isFalse() @@ -164,7 +164,7 @@ class RoomDetailsPresenterTests { givenCanInviteResult(Result.success(false)) } val presenter = aRoomDetailsPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { // Initially false @@ -193,7 +193,7 @@ class RoomDetailsPresenterTests { givenCanInviteResult(Result.success(false)) } val presenter = aRoomDetailsPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { // Initially false @@ -222,7 +222,7 @@ class RoomDetailsPresenterTests { givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true)) } val presenter = aRoomDetailsPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -243,7 +243,7 @@ class RoomDetailsPresenterTests { givenCanInviteResult(Result.success(false)) } val presenter = aRoomDetailsPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { // Initially false @@ -264,7 +264,7 @@ class RoomDetailsPresenterTests { givenCanInviteResult(Result.success(false)) } val presenter = aRoomDetailsPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { // Initially false, and no further events @@ -280,7 +280,7 @@ class RoomDetailsPresenterTests { } val presenter = aRoomDetailsPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { // The initial state is "hidden" and no further state changes happen @@ -296,7 +296,7 @@ class RoomDetailsPresenterTests { } val presenter = aRoomDetailsPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { // Ignore the initial state @@ -314,7 +314,7 @@ class RoomDetailsPresenterTests { val leaveRoomPresenter = LeaveRoomPresenterFake() val room = aMatrixRoom() val presenter = aRoomDetailsPresenter(room, leaveRoomPresenter) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { awaitItem().eventSink(RoomDetailsEvent.LeaveRoom) 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 20d253f3fb..e43703e235 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 @@ -17,7 +17,7 @@ package io.element.android.features.roomdetails.edit import android.net.Uri -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -82,7 +82,7 @@ class RoomDetailsEditPresenterTest { val room = aMatrixRoom(avatarUrl = AN_AVATAR_URL) val presenter = aRoomDetailsEditPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -109,7 +109,7 @@ class RoomDetailsEditPresenterTest { } val presenter = aRoomDetailsEditPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { // Initially false @@ -135,7 +135,7 @@ class RoomDetailsEditPresenterTest { } val presenter = aRoomDetailsEditPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { // Initially false @@ -161,7 +161,7 @@ class RoomDetailsEditPresenterTest { } val presenter = aRoomDetailsEditPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { // Initially false @@ -183,7 +183,7 @@ class RoomDetailsEditPresenterTest { val room = aMatrixRoom(topic = "My topic", name = "Name", avatarUrl = AN_AVATAR_URL) val presenter = aRoomDetailsEditPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -229,7 +229,7 @@ class RoomDetailsEditPresenterTest { val presenter = aRoomDetailsEditPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -250,7 +250,7 @@ class RoomDetailsEditPresenterTest { val presenter = aRoomDetailsEditPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -271,7 +271,7 @@ class RoomDetailsEditPresenterTest { val presenter = aRoomDetailsEditPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -323,7 +323,7 @@ class RoomDetailsEditPresenterTest { val presenter = aRoomDetailsEditPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -373,7 +373,7 @@ class RoomDetailsEditPresenterTest { val presenter = aRoomDetailsEditPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -398,7 +398,7 @@ class RoomDetailsEditPresenterTest { val presenter = aRoomDetailsEditPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -422,7 +422,7 @@ class RoomDetailsEditPresenterTest { val presenter = aRoomDetailsEditPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -445,7 +445,7 @@ class RoomDetailsEditPresenterTest { val presenter = aRoomDetailsEditPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -470,7 +470,7 @@ class RoomDetailsEditPresenterTest { val presenter = aRoomDetailsEditPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -495,7 +495,7 @@ class RoomDetailsEditPresenterTest { val presenter = aRoomDetailsEditPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -561,7 +561,7 @@ class RoomDetailsEditPresenterTest { val presenter = aRoomDetailsEditPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -580,7 +580,7 @@ class RoomDetailsEditPresenterTest { private suspend fun saveAndAssertFailure(room: MatrixRoom, event: RoomDetailsEditEvents) { val presenter = aRoomDetailsEditPresenter(room) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 8600cefeac..ede7342882 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 @@ -16,7 +16,7 @@ package io.element.android.features.roomdetails.impl.invite -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -52,7 +52,7 @@ internal class RoomInviteMembersPresenterTest { coroutineDispatchers = testCoroutineDispatchers() ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -74,7 +74,7 @@ internal class RoomInviteMembersPresenterTest { coroutineDispatchers = testCoroutineDispatchers() ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -95,7 +95,7 @@ internal class RoomInviteMembersPresenterTest { roomMemberListDataSource = createDataSource(FakeMatrixRoom()), coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -121,7 +121,7 @@ internal class RoomInviteMembersPresenterTest { roomMemberListDataSource = createDataSource(FakeMatrixRoom()), coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -173,7 +173,7 @@ internal class RoomInviteMembersPresenterTest { ), coroutineDispatchers = coroutineDispatchers ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -232,7 +232,7 @@ internal class RoomInviteMembersPresenterTest { coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -270,7 +270,7 @@ internal class RoomInviteMembersPresenterTest { coroutineDispatchers = testCoroutineDispatchers() ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -298,7 +298,7 @@ internal class RoomInviteMembersPresenterTest { roomMemberListDataSource = createDataSource(FakeMatrixRoom()), coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -339,7 +339,7 @@ internal class RoomInviteMembersPresenterTest { roomMemberListDataSource = createDataSource(FakeMatrixRoom()), coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 c3a79481e6..9de035d017 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 @@ -16,7 +16,7 @@ package io.element.android.features.roomdetails.members -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth @@ -44,7 +44,7 @@ class RoomMemberListPresenterTests { @Test fun `search is done automatically on start, but is async`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -52,7 +52,6 @@ class RoomMemberListPresenterTests { Truth.assertThat(initialState.searchQuery).isEmpty() Truth.assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.NotSearching::class.java) Truth.assertThat(initialState.isSearchActive).isFalse() - val loadedState = awaitItem() Truth.assertThat(loadedState.roomMembers).isInstanceOf(Async.Success::class.java) Truth.assertThat((loadedState.roomMembers as Async.Success).data.invited).isEqualTo(listOf(aVictor(), aWalter())) @@ -63,32 +62,30 @@ class RoomMemberListPresenterTests { @Test fun `open search`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - val initialState = awaitItem() + skipItems(1) val loadedState = awaitItem() - loadedState.eventSink(RoomMemberListEvents.OnSearchActiveChanged(true)) - val searchActiveState = awaitItem() - Truth.assertThat((searchActiveState.isSearchActive)).isTrue() + Truth.assertThat(searchActiveState.isSearchActive).isTrue() } } @Test fun `search for something which is not found`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - val initialState = awaitItem() + skipItems(1) val loadedState = awaitItem() loadedState.eventSink(RoomMemberListEvents.OnSearchActiveChanged(true)) val searchActiveState = awaitItem() - loadedState.eventSink(RoomMemberListEvents.UpdateSearchQuery("something")) + searchActiveState.eventSink(RoomMemberListEvents.UpdateSearchQuery("something")) val searchQueryUpdatedState = awaitItem() - Truth.assertThat((searchQueryUpdatedState.searchQuery)).isEqualTo("something") + Truth.assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("something") val searchSearchResultDelivered = awaitItem() Truth.assertThat(searchSearchResultDelivered.searchResults).isInstanceOf(SearchBarResultState.NoResults::class.java) } @@ -97,21 +94,20 @@ class RoomMemberListPresenterTests { @Test fun `search for something which is found`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - val initialState = awaitItem() + skipItems(1) val loadedState = awaitItem() loadedState.eventSink(RoomMemberListEvents.OnSearchActiveChanged(true)) val searchActiveState = awaitItem() - loadedState.eventSink(RoomMemberListEvents.UpdateSearchQuery("Alice")) + searchActiveState.eventSink(RoomMemberListEvents.UpdateSearchQuery("Alice")) val searchQueryUpdatedState = awaitItem() - Truth.assertThat((searchQueryUpdatedState.searchQuery)).isEqualTo("Alice") + Truth.assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("Alice") val searchSearchResultDelivered = awaitItem() - Truth.assertThat((searchSearchResultDelivered.searchResults)).isInstanceOf(SearchBarResultState.Results::class.java) + Truth.assertThat(searchSearchResultDelivered.searchResults).isInstanceOf(SearchBarResultState.Results::class.java) Truth.assertThat((searchSearchResultDelivered.searchResults as SearchBarResultState.Results).results.joined.first().displayName) .isEqualTo("Alice") - } } @@ -122,7 +118,7 @@ class RoomMemberListPresenterTests { givenCanInviteResult(Result.success(true)) } ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -138,7 +134,7 @@ class RoomMemberListPresenterTests { givenCanInviteResult(Result.success(false)) } ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -154,7 +150,7 @@ class RoomMemberListPresenterTests { givenCanInviteResult(Result.failure(Throwable("Eek"))) } ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) 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 94b940bb17..71df3ad633 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 @@ -16,7 +16,7 @@ package io.element.android.features.roomdetails.members.details -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth @@ -45,7 +45,7 @@ class RoomMemberDetailsPresenterTests { givenRoomMembersState(MatrixRoomMembersState.Ready(listOf(roomMember))) } val presenter = RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMember.userId) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -69,7 +69,7 @@ class RoomMemberDetailsPresenterTests { givenRoomMembersState(MatrixRoomMembersState.Ready(listOf(roomMember))) } val presenter = RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMember.userId) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -89,7 +89,7 @@ class RoomMemberDetailsPresenterTests { givenRoomMembersState(MatrixRoomMembersState.Ready(listOf(roomMember))) } val presenter = RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMember.userId) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -105,7 +105,7 @@ class RoomMemberDetailsPresenterTests { val room = aMatrixRoom() val roomMember = aRoomMember() val presenter = RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMember.userId) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -126,7 +126,7 @@ class RoomMemberDetailsPresenterTests { val room = aMatrixRoom() val roomMember = aRoomMember() val presenter = RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMember.userId) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -147,7 +147,7 @@ class RoomMemberDetailsPresenterTests { val matrixClient = FakeMatrixClient() matrixClient.givenIgnoreUserResult(Result.failure(A_THROWABLE)) val presenter = RoomMemberDetailsPresenter(matrixClient, room, roomMember.userId) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -166,7 +166,7 @@ class RoomMemberDetailsPresenterTests { val room = aMatrixRoom() val roomMember = aRoomMember() val presenter = RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMember.userId) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() diff --git a/features/roomlist/impl/build.gradle.kts b/features/roomlist/impl/build.gradle.kts index 302e54fe09..f5a08ba860 100644 --- a/features/roomlist/impl/build.gradle.kts +++ b/features/roomlist/impl/build.gradle.kts @@ -71,6 +71,4 @@ dependencies { testImplementation(projects.features.networkmonitor.test) testImplementation(projects.tests.testutils) testImplementation(projects.features.leaveroom.fake) - - androidTestImplementation(libs.test.junitext) } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt index 740c14aa8d..700235f3e2 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListContextMenu.kt @@ -30,7 +30,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.element.android.libraries.designsystem.VectorIcons diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index 21f946b3fd..5a82b20723 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -44,7 +44,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import javax.inject.Inject -private const val extendedRangeSize = 40 +private const val EXTENDED_RANGE_SIZE = 40 class RoomListPresenter @Inject constructor( private val client: MatrixClient, @@ -130,7 +130,7 @@ class RoomListPresenter @Inject constructor( private fun updateVisibleRange(range: IntRange) { if (range.isEmpty()) return - val midExtendedRangeSize = extendedRangeSize / 2 + val midExtendedRangeSize = EXTENDED_RANGE_SIZE / 2 val extendedRangeStart = (range.first - midExtendedRangeSize).coerceAtLeast(0) // Safe to give bigger size than room list val extendedRangeEnd = range.last + midExtendedRangeSize diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt index b32f169e6b..aab174c6a4 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomSummaryRow.kt @@ -155,7 +155,7 @@ private fun RowScope.NameAndTimestampRow(room: RoomListRoomSummary) { @Composable private fun RowScope.LastMessageAndIndicatorRow(room: RoomListRoomSummary) { // Last Message - val attributedLastMessage = (room.lastMessage as? AnnotatedString) + val attributedLastMessage = room.lastMessage as? AnnotatedString ?: AnnotatedString(room.lastMessage.orEmpty().toString()) Text( modifier = Modifier @@ -186,10 +186,10 @@ class PercentRectangleSizeShape(private val percent: Float) : Shape { val halfPercent = percent / 2f val path = Path().apply { val rect = Rect( - 0f, - size.height * halfPercent, - size.width, - size.height - (size.height * halfPercent) + left = 0f, + top = size.height * halfPercent, + right = size.width, + bottom = size.height * (1 - halfPercent) ) addRect(rect) close() 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 0ead16da45..ce8e83c70d 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 @@ -16,7 +16,7 @@ package io.element.android.features.roomlist.impl -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth @@ -61,7 +61,7 @@ class RoomListPresenterTests { @Test fun `present - should start with no user and then load user with success`() = runTest { val presenter = createRoomListPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -81,7 +81,7 @@ class RoomListPresenterTests { userAvatarURLString = Result.failure(AN_EXCEPTION), ) val presenter = createRoomListPresenter(matrixClient) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -94,7 +94,7 @@ class RoomListPresenterTests { @Test fun `present - should filter room with success`() = runTest { val presenter = createRoomListPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -115,7 +115,7 @@ class RoomListPresenterTests { roomSummaryDataSource = roomSummaryDataSource ) val presenter = createRoomListPresenter(matrixClient) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -138,7 +138,7 @@ class RoomListPresenterTests { roomSummaryDataSource = roomSummaryDataSource ) val presenter = createRoomListPresenter(matrixClient) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled())) @@ -167,7 +167,7 @@ class RoomListPresenterTests { roomSummaryDataSource = roomSummaryDataSource ) val presenter = createRoomListPresenter(matrixClient) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { roomSummaryDataSource.postAllRooms(listOf(aRoomSummaryFilled())) @@ -213,7 +213,7 @@ class RoomListPresenterTests { givenVerifiedStatus(SessionVerifiedStatus.NotVerified) }, ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val eventSink = awaitItem().eventSink @@ -229,7 +229,7 @@ class RoomListPresenterTests { val inviteStateFlow = MutableStateFlow(InvitesState.NoInvites) val inviteStateDataSource = FakeInviteDataSource(inviteStateFlow) val presenter = createRoomListPresenter(inviteStateDataSource = inviteStateDataSource) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -249,7 +249,7 @@ class RoomListPresenterTests { @Test fun `present - show context menu`() = runTest { val presenter = createRoomListPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -267,7 +267,7 @@ class RoomListPresenterTests { @Test fun `present - hide context menu`() = runTest { val presenter = createRoomListPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -290,7 +290,7 @@ class RoomListPresenterTests { fun `present - leave room calls into leave room presenter`() = runTest { val leaveRoomPresenter = LeaveRoomPresenterFake() val presenter = createRoomListPresenter(leaveRoomPresenter = leaveRoomPresenter) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSourceTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSourceTest.kt index b67a6c6b43..88e69068fd 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSourceTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/datasource/DefaultInviteStateDataSourceTest.kt @@ -16,7 +16,7 @@ package io.element.android.features.roomlist.impl.datasource -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth @@ -40,7 +40,7 @@ internal class DefaultInviteStateDataSourceTest { val seenStore = FakeSeenInvitesStore() val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers()) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { dataSource.inviteState() }.test { Truth.assertThat(awaitItem()).isEqualTo(InvitesState.NoInvites) @@ -55,7 +55,7 @@ internal class DefaultInviteStateDataSourceTest { val seenStore = FakeSeenInvitesStore() val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers()) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { dataSource.inviteState() }.test { skipItems(1) @@ -72,7 +72,7 @@ internal class DefaultInviteStateDataSourceTest { seenStore.publishRoomIds(setOf(A_ROOM_ID)) val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers(useUnconfinedTestDispatcher = true)) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { dataSource.inviteState() }.test { skipItems(1) @@ -89,7 +89,7 @@ internal class DefaultInviteStateDataSourceTest { seenStore.publishRoomIds(setOf(A_ROOM_ID)) val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers(useUnconfinedTestDispatcher = true)) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { dataSource.inviteState() }.test { skipItems(1) @@ -105,7 +105,7 @@ internal class DefaultInviteStateDataSourceTest { val seenStore = FakeSeenInvitesStore() val dataSource = DefaultInviteStateDataSource(client, seenStore, testCoroutineDispatchers()) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { dataSource.inviteState() }.test { // Initially there are no invites 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 0b58c125de..82664f0e03 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 @@ -16,7 +16,7 @@ package io.element.android.features.verifysession.impl -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.ReceiveTurbine import app.cash.turbine.test @@ -36,7 +36,7 @@ class VerifySelfSessionPresenterTests { @Test fun `present - Initial state is received`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Initial) @@ -47,7 +47,7 @@ class VerifySelfSessionPresenterTests { fun `present - Handles requestVerification`() = runTest { val service = FakeSessionVerificationService() val presenter = createPresenter(service) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { requestVerificationAndAwaitVerifyingState(service) @@ -58,7 +58,7 @@ class VerifySelfSessionPresenterTests { fun `present - Handles startSasVerification`() = runTest { val service = FakeSessionVerificationService() val presenter = createPresenter(service) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -77,7 +77,7 @@ class VerifySelfSessionPresenterTests { @Test fun `present - Cancelation on initial state does nothing`() = runTest { val presenter = createPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -92,7 +92,7 @@ class VerifySelfSessionPresenterTests { fun `present - A fail in the flow cancels it`() = runTest { val service = FakeSessionVerificationService() val presenter = createPresenter(service) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val state = requestVerificationAndAwaitVerifyingState(service) @@ -109,7 +109,7 @@ class VerifySelfSessionPresenterTests { fun `present - Canceling the flow once it's verifying cancels it`() = runTest { val service = FakeSessionVerificationService() val presenter = createPresenter(service) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val state = requestVerificationAndAwaitVerifyingState(service) @@ -123,7 +123,7 @@ class VerifySelfSessionPresenterTests { fun `present - When verifying, if we receive another challenge we ignore it`() = runTest { val service = FakeSessionVerificationService() val presenter = createPresenter(service) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { requestVerificationAndAwaitVerifyingState(service) @@ -136,7 +136,7 @@ class VerifySelfSessionPresenterTests { fun `present - Restart after cancelation returns to requesting verification`() = runTest { val service = FakeSessionVerificationService() val presenter = createPresenter(service) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val state = requestVerificationAndAwaitVerifyingState(service) @@ -158,7 +158,7 @@ class VerifySelfSessionPresenterTests { givenEmojiList(emojis) } val presenter = createPresenter(service) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val state = requestVerificationAndAwaitVerifyingState(service) @@ -172,7 +172,7 @@ class VerifySelfSessionPresenterTests { fun `present - When verification is declined, the flow is canceled`() = runTest { val service = FakeSessionVerificationService() val presenter = createPresenter(service) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val state = requestVerificationAndAwaitVerifyingState(service) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f0759ee705..fe9a5aa182 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ android_gradle_plugin = "8.0.2" kotlin = "1.8.22" ksp = "1.8.22-1.0.11" -molecule = "0.11.0" +molecule = "1.1.0" # AndroidX material = "1.9.0" @@ -163,7 +163,7 @@ maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:2.0.0" # Analytics posthog = "com.posthog.android:posthog:2.0.3" -sentry_android = "io.sentry:sentry-android:6.26.0" +sentry = "io.sentry:sentry-android:6.26.0" matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:42b2faa417c1e95f430bf8f6e379adba25ad5ef8" # Di diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt index 65a5dc9e0d..8347990303 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt @@ -59,7 +59,7 @@ fun Context.isAnimationEnabled(): Boolean { } @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O) -fun supportNotificationChannels() = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) +fun supportNotificationChannels() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O /** * Return the application label of the provided package. If not found, the package is returned. diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/ElementLogoAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/ElementLogoAtom.kt index 94e50d5953..8f96ed6b6e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/ElementLogoAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/ElementLogoAtom.kt @@ -138,7 +138,7 @@ fun Modifier.shapeShadow( val paint = Paint() val frameworkPaint = paint.asFrameworkPaint() if (blurRadius != 0.dp) { - frameworkPaint.maskFilter = (BlurMaskFilter(blurRadius.toPx(), BlurMaskFilter.Blur.NORMAL)) + frameworkPaint.maskFilter = BlurMaskFilter(blurRadius.toPx(), BlurMaskFilter.Blur.NORMAL) } frameworkPaint.color = color.toArgb() diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/SheetState.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/SheetState.kt new file mode 100644 index 0000000000..a83bc5708c --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/SheetState.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.preview + +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.SheetState +import androidx.compose.material3.SheetValue + +@OptIn(ExperimentalMaterial3Api::class) +val sheetStateForPreview = SheetState( + skipPartiallyExpanded = true, + initialValue = SheetValue.Expanded, +) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheet.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheet.kt index a31e4188c6..57b0612ad7 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheet.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheet.kt @@ -25,7 +25,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.BottomSheetDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SheetState -import androidx.compose.material3.SheetValue import androidx.compose.material3.contentColorFor import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable @@ -38,6 +37,7 @@ import androidx.compose.ui.unit.dp import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.designsystem.preview.sheetStateForPreview import io.element.android.libraries.theme.ElementTheme import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -100,10 +100,7 @@ private fun ContentToPreview() { ) { ModalBottomSheet( onDismissRequest = {}, - sheetState = SheetState( - skipPartiallyExpanded = true, - initialValue = SheetValue.Expanded, - ), + sheetState = sheetStateForPreview, ) { Text( text = "Sheet Content", diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/LogCompositions.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/LogCompositions.kt index f6edd1a8fb..dcbef866fe 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/LogCompositions.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/LogCompositions.kt @@ -30,7 +30,7 @@ fun LogCompositions(tag: String, msg: String) { if (BuildConfig.DEBUG) { val ref = remember { Ref(0) } SideEffect { ref.value++ } - Timber.d(tag, "Compositions: $msg ${ref.value}") + Timber.tag(tag).d("Compositions: $msg ${ref.value}") } } 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 920be09389..4c1bfed33a 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 @@ -22,16 +22,8 @@ enum class FeatureFlags( override val description: String? = null, override val defaultValue: Boolean = true ) : Feature { - CollapseRoomStateEvents( - key = "feature.collapseroomstateevents", - title = "Collapse room state events", - ), - ShowStartChatFlow( - key = "feature.showstartchatflow", - title = "Show start chat flow", - ), - ShowMediaUploadingFlow( - key = "feature.showmediauploadingflow", - title = "Show media uploading flow", + LocationSharing( + key = "feature.locationsharing", + title = "Allow user to share location", ) } 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/BuildtimeFeatureFlagProvider.kt index ae498e67df..d226885495 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/BuildtimeFeatureFlagProvider.kt @@ -29,9 +29,7 @@ class BuildtimeFeatureFlagProvider @Inject constructor() : override suspend fun isFeatureEnabled(feature: Feature): Boolean { return if (feature is FeatureFlags) { when (feature) { - FeatureFlags.CollapseRoomStateEvents -> false - FeatureFlags.ShowStartChatFlow -> false - FeatureFlags.ShowMediaUploadingFlow -> false + FeatureFlags.LocationSharing -> true } } else { false 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 5f7f01423a..ea9b03acdf 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 @@ -26,14 +26,14 @@ class DefaultFeatureFlagServiceTest { @Test fun `given service without provider when feature is checked then it returns the default value`() = runTest { val featureFlagService = DefaultFeatureFlagService(emptySet()) - val isFeatureEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.ShowStartChatFlow) - assertThat(isFeatureEnabled).isEqualTo(FeatureFlags.ShowStartChatFlow.defaultValue) + val isFeatureEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.LocationSharing) + assertThat(isFeatureEnabled).isEqualTo(FeatureFlags.LocationSharing.defaultValue) } @Test fun `given service without provider when set enabled feature is called then it returns false`() = runTest { val featureFlagService = DefaultFeatureFlagService(emptySet()) - val result = featureFlagService.setFeatureEnabled(FeatureFlags.ShowStartChatFlow, true) + val result = featureFlagService.setFeatureEnabled(FeatureFlags.LocationSharing, true) assertThat(result).isEqualTo(false) } @@ -41,7 +41,7 @@ class DefaultFeatureFlagServiceTest { fun `given service with a runtime provider when set enabled feature is called then it returns true`() = runTest { val featureFlagProvider = FakeRuntimeFeatureFlagProvider(0) val featureFlagService = DefaultFeatureFlagService(setOf(featureFlagProvider)) - val result = featureFlagService.setFeatureEnabled(FeatureFlags.ShowStartChatFlow, true) + val result = featureFlagService.setFeatureEnabled(FeatureFlags.LocationSharing, true) assertThat(result).isEqualTo(true) } @@ -49,10 +49,10 @@ class DefaultFeatureFlagServiceTest { 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 featureFlagService = DefaultFeatureFlagService(setOf(featureFlagProvider)) - featureFlagService.setFeatureEnabled(FeatureFlags.ShowStartChatFlow, true) - assertThat(featureFlagService.isFeatureEnabled(FeatureFlags.ShowStartChatFlow)).isEqualTo(true) - featureFlagService.setFeatureEnabled(FeatureFlags.ShowStartChatFlow, false) - assertThat(featureFlagService.isFeatureEnabled(FeatureFlags.ShowStartChatFlow)).isEqualTo(false) + featureFlagService.setFeatureEnabled(FeatureFlags.LocationSharing, true) + assertThat(featureFlagService.isFeatureEnabled(FeatureFlags.LocationSharing)).isEqualTo(true) + featureFlagService.setFeatureEnabled(FeatureFlags.LocationSharing, false) + assertThat(featureFlagService.isFeatureEnabled(FeatureFlags.LocationSharing)).isEqualTo(false) } @Test @@ -60,8 +60,8 @@ class DefaultFeatureFlagServiceTest { val lowPriorityfeatureFlagProvider = FakeRuntimeFeatureFlagProvider(LOW_PRIORITY) val highPriorityfeatureFlagProvider = FakeRuntimeFeatureFlagProvider(HIGH_PRIORITY) val featureFlagService = DefaultFeatureFlagService(setOf(lowPriorityfeatureFlagProvider, highPriorityfeatureFlagProvider)) - lowPriorityfeatureFlagProvider.setFeatureEnabled(FeatureFlags.ShowStartChatFlow, false) - highPriorityfeatureFlagProvider.setFeatureEnabled(FeatureFlags.ShowStartChatFlow, true) - assertThat(featureFlagService.isFeatureEnabled(FeatureFlags.ShowStartChatFlow)).isEqualTo(true) + lowPriorityfeatureFlagProvider.setFeatureEnabled(FeatureFlags.LocationSharing, false) + highPriorityfeatureFlagProvider.setFeatureEnabled(FeatureFlags.LocationSharing, true) + assertThat(featureFlagService.isFeatureEnabled(FeatureFlags.LocationSharing)).isEqualTo(true) } } diff --git a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapLocationSettings.kt b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapLocationSettings.kt index 4b7b7005f2..69e0e9b1d4 100644 --- a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapLocationSettings.kt +++ b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapLocationSettings.kt @@ -18,6 +18,8 @@ package io.element.android.libraries.maplibre.compose +import androidx.compose.ui.graphics.Color + internal val DefaultMapLocationSettings = MapLocationSettings() /** @@ -28,4 +30,11 @@ internal val DefaultMapLocationSettings = MapLocationSettings() */ public data class MapLocationSettings( public val locationEnabled: Boolean = false, + public val backgroundTintColor: Color = Color.Unspecified, + public val foregroundTintColor: Color = Color.Unspecified, + public val backgroundStaleTintColor: Color = Color.Unspecified, + public val foregroundStaleTintColor: Color = Color.Unspecified, + public val accuracyColor: Color = Color.Unspecified, + public val pulseEnabled: Boolean = false, + public val pulseColor: Color = Color.Unspecified ) diff --git a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapUpdater.kt b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapUpdater.kt index d7d5f9ca11..e4e3565f22 100644 --- a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapUpdater.kt +++ b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapUpdater.kt @@ -39,6 +39,7 @@ internal class MapPropertiesNode( style: Style, context: Context, cameraPositionState: CameraPositionState, + locationSettings: MapLocationSettings, ) : MapNode { init { @@ -46,7 +47,13 @@ internal class MapPropertiesNode( LocationComponentActivationOptions.Builder(context, style) .locationComponentOptions( LocationComponentOptions.builder(context) - .pulseEnabled(true) + .backgroundTintColor(locationSettings.backgroundTintColor.toArgb()) + .foregroundTintColor(locationSettings.foregroundTintColor.toArgb()) + .backgroundStaleTintColor(locationSettings.backgroundStaleTintColor.toArgb()) + .foregroundStaleTintColor(locationSettings.foregroundStaleTintColor.toArgb()) + .accuracyColor(locationSettings.accuracyColor.toArgb()) + .pulseEnabled(locationSettings.pulseEnabled) + .pulseColor(locationSettings.pulseColor.toArgb()) .build() ) .locationEngineRequest( @@ -116,9 +123,9 @@ internal class MapPropertiesNode( @Composable internal inline fun MapUpdater( cameraPositionState: CameraPositionState, - mapLocationSettings: MapLocationSettings, - mapUiSettings: MapUiSettings, - mapSymbolManagerSettings: MapSymbolManagerSettings, + locationSettings: MapLocationSettings, + uiSettings: MapUiSettings, + symbolManagerSettings: MapSymbolManagerSettings, ) { val mapApplier = currentComposer.applier as MapApplier val map = mapApplier.map @@ -132,21 +139,22 @@ internal inline fun MapUpdater( style = style, context = context, cameraPositionState = cameraPositionState, + locationSettings = locationSettings, ) }, update = { - set(mapLocationSettings.locationEnabled) { map.locationComponent.isLocationComponentEnabled = it } + set(locationSettings.locationEnabled) { map.locationComponent.isLocationComponentEnabled = it } - set(mapUiSettings.compassEnabled) { map.uiSettings.isCompassEnabled = it } - set(mapUiSettings.rotationGesturesEnabled) { map.uiSettings.isRotateGesturesEnabled = it } - set(mapUiSettings.scrollGesturesEnabled) { map.uiSettings.isScrollGesturesEnabled = it } - set(mapUiSettings.tiltGesturesEnabled) { map.uiSettings.isTiltGesturesEnabled = it } - set(mapUiSettings.zoomGesturesEnabled) { map.uiSettings.isZoomGesturesEnabled = it } - set(mapUiSettings.logoGravity) { map.uiSettings.logoGravity = it } - set(mapUiSettings.attributionGravity) { map.uiSettings.attributionGravity = it } - set(mapUiSettings.attributionTintColor) { map.uiSettings.setAttributionTintColor(it.toArgb()) } + set(uiSettings.compassEnabled) { map.uiSettings.isCompassEnabled = it } + set(uiSettings.rotationGesturesEnabled) { map.uiSettings.isRotateGesturesEnabled = it } + set(uiSettings.scrollGesturesEnabled) { map.uiSettings.isScrollGesturesEnabled = it } + set(uiSettings.tiltGesturesEnabled) { map.uiSettings.isTiltGesturesEnabled = it } + set(uiSettings.zoomGesturesEnabled) { map.uiSettings.isZoomGesturesEnabled = it } + set(uiSettings.logoGravity) { map.uiSettings.logoGravity = it } + set(uiSettings.attributionGravity) { map.uiSettings.attributionGravity = it } + set(uiSettings.attributionTintColor) { map.uiSettings.setAttributionTintColor(it.toArgb()) } - set(mapSymbolManagerSettings.iconAllowOverlap) { symbolManager.iconAllowOverlap = it } + set(symbolManagerSettings.iconAllowOverlap) { symbolManager.iconAllowOverlap = it } update(cameraPositionState) { this.cameraPositionState = it } } diff --git a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapboxMap.kt b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapboxMap.kt index 3c3cf3e44f..c67be52c1c 100644 --- a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapboxMap.kt +++ b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapboxMap.kt @@ -124,9 +124,9 @@ public fun MapboxMap( ) { MapUpdater( cameraPositionState = currentCameraPositionState, - mapUiSettings = currentUiSettings, - mapLocationSettings = currentMapLocationSettings, - mapSymbolManagerSettings = currentSymbolManagerSettings, + uiSettings = currentUiSettings, + locationSettings = currentMapLocationSettings, + symbolManagerSettings = currentSymbolManagerSettings, ) CompositionLocalProvider( LocalCameraPositionState provides cameraPositionState, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthenticationException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthenticationException.kt index e670e02f11..48712b7ddf 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthenticationException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthenticationException.kt @@ -22,5 +22,6 @@ sealed class AuthenticationException(message: String) : Exception(message) { class SlidingSyncNotAvailable(message: String) : AuthenticationException(message) class SessionMissing(message: String) : AuthenticationException(message) class Generic(message: String) : AuthenticationException(message) - class OidcError(type: String, message: String) : AuthenticationException(message) + // TODO Oidc + // class OidcError(type: String, message: String) : AuthenticationException(message) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt index 6a15bfb514..31e28a40db 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkBuilder.kt @@ -25,7 +25,6 @@ object PermalinkBuilder { private const val ROOM_PATH = "room/" private const val USER_PATH = "user/" - private const val GROUP_PATH = "group/" private val permalinkBaseUrl get() = (MatrixConfiguration.clientPermalinkBaseUrl ?: MatrixConfiguration.matrixToPermalinkBaseUrl).also { var baseUrl = it 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 be0ff447b3..1445f85228 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 @@ -63,7 +63,11 @@ interface MatrixRoom : Closeable { val timeline: MatrixTimeline - fun open(): Result + fun destroy() + + fun subscribeToSync() + + fun unsubscribeFromSync() suspend fun userDisplayName(userId: UserId): Result @@ -133,6 +137,8 @@ interface MatrixRoom : Closeable { zoomLevel: Int? = null, assetType: AssetType? = null, ): Result + + override fun close() = destroy() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfig.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfig.kt index 1ba5063df9..b5115ffad4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfig.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfig.kt @@ -16,8 +16,8 @@ package io.element.android.libraries.matrix.impl.auth -import io.element.android.libraries.matrix.api.auth.OidcConfig // TODO Oidc +// import io.element.android.libraries.matrix.api.auth.OidcConfig // import org.matrix.rustcomponents.sdk.OidcClientMetadata /* diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ImageInfo.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ImageInfo.kt index b66cec96fd..99b806ed4a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ImageInfo.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ImageInfo.kt @@ -17,7 +17,6 @@ package io.element.android.libraries.matrix.impl.media import io.element.android.libraries.matrix.api.media.ImageInfo -import org.matrix.rustcomponents.sdk.MediaSource import org.matrix.rustcomponents.sdk.ImageInfo as RustImageInfo fun RustImageInfo.map(): ImageInfo = ImageInfo( 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 b185c34205..89e83b58c6 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 @@ -40,9 +40,6 @@ import io.element.android.libraries.matrix.impl.core.toProgressWatcher import io.element.android.libraries.matrix.impl.media.map import io.element.android.libraries.matrix.impl.room.location.toInner import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline -import io.element.android.libraries.matrix.impl.timeline.backPaginationStatusFlow -import io.element.android.libraries.matrix.impl.timeline.eventOrigin -import io.element.android.libraries.matrix.impl.timeline.timelineDiffFlow import io.element.android.libraries.sessionstorage.api.SessionData import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.coroutines.CoroutineScope @@ -51,11 +48,7 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import org.matrix.rustcomponents.sdk.EventItemOrigin import org.matrix.rustcomponents.sdk.RequiredState import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.RoomListItem @@ -88,7 +81,6 @@ class RustMatrixRoom( private val roomCoroutineScope = sessionCoroutineScope.childScope(coroutineDispatchers.main, "RoomScope-$roomId") private val _membersStateFlow = MutableStateFlow(MatrixRoomMembersState.Unknown) - private val isInit = MutableStateFlow(false) private val _syncUpdateFlow = MutableStateFlow(0L) private val _timeline by lazy { RustMatrixTimeline( @@ -97,6 +89,7 @@ class RustMatrixRoom( roomCoroutineScope = roomCoroutineScope, dispatcher = roomDispatcher, lastLoginTimestamp = sessionData.loginTimestamp, + onNewSyncedEvent = { _syncUpdateFlow.value = systemClock.epochMillis() } ) } @@ -106,8 +99,7 @@ class RustMatrixRoom( override val timeline: MatrixTimeline = _timeline - override fun open(): Result { - if (isInit.value) return Result.failure(IllegalStateException("Listener already registered")) + override fun subscribeToSync() { val settings = RoomSubscription( requiredState = listOf( RequiredState(key = EventType.STATE_ROOM_CANONICAL_ALIAS, value = ""), @@ -118,37 +110,16 @@ class RustMatrixRoom( timelineLimit = null ) roomListItem.subscribe(settings) - roomCoroutineScope.launch(roomDispatcher) { - innerRoom.timelineDiffFlow { initialList -> - _timeline.postItems(initialList) - }.onEach { diff -> - diff.use { - if (diff.eventOrigin() == EventItemOrigin.SYNC) { - _syncUpdateFlow.value = systemClock.epochMillis() - } - _timeline.postDiff(diff) - } - }.launchIn(this) - - innerRoom.backPaginationStatusFlow() - .onEach { - _timeline.postPaginationStatus(it) - }.launchIn(this) - - fetchMembers() - } - isInit.value = true - return Result.success(Unit) } - override fun close() { - if (isInit.value) { - isInit.value = false - roomCoroutineScope.cancel() - roomListItem.unsubscribe() - innerRoom.destroy() - roomListItem.destroy() - } + override fun unsubscribeFromSync() { + roomListItem.unsubscribe() + } + + override fun destroy() { + roomCoroutineScope.cancel() + innerRoom.destroy() + roomListItem.destroy() } override val name: String? @@ -365,12 +336,6 @@ class RustMatrixRoom( } } - private suspend fun fetchMembers() = withContext(roomDispatcher) { - runCatching { - innerRoom.fetchMembers() - } - } - override suspend fun reportContent(eventId: EventId, reason: String, blockUserId: UserId?): Result = withContext(roomDispatcher) { runCatching { innerRoom.reportContent(eventId = eventId.value, score = null, reason = reason) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt index 5e7f4a2282..d556d8aab9 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt @@ -39,10 +39,14 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.getAndUpdate +import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.sample +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.BackPaginationStatus +import org.matrix.rustcomponents.sdk.EventItemOrigin import org.matrix.rustcomponents.sdk.PaginationOptions import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.TimelineDiff @@ -59,6 +63,7 @@ class RustMatrixTimeline( private val innerRoom: Room, private val dispatcher: CoroutineDispatcher, private val lastLoginTimestamp: Date?, + private val onNewSyncedEvent: () -> Unit, ) : MatrixTimeline { private val initLatch = CompletableDeferred() @@ -95,13 +100,40 @@ class RustMatrixTimeline( override val paginationState: StateFlow = _paginationState.asStateFlow() + init { + Timber.d("Initialize timeline for room ${matrixRoom.roomId}") + roomCoroutineScope.launch(dispatcher) { + innerRoom.timelineDiffFlow { initialList -> + postItems(initialList) + }.onEach { diff -> + if (diff.eventOrigin() == EventItemOrigin.SYNC) { + onNewSyncedEvent() + } + postDiff(diff) + }.launchIn(this) + + innerRoom.backPaginationStatusFlow() + .onEach { + postPaginationStatus(it) + }.launchIn(this) + + fetchMembers() + } + } + + private suspend fun fetchMembers() = withContext(dispatcher) { + runCatching { + innerRoom.fetchMembers() + } + } + @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) override val timelineItems: Flow> = _timelineItems.sample(50) .mapLatest { items -> encryptedHistoryPostProcessor.process(items) } - internal suspend fun postItems(items: List) = coroutineScope { + private suspend fun postItems(items: List) = coroutineScope { // Split the initial items in multiple list as there is no pagination in the cached data, so we can post timelineItems asap. items.chunked(INITIAL_MAX_SIZE).reversed().forEach { ensureActive() @@ -111,12 +143,12 @@ class RustMatrixTimeline( initLatch.complete(Unit) } - internal suspend fun postDiff(timelineDiff: TimelineDiff) { + private suspend fun postDiff(timelineDiff: TimelineDiff) { initLatch.await() timelineDiffProcessor.postDiff(timelineDiff) } - internal fun postPaginationStatus(status: BackPaginationStatus) { + private fun postPaginationStatus(status: BackPaginationStatus) { _paginationState.getAndUpdate { currentPaginationState -> if (hasEncryptionHistoryBanner()) { return@getAndUpdate currentPaginationState.copy( diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt index 9f4df1f6b3..96b61fd558 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt @@ -45,7 +45,7 @@ class EventMessageMapper { fun map(message: Message): MessageContent = message.use { val type = it.msgtype().use(this::mapMessageType) val inReplyToId = it.inReplyTo()?.eventId?.let(::EventId) - val inReplyToEvent: InReplyTo? = (it.inReplyTo()?.event)?.use { details -> + val inReplyToEvent: InReplyTo? = it.inReplyTo()?.event?.use { details -> when (details) { is RepliedToEventDetails.Ready -> { val senderProfile = details.senderProfile as? ProfileDetails.Ready diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessor.kt index ca5c7342f8..0fe12e6391 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessor.kt @@ -22,7 +22,6 @@ import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTime import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.getAndUpdate import java.util.Date -import java.util.UUID class TimelineEncryptedHistoryPostProcessor( private val lastLoginTimestamp: Date?, @@ -70,5 +69,4 @@ class TimelineEncryptedHistoryPostProcessor( val timestamp = (item as? MatrixTimelineItem.Event)?.event?.timestamp ?: return false return timestamp <= lastLoginTimestamp!!.time } - } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt index 3da47a8563..677774afe4 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt @@ -24,7 +24,6 @@ import io.element.android.libraries.matrix.api.core.SpaceId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.TransactionId import io.element.android.libraries.matrix.api.core.UserId -import java.util.UUID const val A_USER_NAME = "alice" const val A_PASSWORD = "password" 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 7fe7de5b9f..59f6ed57bd 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 @@ -100,7 +100,6 @@ class FakeMatrixRoom( private val _sentLocations = mutableListOf() val sentLocations: List = _sentLocations - var invitedUserId: UserId? = null private set @@ -128,9 +127,11 @@ class FakeMatrixRoom( override val timeline: MatrixTimeline = matrixTimeline - override fun open(): Result { - return Result.success(Unit) - } + override fun subscribeToSync() = Unit + + override fun unsubscribeFromSync() = Unit + + override fun destroy() = Unit override suspend fun userDisplayName(userId: UserId): Result = simulateLongTask { userDisplayNameResult @@ -283,8 +284,6 @@ class FakeMatrixRoom( return sendLocationResult } - override fun close() = Unit - fun givenLeaveRoomError(throwable: Throwable?) { this.leaveRoomError = throwable } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersList.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersList.kt index c0ed0460dc..f9649e1b34 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersList.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersList.kt @@ -88,7 +88,7 @@ fun SelectedUsersList( val maxVisibleUsers = rowWidth / userWidthWithSpacing // Round down the number of visible users to end with a state where one is half visible - val targetFraction = (userWidth / 2) / userWidthWithSpacing + val targetFraction = userWidth / 2 / userWidthWithSpacing val targetUsers = floor(maxVisibleUsers - targetFraction) + targetFraction // Work out how much extra spacing we need to reduce the number of users that much, then split it evenly amongst the visible users @@ -153,7 +153,7 @@ private fun ContentToPreview() { SelectedUsersList( selectedUsers = aMatrixUserList().take(6).toImmutableList(), modifier = Modifier - .width((200 + (i * 20)).dp) + .width((200 + i * 20).dp) .border(1.dp, Color.Red) ) } diff --git a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt index cfa59d65d3..1622ab2eef 100644 --- a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt +++ b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt @@ -82,7 +82,6 @@ class MediaSender @Inject constructor( progressCallback = progressCallback ) } - else -> Result.failure(IllegalStateException("Unexpected MediaUploadInfo format: $uploadInfo")) } } } 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 2b8669fe42..938072433a 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,7 +19,6 @@ package io.element.android.libraries.mediaupload import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory -import com.vanniktech.blurhash.BlurHash import io.element.android.libraries.androidutils.bitmap.calculateInSampleSize import io.element.android.libraries.androidutils.bitmap.resizeToMax import io.element.android.libraries.androidutils.bitmap.rotateToMetadataOrientation @@ -92,7 +91,7 @@ class ImageCompressor @Inject constructor( ) { val (width, height) = when (resizeMode) { is ResizeMode.Approximate -> resizeMode.desiredWidth to resizeMode.desiredHeight - is ResizeMode.Strict -> (resizeMode.maxWidth / 2) to (resizeMode.maxHeight / 2) + is ResizeMode.Strict -> resizeMode.maxWidth / 2 to resizeMode.maxHeight / 2 is ResizeMode.None -> return } // Read bounds only diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ThumbnailFactory.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ThumbnailFactory.kt index a9ed6319cb..2cee2566d1 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ThumbnailFactory.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ThumbnailFactory.kt @@ -69,6 +69,7 @@ class ThumbnailFactory @Inject constructor( cancellationSignal ) } else { + @Suppress("DEPRECATION") ThumbnailUtils.createImageThumbnail( file.path, MediaStore.Images.Thumbnails.MINI_KIND, diff --git a/libraries/permissions/impl/build.gradle.kts b/libraries/permissions/impl/build.gradle.kts index 31a4e4bbb1..9808986f8f 100644 --- a/libraries/permissions/impl/build.gradle.kts +++ b/libraries/permissions/impl/build.gradle.kts @@ -57,7 +57,5 @@ dependencies { testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) - androidTestImplementation(libs.test.junitext) - ksp(libs.showkase.processor) } 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 24b174426c..f501bcee8a 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 @@ -18,7 +18,7 @@ package io.element.android.libraries.permissions.impl -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.accompanist.permissions.ExperimentalPermissionsApi @@ -41,7 +41,7 @@ class DefaultPermissionsPresenterTest { permissionsStore, permissionStateProvider ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -64,7 +64,7 @@ class DefaultPermissionsPresenterTest { permissionsStore, permissionStateProvider ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -84,7 +84,7 @@ class DefaultPermissionsPresenterTest { permissionsStore, permissionStateProvider ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -113,7 +113,7 @@ class DefaultPermissionsPresenterTest { permissionsStore, permissionStateProvider ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() @@ -142,7 +142,7 @@ class DefaultPermissionsPresenterTest { permissionsStore, permissionStateProvider ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { skipItems(1) @@ -164,7 +164,7 @@ class DefaultPermissionsPresenterTest { permissionsStore, permissionStateProvider ) - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() 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 9992480436..912edd9294 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 @@ -16,7 +16,7 @@ package io.element.android.libraries.permissions.noop -import app.cash.molecule.RecompositionClock +import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -27,7 +27,7 @@ class NoopPermissionsPresenterTest { @Test fun `present - initial state`() = runTest { val presenter = NoopPermissionsPresenter() - moleculeFlow(RecompositionClock.Immediate) { + moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() diff --git a/libraries/push/impl/build.gradle.kts b/libraries/push/impl/build.gradle.kts index b639f61b8b..b0f3be6deb 100644 --- a/libraries/push/impl/build.gradle.kts +++ b/libraries/push/impl/build.gradle.kts @@ -52,9 +52,6 @@ dependencies { implementation(projects.services.appnavstate.api) implementation(projects.services.toolbox.api) - // TODO Temporary use the deprecated LocalBroadcastManager, to be changed later. - implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.1.0") - testImplementation(libs.test.junit) testImplementation(libs.test.mockk) testImplementation(libs.test.truth) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt index 81a86c5345..f3afb940cd 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt @@ -103,33 +103,6 @@ class PushersManager @Inject constructor( return "{\"cs\":\"$secretForUser\"}" } - suspend fun registerEmailForPush(email: String) { - TODO() - /* - val currentSession = activeSessionHolder.getActiveSession() - val appName = appNameProvider.getAppName() - currentSession.pushersService().addEmailPusher( - email = email, - lang = localeProvider.current().language, - emailBranding = appName, - appDisplayName = appName, - deviceDisplayName = currentSession.sessionParams.deviceId ?: "MOBILE" - ) - */ - } - - fun getPusherForCurrentSession() {}/*: Pusher? { - val session = activeSessionHolder.getSafeActiveSession() ?: return null - val deviceId = session.sessionParams.deviceId - return session.pushersService().getPushers().firstOrNull { it.deviceId == deviceId } - } - */ - - suspend fun unregisterEmailPusher(email: String) { - // val currentSession = activeSessionHolder.getSafeActiveSession() ?: return - // currentSession.pushersService().removeEmailPusher(email) - } - override suspend fun unregisterPusher(matrixClient: MatrixClient, pushKey: String, gateway: String) { matrixClient.pushersService().unsetHttpPusher() } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt index 9cd4956dca..91b3987251 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt @@ -23,17 +23,17 @@ import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.SingleIn +import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.user.MatrixUser -import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.push.api.notifications.NotificationDrawerManager -import io.element.android.libraries.push.api.store.PushDataStore import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent -import io.element.android.services.appnavstate.api.NavigationState import io.element.android.services.appnavstate.api.AppNavigationStateService +import io.element.android.services.appnavstate.api.NavigationState +import io.element.android.services.appnavstate.api.currentSessionId import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -48,7 +48,6 @@ import javax.inject.Inject */ @SingleIn(AppScope::class) class DefaultNotificationDrawerManager @Inject constructor( - private val pushDataStore: PushDataStore, private val notifiableEventProcessor: NotifiableEventProcessor, private val notificationRenderer: NotificationRenderer, private val notificationEventPersistence: NotificationEventPersistence, @@ -76,9 +75,16 @@ class DefaultNotificationDrawerManager @Inject constructor( } } + private var currentAppNavigationState: NavigationState? = null + private fun onAppNavigationStateChange(navigationState: NavigationState) { when (navigationState) { - NavigationState.Root -> {} + NavigationState.Root -> { + currentAppNavigationState?.currentSessionId()?.let { sessionId -> + // User signed out, clear all notifications related to the session. + clearAllEvents(sessionId) + } + } is NavigationState.Session -> {} is NavigationState.Space -> {} is NavigationState.Room -> { @@ -93,6 +99,7 @@ class DefaultNotificationDrawerManager @Inject constructor( ) } } + currentAppNavigationState = navigationState } private fun createInitialNotificationState(): NotificationState { @@ -133,12 +140,21 @@ class DefaultNotificationDrawerManager @Inject constructor( /** * Clear all known events and refresh the notification drawer. */ - fun clearAllEvents(sessionId: SessionId) { + fun clearAllMessagesEvents(sessionId: SessionId) { updateEvents { it.clearMessagesForSession(sessionId) } } + /** + * Clear all notifications related to the session and refresh the notification drawer. + */ + fun clearAllEvents(sessionId: SessionId) { + updateEvents { + it.clearAllForSession(sessionId) + } + } + /** * Should be called when the application is currently opened and showing timeline for the given roomId. * Used to ignore events related to that room (no need to display notification) and clean any existing notification on this room. diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FilteredEventDetector.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FilteredEventDetector.kt index a24f088998..2d4d27472b 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FilteredEventDetector.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FilteredEventDetector.kt @@ -27,7 +27,7 @@ class FilteredEventDetector @Inject constructor( * Returns true if the given event should be ignored. * Used to skip notifications if a non expected message is received. */ - fun shouldBeIgnored(notifiableEvent: NotifiableEvent): Boolean { + fun shouldBeIgnored(@Suppress("UNUSED_PARAMETER") notifiableEvent: NotifiableEvent): Boolean { /* TODO EAx val session = activeSessionDataSource.currentValue?.orNull() ?: return false diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt index dbdddf1ac9..29ac866347 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt @@ -17,7 +17,7 @@ package io.element.android.libraries.push.impl.notifications import io.element.android.libraries.core.log.logger.LoggerTag -import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId @@ -34,7 +34,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessage import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType -import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.log.pushLoggerTag import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent @@ -57,9 +56,6 @@ private val loggerTag = LoggerTag("NotifiableEventResolver", pushLoggerTag) */ class NotifiableEventResolver @Inject constructor( private val stringProvider: StringProvider, - // private val noticeEventFormatter: NoticeEventFormatter, - // private val displayableEventFormatter: DisplayableEventFormatter, - private val buildMeta: BuildMeta, private val clock: SystemClock, private val matrixClientProvider: MatrixClientProvider, ) { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiver.kt index d5df1001ca..59a763bd55 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiver.kt @@ -19,15 +19,12 @@ package io.element.android.libraries.push.impl.notifications import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import androidx.core.app.RemoteInput import io.element.android.libraries.architecture.bindings import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId -import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.push.impl.log.notificationLoggerTag -import io.element.android.services.toolbox.api.systemclock.SystemClock import timber.log.Timber import javax.inject.Inject @@ -39,10 +36,6 @@ private val loggerTag = LoggerTag("NotificationBroadcastReceiver", notificationL class NotificationBroadcastReceiver : BroadcastReceiver() { @Inject lateinit var defaultNotificationDrawerManager: DefaultNotificationDrawerManager - - //@Inject lateinit var activeSessionHolder: ActiveSessionHolder - //@Inject lateinit var analyticsTracker: AnalyticsTracker - @Inject lateinit var clock: SystemClock @Inject lateinit var actionIds: NotificationActionIds override fun onReceive(context: Context?, intent: Intent?) { @@ -59,7 +52,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { defaultNotificationDrawerManager.clearMessagesForRoom(sessionId, roomId) } actionIds.dismissSummary -> - defaultNotificationDrawerManager.clearAllEvents(sessionId) + defaultNotificationDrawerManager.clearAllMessagesEvents(sessionId) actionIds.dismissInvite -> if (roomId != null) { defaultNotificationDrawerManager.clearMembershipNotificationForRoom(sessionId, roomId) } @@ -81,6 +74,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { } } + @Suppress("UNUSED_PARAMETER") private fun handleJoinRoom(sessionId: SessionId, roomId: RoomId) { /* activeSessionHolder.getSafeActiveSession()?.let { session -> @@ -94,10 +88,10 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { } } } - */ } + @Suppress("UNUSED_PARAMETER") private fun handleRejectRoom(sessionId: SessionId, roomId: RoomId) { /* activeSessionHolder.getSafeActiveSession()?.let { session -> @@ -109,6 +103,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { */ } + @Suppress("UNUSED_PARAMETER") private fun handleMarkAsRead(sessionId: SessionId, roomId: RoomId) { /* activeSessionHolder.getActiveSession().let { session -> @@ -123,7 +118,9 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { */ } + @Suppress("UNUSED_PARAMETER") private fun handleSmartReply(intent: Intent, context: Context) { + /* val message = getReplyMessage(intent) val sessionId = intent.getStringExtra(KEY_SESSION_ID)?.let(::SessionId) val roomId = intent.getStringExtra(KEY_ROOM_ID)?.let(::RoomId) @@ -134,13 +131,11 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { // Can this happen? should we update notification? return } - /* activeSessionHolder.getActiveSession().let { session -> session.getRoom(roomId)?.let { room -> sendMatrixEvent(message, threadId, session, room, context) } } - */ } @@ -234,6 +229,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { */ + /* private fun getReplyMessage(intent: Intent?): String? { if (intent != null) { val remoteInput = RemoteInput.getResultsFromIntent(intent) @@ -243,6 +239,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { } return null } + */ companion object { const val KEY_SESSION_ID = "sessionID" diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueue.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueue.kt index 97b90476b0..6b6730c904 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueue.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationEventQueue.kt @@ -154,6 +154,11 @@ data class NotificationEventQueue constructor( queue.removeAll { it is NotifiableMessageEvent && it.sessionId == sessionId } } + fun clearAllForSession(sessionId: SessionId) { + Timber.d("clearAllForSession $sessionId") + queue.removeAll { it.sessionId == sessionId } + } + fun clearMessagesForRoom(sessionId: SessionId, roomId: RoomId) { Timber.d("clearMessageEventOfRoom $sessionId, $roomId") queue.removeAll { it is NotifiableMessageEvent && it.sessionId == sessionId && it.roomId == roomId } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/OutdatedEventDetector.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/OutdatedEventDetector.kt index 5b15dc78d2..27713399fc 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/OutdatedEventDetector.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/OutdatedEventDetector.kt @@ -28,7 +28,7 @@ class OutdatedEventDetector @Inject constructor( * Used to clean up notifications if a displayed message has been read on an * other device. */ - fun isMessageOutdated(notifiableEvent: NotifiableEvent): Boolean { + fun isMessageOutdated(@Suppress("UNUSED_PARAMETER") notifiableEvent: NotifiableEvent): Boolean { /* TODO EAx val session = activeSessionDataSource.currentValue?.orNull() ?: return false diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt index 5f2f6db263..989ba2ad09 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomGroupMessageCreator.kt @@ -69,7 +69,7 @@ class RoomGroupMessageCreator @Inject constructor( val lastMessageTimestamp = events.last().timestamp val smartReplyErrors = events.filter { it.isSmartReplyError() } - val messageCount = (events.size - smartReplyErrors.size) + val messageCount = events.size - smartReplyErrors.size val meta = RoomNotification.Message.Meta( summaryLine = createRoomMessagesGroupSummaryLine(events, roomName, roomIsDirect = !roomIsGroup), messageCount = messageCount, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiver.kt index 42c0fe61af..a7135cee74 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiver.kt @@ -19,12 +19,10 @@ package io.element.android.libraries.push.impl.notifications import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import androidx.localbroadcastmanager.content.LocalBroadcastManager class TestNotificationReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - // Internal broadcast to any one interested - LocalBroadcastManager.getInstance(context).sendBroadcast(intent) + // TODO The test notification has been clicked, notify the ui } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/channels/NotificationChannels.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/channels/NotificationChannels.kt index 0624b863ed..ab115262de 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/channels/NotificationChannels.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/channels/NotificationChannels.kt @@ -162,7 +162,7 @@ class NotificationChannels @Inject constructor( private const val CALL_NOTIFICATION_CHANNEL_ID = "CALL_NOTIFICATION_CHANNEL_ID_V2" @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O) - private fun supportNotificationChannels() = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + private fun supportNotificationChannels() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O fun openSystemSettingsForSilentCategory(activity: Activity) { activity.startNotificationChannelSettingsIntent(SILENT_NOTIFICATION_CHANNEL_ID) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt index 3ad848aeb4..aa1d0032e0 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt @@ -16,22 +16,17 @@ package io.element.android.libraries.push.impl.push -import android.content.Context -import android.content.Intent import android.os.Handler import android.os.Looper -import androidx.localbroadcastmanager.content.LocalBroadcastManager import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.AppScope -import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.push.impl.PushersManager import io.element.android.libraries.push.impl.log.pushLoggerTag -import io.element.android.libraries.push.impl.notifications.NotifiableEventResolver -import io.element.android.libraries.push.impl.notifications.NotificationActionIds import io.element.android.libraries.push.impl.notifications.DefaultNotificationDrawerManager +import io.element.android.libraries.push.impl.notifications.NotifiableEventResolver import io.element.android.libraries.push.impl.store.DefaultPushDataStore import io.element.android.libraries.pushproviders.api.PushData import io.element.android.libraries.pushproviders.api.PushHandler @@ -53,8 +48,7 @@ class DefaultPushHandler @Inject constructor( private val defaultPushDataStore: DefaultPushDataStore, private val userPushStoreFactory: UserPushStoreFactory, private val pushClientSecret: PushClientSecret, - private val actionIds: NotificationActionIds, - @ApplicationContext private val context: Context, + // private val actionIds: NotificationActionIds, private val buildMeta: BuildMeta, private val matrixAuthenticationService: MatrixAuthenticationService, ) : PushHandler { @@ -82,8 +76,8 @@ class DefaultPushHandler @Inject constructor( // Diagnostic Push if (pushData.eventId == PushersManager.TEST_EVENT_ID) { - val intent = Intent(actionIds.push) - LocalBroadcastManager.getInstance(context).sendBroadcast(intent) + // val intent = Intent(actionIds.push) + // TODO The test push has been received, notify the ui return } diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/EnsureFcmTokenIsRetrievedUseCase.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/EnsureFcmTokenIsRetrievedUseCase.kt deleted file mode 100644 index d557aa0334..0000000000 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/EnsureFcmTokenIsRetrievedUseCase.kt +++ /dev/null @@ -1,45 +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.pushproviders.firebase - -import javax.inject.Inject - -// TODO -class EnsureFcmTokenIsRetrievedUseCase @Inject constructor( -// private val unifiedPushHelper: UnifiedPushHelper, -// private val fcmHelper: FcmHelper, - // private val activeSessionHolder: ActiveSessionHolder, -) { - -// fun execute(pushersManager: PushersManager, registerPusher: Boolean) { -// if (unifiedPushHelper.isEmbeddedDistributor()) { -// fcmHelper.ensureFcmTokenIsRetrieved(pushersManager, shouldAddHttpPusher(registerPusher)) -// } -// } - - private fun shouldAddHttpPusher(registerPusher: Boolean) = if (registerPusher) { - /* - TODO EAx - val currentSession = activeSessionHolder.getActiveSession() - val currentPushers = currentSession.pushersService().getPushers() - currentPushers.none { it.deviceId == currentSession.sessionParams.deviceId } - */ - true - } else { - false - } -} diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnregisterUnifiedPushUseCase.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnregisterUnifiedPushUseCase.kt index 4efaacbf3a..4bf8217914 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnregisterUnifiedPushUseCase.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnregisterUnifiedPushUseCase.kt @@ -26,7 +26,7 @@ class UnregisterUnifiedPushUseCase @Inject constructor( @ApplicationContext private val context: Context, //private val pushDataStore: PushDataStore, private val unifiedPushStore: UnifiedPushStore, - private val unifiedPushGatewayResolver: UnifiedPushGatewayResolver, + // private val unifiedPushGatewayResolver: UnifiedPushGatewayResolver, ) { suspend fun execute(clientSecret: String /*pushersManager: PushersManager?*/) { diff --git a/libraries/pushstore/impl/build.gradle.kts b/libraries/pushstore/impl/build.gradle.kts index dca5c82a4d..5946e77694 100644 --- a/libraries/pushstore/impl/build.gradle.kts +++ b/libraries/pushstore/impl/build.gradle.kts @@ -20,6 +20,11 @@ plugins { android { namespace = "io.element.android.libraries.push.pushstore.impl" + + defaultConfig { + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments["clearPackageData"] = "true" + } } anvil { @@ -43,4 +48,13 @@ dependencies { testImplementation(libs.coroutines.test) testImplementation(projects.libraries.matrix.test) testImplementation(projects.services.appnavstate.test) + + androidTestImplementation(libs.coroutines.test) + androidTestImplementation(libs.test.core) + androidTestImplementation(libs.test.junit) + androidTestImplementation(libs.test.truth) + androidTestImplementation(libs.test.runner) + androidTestImplementation(projects.libraries.sessionStorage.test) + + coreLibraryDesugaring(libs.android.desugar) } diff --git a/libraries/pushstore/impl/src/androidTest/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactoryTest.kt b/libraries/pushstore/impl/src/androidTest/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactoryTest.kt new file mode 100644 index 0000000000..c87c772ddf --- /dev/null +++ b/libraries/pushstore/impl/src/androidTest/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactoryTest.kt @@ -0,0 +1,56 @@ +/* + * 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.pushstore.impl + +import androidx.test.platform.app.InstrumentationRegistry +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.pushstore.api.UserPushStore +import io.element.android.libraries.sessionstorage.test.observer.NoOpSessionObserver +import kotlinx.coroutines.runBlocking +import org.junit.Test +import kotlin.concurrent.thread + +/** + * Note: to clear the emulator, invoke: + * adb uninstall io.element.android.libraries.push.pushstore.impl.test + */ +class DefaultUserPushStoreFactoryTest { + + /** + * Ensure that creating UserPushStore is thread safe. + */ + @Test + fun testParallelCreation() { + val context = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext + val sessionId = SessionId("@alice:server.org") + val userPushStoreFactory = DefaultUserPushStoreFactory(context, NoOpSessionObserver()) + var userPushStore1: UserPushStore? = null + val thread1 = thread { + userPushStore1 = userPushStoreFactory.create(sessionId) + } + var userPushStore2: UserPushStore? = null + val thread2 = thread { + userPushStore2 = userPushStoreFactory.create(sessionId) + } + thread1.join() + thread2.join() + runBlocking { + userPushStore1!!.areNotificationEnabledForDevice() + userPushStore2!!.areNotificationEnabledForDevice() + } + } +} diff --git a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactory.kt b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactory.kt index a84fe2ea69..8c85dca80c 100644 --- a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactory.kt +++ b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactory.kt @@ -26,6 +26,7 @@ import io.element.android.libraries.pushstore.api.UserPushStore import io.element.android.libraries.pushstore.api.UserPushStoreFactory import io.element.android.libraries.sessionstorage.api.observer.SessionListener import io.element.android.libraries.sessionstorage.api.observer.SessionObserver +import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject @SingleIn(AppScope::class) @@ -39,7 +40,7 @@ class DefaultUserPushStoreFactory @Inject constructor( } // We can have only one class accessing a single data store, so keep a cache of them. - private val cache = mutableMapOf() + private val cache = ConcurrentHashMap() override fun create(userId: SessionId): UserPushStore { return cache.getOrPut(userId) { UserPushStoreDataStore( diff --git a/libraries/session-storage/test/build.gradle.kts b/libraries/session-storage/test/build.gradle.kts new file mode 100644 index 0000000000..0c8de84669 --- /dev/null +++ b/libraries/session-storage/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.libraries.sessionstorage.test" +} + +dependencies { + implementation(projects.libraries.sessionStorage.api) +} diff --git a/libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/observer/NoOpSessionObserver.kt b/libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/observer/NoOpSessionObserver.kt new file mode 100644 index 0000000000..03487a3701 --- /dev/null +++ b/libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/observer/NoOpSessionObserver.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.sessionstorage.test.observer + +import io.element.android.libraries.sessionstorage.api.observer.SessionListener +import io.element.android.libraries.sessionstorage.api.observer.SessionObserver + +class NoOpSessionObserver : SessionObserver { + override fun addListener(listener: SessionListener) = Unit + override fun removeListener(listener: SessionListener) = Unit +} diff --git a/libraries/textcomposer/src/main/res/values-sk/translations.xml b/libraries/textcomposer/src/main/res/values-sk/translations.xml index a5f42a60f8..26ac1df436 100644 --- a/libraries/textcomposer/src/main/res/values-sk/translations.xml +++ b/libraries/textcomposer/src/main/res/values-sk/translations.xml @@ -1,5 +1,6 @@ + "Pridať prílohu" "Prepnúť zoznam odrážok" "Prepnúť blok kódu" "Správa…" 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 654f3d7cbe..726d1b1505 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -143,6 +143,8 @@ "%1$s nedokázal načítať mapu. Skúste to prosím neskôr." "Načítanie správ zlyhalo" "%1$s nemohol získať prístup k vašej polohe. Skúste to prosím neskôr." + "Ak chcete odoslať polohu, povoľte %1$s prístup k vašej polohe z obrazovky nastavení." + "Ak chcete odoslať polohu, povoľte %1$s prístup k vašej polohe v nasledujúcom dialógovom okne." "Niektoré správy neboli odoslané" "Prepáčte, vyskytla sa chyba" "🔐️ Pripojte sa ku mne na %1$s" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 10952f4194..b17d8f337f 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -143,8 +143,8 @@ "%1$s could not load the map. Please try again later." "Failed loading messages" "%1$s could not access your location. Please try again later." - "To send a location, allow %1$s to access your location from its settings screen." - "To send a location, allow %1$s to access your location in the next dialog." + "%1$s does not have permission to access your location. You can enable access in Settings." + "%1$s does not have permission to access your location. Enable access below." "Some messages have not been sent" "Sorry, an error occurred" "🔐️ Join me on %1$s" diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index 3a6c591e19..2e89afa70f 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -17,10 +17,50 @@ import org.gradle.api.JavaVersion import org.gradle.jvm.toolchain.JavaLanguageVersion -object Versions { - const val versionCode = 100200 - const val versionName = "0.2.0" +/** + * Version codes are quite sensitive, because there is a mix between bundle and APKs, and we have to take into + * account the future upgrade of Element Android. + * Max versionCode allowed by the PlayStore (for information): + * 2_100_000_000 + * Current version code of EAx on the PlayStore, for the first uploaded beta (we cannot go below): + * ----1_001_000 + * Current version code of EAx on the nightly: + * ----1_001_000 + * Current version of Element Android (at some point EAx will replace this app) (v1.6.3) + * ----40_106_03a where a stands for the architecture: 1, 2, 3, 4 and 0 for the universal APK + * Current version of EAx distributed with Firebase app distribution: + * ----1_002_000 + * Latest version of EAx distributed with Firebase app distribution (downgrading, so that's a problem) + * -------10_200 + * Version when running the current debug build + * -------10_200 + * + * So adding 4_000_000 to the current version Code computed here should be fine, and since the versionCode + * is multiplied by 10 in app/build.gradle.kts#L168: + * ``` + * output.versionCode.set((output.versionCode.get() ?: 0) * 10 + abiCode)) + * ``` + * we will have: + * Release version: + * ---40_001_020 + * Nightly version: + * ---40_001_020 + * Debug version: + * ---40_010_200 + */ +// Note: 2 digits max for each value +private const val versionMajor = 0 +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 = 2 + +object Versions { + val versionCode = 4_000_000 + versionMajor * 1_00_00 + versionMinor * 1_00 + versionPatch + val versionName = "$versionMajor.$versionMinor.$versionPatch" const val compileSdk = 33 const val targetSdk = 33 const val minSdk = 23 diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index 88f499b993..fb082e27a7 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -103,8 +103,12 @@ fun DependencyHandlerScope.allLibrariesImpl() { } fun DependencyHandlerScope.allServicesImpl() { + // For analytics configuration, either use noop, or use the impl, with at least one analyticsproviders implementation + // implementation(project(":services:analytics:noop")) implementation(project(":services:analytics:impl")) implementation(project(":services:analyticsproviders:posthog")) + implementation(project(":services:analyticsproviders:sentry")) + implementation(project(":services:apperror:impl")) implementation(project(":services:appnavstate:impl")) implementation(project(":services:toolbox:impl")) diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt index f66de878fb..41dbc8bfe2 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt @@ -87,7 +87,6 @@ class RoomListScreen( Singleton.appScope.launch { withContext(coroutineDispatchers.io) { matrixClient.getRoom(roomId)!!.use { room -> - room.open() room.timeline.paginateBackwards(20, 50) } } diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsService.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsService.kt index 9c6fb2d522..309a885ad2 100644 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsService.kt +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsService.kt @@ -22,7 +22,10 @@ import io.element.android.services.analyticsproviders.api.trackers.ErrorTracker import kotlinx.coroutines.flow.Flow interface AnalyticsService: AnalyticsTracker, ErrorTracker { - fun getAvailableAnalyticsProviders(): List + /** + * Get the available analytics providers. + */ + fun getAvailableAnalyticsProviders(): Set /** * Return a Flow of Boolean, true if the user has given their consent. diff --git a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsService.kt b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsService.kt index 5639f954ac..42acd29b56 100644 --- a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsService.kt +++ b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsService.kt @@ -56,8 +56,8 @@ class DefaultAnalyticsService @Inject constructor( observeSessions() } - override fun getAvailableAnalyticsProviders(): List { - return analyticsProviders.sortedBy { it.index } + override fun getAvailableAnalyticsProviders(): Set { + return analyticsProviders } override fun getUserConsent(): Flow { diff --git a/services/analytics/noop/build.gradle.kts b/services/analytics/noop/build.gradle.kts index a5678f5cb3..000434a05c 100644 --- a/services/analytics/noop/build.gradle.kts +++ b/services/analytics/noop/build.gradle.kts @@ -19,7 +19,7 @@ plugins { } android { - namespace = "io.element.android.services.analytics.impl" + namespace = "io.element.android.services.analytics.noop" } anvil { @@ -28,6 +28,7 @@ anvil { dependencies { implementation(libs.dagger) + implementation(projects.libraries.architecture) implementation(projects.libraries.di) api(projects.services.analytics.api) } diff --git a/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsService.kt b/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsService.kt new file mode 100644 index 0000000000..f82e7ff550 --- /dev/null +++ b/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsService.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.services.analytics.noop + +import com.squareup.anvil.annotations.ContributesBinding +import im.vector.app.features.analytics.itf.VectorAnalyticsEvent +import im.vector.app.features.analytics.itf.VectorAnalyticsScreen +import im.vector.app.features.analytics.plan.UserProperties +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SingleIn +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analyticsproviders.api.AnalyticsProvider +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import javax.inject.Inject + +@SingleIn(AppScope::class) +@ContributesBinding(AppScope::class) +class NoopAnalyticsService @Inject constructor( +) : AnalyticsService { + override fun getAvailableAnalyticsProviders(): Set = emptySet() + override fun getUserConsent(): Flow = flowOf(false) + override suspend fun setUserConsent(userConsent: Boolean) = Unit + override fun didAskUserConsent(): Flow = flowOf(true) + override suspend fun setDidAskUserConsent() = Unit + override fun getAnalyticsId(): Flow = flowOf("") + override suspend fun setAnalyticsId(analyticsId: String) = Unit + override suspend fun onSignOut() = Unit + override suspend fun reset() = Unit + override fun capture(event: VectorAnalyticsEvent) = Unit + override fun screen(screen: VectorAnalyticsScreen) = Unit + override fun updateUserProperties(userProperties: UserProperties) = Unit + override fun trackError(throwable: Throwable) = Unit +} diff --git a/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/AnalyticsProvider.kt b/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/AnalyticsProvider.kt index 548f47d7ad..807c8d1413 100644 --- a/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/AnalyticsProvider.kt +++ b/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/AnalyticsProvider.kt @@ -20,11 +20,6 @@ import io.element.android.services.analyticsproviders.api.trackers.AnalyticsTrac import io.element.android.services.analyticsproviders.api.trackers.ErrorTracker interface AnalyticsProvider: AnalyticsTracker, ErrorTracker { - /** - * Allow to sort providers, from lower index to higher index. - */ - val index: Int - /** * User friendly name. */ 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 92e73195c0..fb2e341e1e 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 @@ -16,7 +16,6 @@ package io.element.android.services.analyticsproviders.posthog -import com.posthog.android.Options import com.posthog.android.PostHog import com.posthog.android.Properties import com.squareup.anvil.annotations.ContributesMultibinding @@ -29,14 +28,13 @@ import io.element.android.services.analyticsproviders.posthog.log.analyticsTag import timber.log.Timber import javax.inject.Inject -private val REUSE_EXISTING_ID: String? = null -private val IGNORED_OPTIONS: Options? = null +// private val REUSE_EXISTING_ID: String? = null +// private val IGNORED_OPTIONS: Options? = null @ContributesMultibinding(AppScope::class) class PosthogAnalyticsProvider @Inject constructor( private val postHogFactory: PostHogFactory, ) : AnalyticsProvider { - override val index = PosthogConfig.index override val name = PosthogConfig.name private var posthog: PostHog? = null @@ -101,9 +99,11 @@ class PosthogAnalyticsProvider @Inject constructor( * We avoid sending nulls as part of the UserProperties as this will reset the values across all devices. * The UserProperties event has nullable properties to allow for clients to opt in. */ + /* private fun Map.toPostHogUserProperties(): Properties { return Properties().apply { putAll(this@toPostHogUserProperties.filter { it.value != null }) } } + */ } 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/PosthogConfig.kt index 877fb7dc9a..96d8659b11 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/PosthogConfig.kt @@ -17,7 +17,6 @@ package io.element.android.services.analyticsproviders.posthog object PosthogConfig { - const val index = 0 const val name = "Posthog" const val postHogHost = "https://posthog.element.dev" const val postHogApiKey = "phc_VtA1L35nw3aeAtHIx1ayrGdzGkss7k1xINeXcoIQzXN" diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/log/AnalyticsLoggerTag.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/log/AnalyticsLoggerTag.kt index 8e64ca100d..a298bda1c5 100644 --- a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/log/AnalyticsLoggerTag.kt +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/log/AnalyticsLoggerTag.kt @@ -18,4 +18,4 @@ package io.element.android.services.analyticsproviders.posthog.log import io.element.android.libraries.core.log.logger.LoggerTag -val analyticsTag = LoggerTag("Analytics") +internal val analyticsTag = LoggerTag("Posthog") diff --git a/services/analyticsproviders/sentry/build.gradle.kts b/services/analyticsproviders/sentry/build.gradle.kts new file mode 100644 index 0000000000..34c444eb7d --- /dev/null +++ b/services/analyticsproviders/sentry/build.gradle.kts @@ -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. + */ +plugins { + id("io.element.android-library") + alias(libs.plugins.anvil) +} + +android { + namespace = "io.element.android.services.analyticsproviders.sentry" +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + implementation(libs.dagger) + implementation(libs.sentry) + implementation(projects.libraries.core) + implementation(projects.libraries.di) + implementation(projects.services.analyticsproviders.api) +} diff --git a/services/analyticsproviders/sentry/src/main/AndroidManifest.xml b/services/analyticsproviders/sentry/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..079912fc00 --- /dev/null +++ b/services/analyticsproviders/sentry/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + diff --git a/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProvider.kt b/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProvider.kt new file mode 100644 index 0000000000..6bc4df426f --- /dev/null +++ b/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProvider.kt @@ -0,0 +1,79 @@ +/* + * 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.sentry + +import android.content.Context +import com.squareup.anvil.annotations.ContributesMultibinding +import im.vector.app.features.analytics.itf.VectorAnalyticsEvent +import im.vector.app.features.analytics.itf.VectorAnalyticsScreen +import im.vector.app.features.analytics.plan.UserProperties +import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.core.meta.BuildType +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.ApplicationContext +import io.element.android.services.analyticsproviders.api.AnalyticsProvider +import io.element.android.services.analyticsproviders.sentry.log.analyticsTag +import io.sentry.Sentry +import io.sentry.SentryOptions +import io.sentry.android.core.SentryAndroid +import timber.log.Timber +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +class SentryAnalyticsProvider @Inject constructor( + @ApplicationContext private val context: Context, + private val buildMeta: BuildMeta, +) : AnalyticsProvider { + override val name = SentryConfig.name + + override fun init() { + Timber.tag(analyticsTag.value).d("Initializing Sentry") + if (Sentry.isEnabled()) return + SentryAndroid.init(context) { options -> + options.dsn = SentryConfig.dns + options.beforeSend = SentryOptions.BeforeSendCallback { event, _ -> event } + options.tracesSampleRate = 1.0 + options.isEnableUserInteractionTracing = true + options.environment = buildMeta.buildType.toSentryEnv() + options.diagnosticLevel + } + } + + override fun stop() { + Timber.tag(analyticsTag.value).d("Stopping Sentry") + Sentry.close() + } + + override fun capture(event: VectorAnalyticsEvent) { + } + + override fun screen(screen: VectorAnalyticsScreen) { + } + + override fun updateUserProperties(userProperties: UserProperties) { + } + + override fun trackError(throwable: Throwable) { + Sentry.captureException(throwable) + } +} + +private fun BuildType.toSentryEnv() = when (this) { + BuildType.RELEASE -> SentryConfig.envRelease + BuildType.NIGHTLY, + BuildType.DEBUG -> SentryConfig.envDebug +} diff --git a/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryConfig.kt b/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryConfig.kt new file mode 100644 index 0000000000..f2048b59f0 --- /dev/null +++ b/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryConfig.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.services.analyticsproviders.sentry + +object SentryConfig { + const val name = "Sentry" + const val dns = "https://32f7ff6a6e724f90838b7654042b2e81@sentry.tools.element.io/59" + const val envDebug = "DEBUG" + const val envRelease = "RELEASE" +} diff --git a/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/log/AnalyticsLoggerTag.kt b/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/log/AnalyticsLoggerTag.kt new file mode 100644 index 0000000000..f792009ee4 --- /dev/null +++ b/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/log/AnalyticsLoggerTag.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.services.analyticsproviders.sentry.log + +import io.element.android.libraries.core.log.logger.LoggerTag + +internal val analyticsTag = LoggerTag("Sentry") diff --git a/tests/uitests/build.gradle.kts b/tests/uitests/build.gradle.kts index 729899c4f8..1881822691 100644 --- a/tests/uitests/build.gradle.kts +++ b/tests/uitests/build.gradle.kts @@ -42,7 +42,6 @@ dependencies { testImplementation(libs.test.junit) testImplementation(libs.test.parameter.injector) testImplementation(projects.libraries.designsystem) - androidTestImplementation(libs.test.junitext) ksp(libs.showkase.processor) kspTest(libs.showkase.processor) diff --git a/tests/uitests/src/main/kotlin/io/element/android/tests/uitests/ShowkaseNavigation.kt b/tests/uitests/src/main/kotlin/io/element/android/tests/uitests/ShowkaseNavigation.kt index 8a33430340..cb7795c05a 100644 --- a/tests/uitests/src/main/kotlin/io/element/android/tests/uitests/ShowkaseNavigation.kt +++ b/tests/uitests/src/main/kotlin/io/element/android/tests/uitests/ShowkaseNavigation.kt @@ -18,7 +18,6 @@ package io.element.android.tests.uitests import android.app.Activity import android.content.Intent -import com.airbnb.android.showkase.models.Showkase import com.airbnb.android.showkase.ui.ShowkaseBrowserActivity fun openShowkase(activity: Activity) { diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.ftue.impl.welcome_null_DefaultGroup_WelcomeViewPreview-D-0_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.ftue.impl.welcome_null_DefaultGroup_WelcomeViewPreview-D-0_1_null,NEXUS_5,1.0,en].png index f300f92921..a72f564891 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.ftue.impl.welcome_null_DefaultGroup_WelcomeViewPreview-D-0_1_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.ftue.impl.welcome_null_DefaultGroup_WelcomeViewPreview-D-0_1_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16de62092834bf803c8165e974f45e14ccfc0128a3e74295a58eef965abc10c5 -size 301336 +oid sha256:1387a1337da70f8e87474aef106110f0dfb55e59f340e0906390e509da1dd0b4 +size 299376 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.ftue.impl.welcome_null_DefaultGroup_WelcomeViewPreview-N-0_2_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.ftue.impl.welcome_null_DefaultGroup_WelcomeViewPreview-N-0_2_null,NEXUS_5,1.0,en].png index 7465768560..a12f3bceee 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.ftue.impl.welcome_null_DefaultGroup_WelcomeViewPreview-N-0_2_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.ftue.impl.welcome_null_DefaultGroup_WelcomeViewPreview-N-0_2_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6838e81cc5f2755ff76de7254e2c8bb445b76662d7ba9b4c83443b2c2ed03029 -size 406044 +oid sha256:cdf0215f1ba1f6a89a6204e19b3df7dec1e64d7fd71bdf8c706e1969e11e702d +size 404366 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-D-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-D-0_1_null_0,NEXUS_5,1.0,en].png index fe104f1c2f..c092db7ce4 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-D-0_1_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-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:69535debd585127a4ce8b490ef6682c2e6c3c4d16478e6b9e9687ee1c1133637 -size 20879 +oid sha256:84581aac943c5065f1e5438465ee7d1845555e68aa005d8b596542ce3830dc83 +size 21258 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-D-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-D-0_1_null_1,NEXUS_5,1.0,en].png index 09e7be69f8..84cc8df088 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-D-0_1_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-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:356756de9f08042c3c2f3033d3f8a39cd9b49c5cfbcfbc274933c3efedd80d3d -size 34534 +oid sha256:bb559f5cd8b391ab4406c903a59376061255142317b035bd084f153779036eb9 +size 36589 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-D-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-D-0_1_null_2,NEXUS_5,1.0,en].png index 5311a17e6e..a489609443 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-D-0_1_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-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:51ac3c4bb27d78419f73b99cb24327514ce56a64a03bf74ce41f158c2c3bd516 -size 33605 +oid sha256:ae73b980357c9def721335ecd69ffb8d921963d422e38c84735f53b6f4a596f2 +size 35101 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-D-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-D-0_1_null_3,NEXUS_5,1.0,en].png index fe104f1c2f..c092db7ce4 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-D-0_1_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-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:69535debd585127a4ce8b490ef6682c2e6c3c4d16478e6b9e9687ee1c1133637 -size 20879 +oid sha256:84581aac943c5065f1e5438465ee7d1845555e68aa005d8b596542ce3830dc83 +size 21258 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-D-0_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-D-0_1_null_4,NEXUS_5,1.0,en].png index 960bd96e80..c9c6837a0e 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-D-0_1_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-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:1fa3b5aae9cee5e2fec8929697b0606b655c45b10a65bcd641da315e98c48e1e -size 20951 +oid sha256:46ad7d3a46b54543f226e82fa4199e2e2de7e2e91748d663bed88dfb7afc5b61 +size 21350 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-N-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-N-0_2_null_0,NEXUS_5,1.0,en].png index 643983770c..49bfbea7e2 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-N-0_2_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-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:ce74fa2b0364763152e69dcfa4f8d598504b630fa1227f2fa685bc886ccd5afa -size 19434 +oid sha256:6fb273e816484cb326ea4ba00948e749362542c1b964e23c5dc48b306b909136 +size 19843 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-N-0_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-N-0_2_null_1,NEXUS_5,1.0,en].png index 173360baef..b3f1ae991d 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-N-0_2_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-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:12a78fbc2d2e84e93d40d1e075d8b46c5366f3d323dd85f27be286a64231f884 -size 32120 +oid sha256:fac71b82a06f65e799ca345cc9b2e22af6c0000b9900bdcdc584ca6b0f6c2c4d +size 34178 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-N-0_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-N-0_2_null_2,NEXUS_5,1.0,en].png index 0fa0b9c5d2..521af2091c 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-N-0_2_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-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:a1bd4187a3713153d6e9ed94594385c3806f59c4d36cc6f2867a38d630551505 -size 31302 +oid sha256:e66b4ae355ae37f7810db1923c4018a44b1056c6471a3ed675032f036d30ff34 +size 32685 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-N-0_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-N-0_2_null_3,NEXUS_5,1.0,en].png index 643983770c..49bfbea7e2 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-N-0_2_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-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:ce74fa2b0364763152e69dcfa4f8d598504b630fa1227f2fa685bc886ccd5afa -size 19434 +oid sha256:6fb273e816484cb326ea4ba00948e749362542c1b964e23c5dc48b306b909136 +size 19843 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-N-0_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-N-0_2_null_4,NEXUS_5,1.0,en].png index ce8b5bf468..652c6b6492 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-N-0_2_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.send_null_DefaultGroup_SendLocationViewPreview-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:9390a3b8111ab0d31b3bbf3b6bf8794432d135130b37404ce4a646a74369d85b -size 19502 +oid sha256:91378a7a126d1d9f9f234368930635ce85db3666721dc32b0646effa59d0fdee +size 19991 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png index b56214c49f..6c81af0a3c 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c936d2d804bc9e98fcc49430f11ddaa572b05fc8d3a0df93ad6521ee8e78f708 -size 21806 +oid sha256:6d98aabf3bd99793367632e18d5cf678e75fb5ad872a1cb300a7e939ad0c2683 +size 19725 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..b56214c49f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c936d2d804bc9e98fcc49430f11ddaa572b05fc8d3a0df93ad6521ee8e78f708 +size 21806 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewLightPreview_0_null_4,NEXUS_5,1.0,en].png index 7701a371c3..ad38501385 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewLightPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewLightPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0bb88c64bc68b10b1fb709135f445de5a5e4d78623448d0fef97504e025d5f6d -size 24551 +oid sha256:94be795b626868e8afbcc26c3f0161ddcb946a122c02eeed7e823825c9aeba19 +size 22263 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewLightPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewLightPreview_0_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7701a371c3 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.location.impl.show_null_DefaultGroup_ShowLocationViewLightPreview_0_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0bb88c64bc68b10b1fb709135f445de5a5e4d78623448d0fef97504e025d5f6d +size 24551 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-D-8_9_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-D-10_11_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-D-8_9_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-D-10_11_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-D-8_9_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-D-10_11_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-D-8_9_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-D-10_11_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-D-8_9_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-D-10_11_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-D-8_9_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-D-10_11_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-N-8_10_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-N-10_12_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-N-8_10_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-N-10_12_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-N-8_10_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-N-10_12_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-N-8_10_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-N-10_12_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-N-8_10_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-N-10_12_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-N-8_10_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemAudioViewPreview-N-10_12_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewPreview-D-9_10_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewPreview-D-11_12_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewPreview-D-9_10_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewPreview-D-11_12_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewPreview-D-9_10_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewPreview-D-11_12_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewPreview-D-9_10_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewPreview-D-11_12_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewPreview-N-9_11_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewPreview-N-11_13_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewPreview-N-9_11_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewPreview-N-11_13_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewPreview-N-9_11_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewPreview-N-11_13_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewPreview-N-9_11_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.event_null_DefaultGroup_TimelineItemLocationViewPreview-N-11_13_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.virtual_null_DefaultGroup_TimelineEncryptedHistoryBannerViewPreview-D-10_11_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.virtual_null_DefaultGroup_TimelineEncryptedHistoryBannerViewPreview-D-12_13_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.virtual_null_DefaultGroup_TimelineEncryptedHistoryBannerViewPreview-D-10_11_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.virtual_null_DefaultGroup_TimelineEncryptedHistoryBannerViewPreview-D-12_13_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.virtual_null_DefaultGroup_TimelineEncryptedHistoryBannerViewPreview-N-10_12_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.virtual_null_DefaultGroup_TimelineEncryptedHistoryBannerViewPreview-N-12_14_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.virtual_null_DefaultGroup_TimelineEncryptedHistoryBannerViewPreview-N-10_12_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components.virtual_null_DefaultGroup_TimelineEncryptedHistoryBannerViewPreview-N-12_14_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionExtraButtonsPreview-D-4_5_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionExtraButtonsPreview-D-4_5_null,NEXUS_5,1.0,en].png index d732d1c37b..37cce233e5 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionExtraButtonsPreview-D-4_5_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionExtraButtonsPreview-D-4_5_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:724bc9b0db8f581119201a8db3c405c9a7e7261cccea45a354fd37a5e33fcb41 -size 11141 +oid sha256:6c73a80923ec5b269bbb5f5108dde9617dac39e9c51f9199ab9229c4dd7ca2b0 +size 11532 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionExtraButtonsPreview-N-4_6_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionExtraButtonsPreview-N-4_6_null,NEXUS_5,1.0,en].png index 9b08510703..c122f1e1f8 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionExtraButtonsPreview-N-4_6_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_MessagesReactionExtraButtonsPreview-N-4_6_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:109a1a9c6359a9fd7fe4258f022e845c9b8142f9e590a03e5c2efa9273cb572f -size 10742 +oid sha256:9eee3a5873231554cc0335fe7b552a62459f35e4ffb8c43b91a9746b34314bdc +size 11171 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowDarkPreview_0_null,NEXUS_5,1.0,en].png index 3fc8c841f2..fa1f71720b 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:38c6d3f4a47ed89c3deb4150024b49c0a533a859f21a1592a82309b8c7316ea4 -size 152242 +oid sha256:85f9960cebee2e04d09f3ef17bd81e9adf9f463b24edae73a56d6d0c0a09ce48 +size 152228 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowLightPreview_0_null,NEXUS_5,1.0,en].png index b204098bdc..abd9d48625 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f82840398e396e038ad68a5fe855394a0088c413e7aade339998b8ed63fb1c09 -size 157273 +oid sha256:39226828ae899c8b765827cad36f9966ec80c0a39ed406c1257224a56255af9b +size 157243 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithManyReactionsDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithManyReactionsDarkPreview_0_null,NEXUS_5,1.0,en].png index d389368d7e..5349a17c04 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithManyReactionsDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithManyReactionsDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c5ef7519aef3badeda3983b2ec5474f31404bc06b4b46b9829848dc7884fe1d -size 81749 +oid sha256:88bdceab1a4e44f971ce507b5b6aeb5513657dfe6c4d61ec5483e215d83254d5 +size 81534 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithManyReactionsLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithManyReactionsLightPreview_0_null,NEXUS_5,1.0,en].png index 722a25f340..f5b5822a12 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithManyReactionsLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithManyReactionsLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c9e9b769422e4714e87ace97058e5a8f064e3b927e3a7c6042494de8381d6b09 -size 85856 +oid sha256:461683659d023323184049e122d02e4c63f217157e0bb3165f50be833d19ba7e +size 85517 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithReplyDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithReplyDarkPreview_0_null,NEXUS_5,1.0,en].png index f2add007e5..dd1ef38d17 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithReplyDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithReplyDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4970830b042e7b6588c03bf632ae3ade5b39de7339e27618ee341cbd9861c0e9 -size 129351 +oid sha256:c00bf46d1ef6337bad18f6454cf4601141ed2190c6a697ddc46d5a156d4997ca +size 127950 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithReplyLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithReplyLightPreview_0_null,NEXUS_5,1.0,en].png index a048edf7d8..dce8da362e 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithReplyLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemEventRowWithReplyLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4aba48e03e8424e5ff131c22fd5a606280745dd318f5822e2167f3b39794ff41 -size 134569 +oid sha256:88cd76c95c31061bf4e9e7528ed5d66a8b49813d868f21b2d2615e77318a9706 +size 133068 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsLayoutPreview-D-5_6_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsLayoutPreview-D-5_6_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a47fa7ba44 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsLayoutPreview-D-5_6_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c81a4a854304b62bd356ae9a6d588918d11bf1df0aaa209910fa2fa970f47cb8 +size 26684 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsLayoutPreview-N-5_7_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsLayoutPreview-N-5_7_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..20d82e1483 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsLayoutPreview-N-5_7_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f32e1f9f7c10672b3a35d6c0c3e3a9347b92ef3fe62be40bc378a5225da5d3d6 +size 26285 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewCollapsedPreview-D-6_7_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewCollapsedPreview-D-6_7_null,NEXUS_5,1.0,en].png deleted file mode 100644 index c838261a0d..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewCollapsedPreview-D-6_7_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dfccebdef9523a984d9edc2414aa159ed025aec14f891945abed73472e462df8 -size 13641 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewCollapsedPreview-N-6_8_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewCollapsedPreview-N-6_8_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 001fbca040..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewCollapsedPreview-N-6_8_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9c971ed744305a474f182bf3d6fde281b9034f99cee4f4207460a733ac8490a7 -size 13464 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewExpandedPreview-D-7_8_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewExpandedPreview-D-7_8_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 53c52d9a7d..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewExpandedPreview-D-7_8_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:999475ddc51f74a590e5ef2810ab31222127e55cba01fadbeb8cb8e9058ad6f0 -size 27170 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewExpandedPreview-N-7_9_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewExpandedPreview-N-7_9_null,NEXUS_5,1.0,en].png deleted file mode 100644 index cbed0a0752..0000000000 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewExpandedPreview-N-7_9_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6fb7202b53005274d98f0aee8014326f02bb62fcd323b38b4d8a447078499dd8 -size 26965 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewFewPreview-D-7_8_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewFewPreview-D-7_8_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..d85a93d2d4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewFewPreview-D-7_8_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e892d14d11fb576228418acd216468438f41a631a073e80638675ec48a91ed6 +size 12334 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewFewPreview-N-7_9_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewFewPreview-N-7_9_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a645425261 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewFewPreview-N-7_9_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64c2e18e97bba9ff6ae6bb24a17fe567352cd49f1b5905065b7de8854369e22f +size 12209 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewIncomingPreview-D-8_9_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewIncomingPreview-D-8_9_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..7fd9264402 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewIncomingPreview-D-8_9_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d0d29cc0acc0d9009c6fd6aebccf72d6244cec8ec3e3bf3bd445e7cced52561 +size 26004 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewIncomingPreview-N-8_10_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewIncomingPreview-N-8_10_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..674e4ccb32 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewIncomingPreview-N-8_10_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbfe5166cb1df7be35f2d3f114f73fb4425a10f4eb5bd24a87cafe3ddd76e873 +size 25719 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewOutgoingPreview-D-9_10_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewOutgoingPreview-D-9_10_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..8fb3ea38a2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewOutgoingPreview-D-9_10_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5f4241f356099cc2f2f55e557218c8872cd0f25f582ba75db254c11613090db +size 26026 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewOutgoingPreview-N-9_11_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewOutgoingPreview-N-9_11_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..fe175d156e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewOutgoingPreview-N-9_11_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf149f6a8c5c8d5d4591b9f6cfe8612a1af2b1cb101b0d1f699945bd711890b4 +size 25685 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewPreview-D-5_6_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewPreview-D-6_7_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewPreview-D-5_6_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewPreview-D-6_7_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewPreview-N-5_7_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewPreview-N-6_8_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewPreview-N-5_7_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_TimelineItemReactionsViewPreview-N-6_8_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_0,NEXUS_5,1.0,en].png index 7c99f5e856..f1add17e7a 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-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:54c12970e3563de958f88e4c538dd368f9810266060627393256a91741f7c6cf -size 53340 +oid sha256:cfc4f0cc8e252cdc9200286cf330c4bbdf3cd66ced2c23364cca680aca79954d +size 53293 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_1,NEXUS_5,1.0,en].png index def9cbe0d1..3a9a78de1d 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-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:3b7ae3084cb9d1ecee2e4db49c228516bdd7352683e797edf737c8d216922dec -size 65601 +oid sha256:01c7f94b759eea738fb42baa4b8b835c27d48e3fbfdd40d0a925080db8196a80 +size 65563 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_10,NEXUS_5,1.0,en].png index 258ce3f3de..2cf5e925c9 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-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:dd034d439c08793e0dfd59f6bd5dcd88c06ded6cbe98172bf3cf296888e6d575 -size 51244 +oid sha256:e4360b6f5be55e5b468e735f83b6263ae3b0f1a3f7c0084e6dc3c07396723abd +size 51206 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_11,NEXUS_5,1.0,en].png index b2f16a9d23..d025eed585 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_11,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-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:d9d835cb1a420117b4d967181e2ca0fad71ba243d6a17bea08b82cae41f6b8e2 -size 68760 +oid sha256:0a58bb336bbf56b05584d73fa7fd3bd2e2fe97c82829a01988343d4c3b4c77ab +size 68727 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_12,NEXUS_5,1.0,en].png index 3bbd94a1c8..78e3c4cef7 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_12,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-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:4ad09278ae2ebb8171adba96d9f6e91d0cc4f120b0b2368087796dadb37eeb87 -size 58539 +oid sha256:ff2c7a701945b72afc6731c9da722e0bf178879f0bd80bc40da7ebb1d6e90d28 +size 58505 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_4,NEXUS_5,1.0,en].png index 4c991e3c30..76a8b6f2f9 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-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:082206122d4e6d9e6171b3b2444c576ff7bd47fb3946d8d98e2812654f39cd40 -size 73641 +oid sha256:4591b08ec297c0b024870e60ac18b03aadeaa8ff20a726dd991b18a80ec353ef +size 73607 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_5,NEXUS_5,1.0,en].png index 80eeb03c19..b76ddce111 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-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:dc695bfc589e8e5a852eeb9b2828ce968d17519bb5be957cf2734a7d6d9cc356 -size 89482 +oid sha256:6586d62bdbd3d4331105f561669c5383d578ad8af559270a26c7d9ff741b473b +size 89412 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_8,NEXUS_5,1.0,en].png index ef081b76b8..7aca1bcd17 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-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:9a243d53d10ca2eaa249b22af8a9fddf1a8ccf60db4f3ad9374e31cf494fe878 -size 54909 +oid sha256:8981ec8fd5e68e6e332f98ee2c95fc0f902b9b912a05201cad1c6a9feb5dbc89 +size 54868 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_9,NEXUS_5,1.0,en].png index 0b13a93b0c..2c64ab5210 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-D-2_3_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-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:3fdd478be89b47fcaf9725ca14c1e775087f1ccf2a266f3476a537bbc2b29922 -size 67476 +oid sha256:a5e787ce25a3542e398bec3aadd2b9b923c81dee4f3fbf8936c1c15e5aa1816b +size 67450 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_0,NEXUS_5,1.0,en].png index 9f5b7d48c6..cb3cb766a7 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-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:1a3b5bbcdd1593e81384b335045bdcb0b3e01782868993e9f6437a15ca39dbca -size 51380 +oid sha256:1a8af5ff868170d946d03db115271e7ea9c0a15db35bf6815bf95cc4e4f7fa81 +size 51357 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_1,NEXUS_5,1.0,en].png index ba9f8a8d63..5b168bd85f 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-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:7e26db0a0a8d767d6e732c1d2742cba3c3475d761b0c3017ff01cee7e3d362a5 -size 62773 +oid sha256:438bc82c007c9cd72228f47681981e833645c591c0a48e8a5cb70bd51721d35a +size 62756 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_10,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_10,NEXUS_5,1.0,en].png index 7d12d81b18..d2d115d1e0 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_10,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-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:3d5cae7d73178aecc12fbf5f1f27c9e66608b90d462deec862a881b403469e93 -size 49475 +oid sha256:a0926170ca4277e47f9ae49f5092c195cee2a84d67259ba8dc3aa3539d4faecf +size 49454 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_11,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_11,NEXUS_5,1.0,en].png index 44fd92b664..7af4192912 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_11,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-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:fe9456f4446104142221e28167a578a2b5cac772dc35aeb14352c0d422dd6fb0 -size 65726 +oid sha256:68ce3ada02e8ed6cb1e257e49d8c703bd0ff3401bf0e520fa617ed1ea91c6756 +size 65691 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_12,NEXUS_5,1.0,en].png index ee3a9ae3fe..2eeac61c7d 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_12,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-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:cad3b5ce023d890fd4a5ff0f5bbabb678bc6d5c76d3476e8053673c06f360c8b +oid sha256:c902787fb2d84a0e92832020cdd447fc039264fc5add63abea32241251c92bf7 size 56102 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_4,NEXUS_5,1.0,en].png index 4a58253b47..cb7e8bc36f 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-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:41a6c2dd81698696708802276b615a78ff22cc7cb8ae2d4b8d8cc862bfe44a24 -size 70856 +oid sha256:bc956e4fdab7bf15d71e10585947f9f5c24bc1d9e4eaa071de9b747fae879f53 +size 70883 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_5,NEXUS_5,1.0,en].png index fdb8345d8c..38441b5580 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-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:167f8368e0b94aa05e5d1cc30055e3b0c2c910752d719092ef2d34aae09dc832 -size 84649 +oid sha256:fdf1b66ffe0b7042356d22aa835ceea2c5b697c4ce62287b5ec040f528210216 +size 84635 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_8,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_8,NEXUS_5,1.0,en].png index 08c13f8f2a..01dfac9579 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_8,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-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:789ee5ccad8356198cdc0634b4e9a65ed44be2d26e7ce83a8662598c1bd8d4c2 -size 52765 +oid sha256:e438a2e542780851a7b570407e02f125e191d23b9c5299d9457baad78523de84 +size 52740 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_9,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_9,NEXUS_5,1.0,en].png index 300bcf7167..c72d23a518 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-N-2_4_null_9,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline_null_DefaultGroup_TimelineViewPreview-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:7b40a2e5d60a906d7c35c3ece1854671f5b319b00ad077322d094cbf906c07f7 -size 64803 +oid sha256:17f11b2cb93b8736a2d1224b7ad70c7e3a1a45bcb7e5ceb60475dba734e6efec +size 64787 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index 101f913aa8..ba94c22dd0 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c724bc77185a9ceec2cf092cb1d7865b13718d5320bd7ac4850bb85590f05b2 -size 52294 +oid sha256:de7f21b5fcc1235a80ea2e7f9e26a93503ea2d0c0793a89db8657979afb33a7d +size 52267 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png index cb28314caa..482ab3b775 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a303894134ed06348b609e41cf109dcafcd994c3dffabc6d9ab436fe92605245 -size 53710 +oid sha256:3d368f7cc84b5a8850577fac658aaeb89a1c83a2890b7fd393577c9cde919069 +size 53689 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png index 2ba437739f..33a7951011 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7523ec0d6defd7074af0c804fdde64fb8d421f6cfa729bc1c4f9858bd87c42d0 -size 52554 +oid sha256:56440b44cf610b0baaf7401bebb8ba2f4504b5dd36cb54fe1669d5deec6d1674 +size 52530 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png index 73715a33a2..01722cbae6 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f48089147f7e089abfa64254143a80857ccc9f840aa60403e1aabc67e2b6d51 -size 55458 +oid sha256:9387ab3e62a9e10721efdea3a6abcdf29db82a04ebdb3c4cefcad2a20ab9b9b8 +size 55386 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png index 9ce6f92309..f96cc5216e 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ff682ee8363d450bb76db72ea06deea87fa47692ce319b7dff315d2a10dfb6a -size 51033 +oid sha256:c0e3bd4e37bb665df997f189e5c2dc763c7f703b78384c63737675bd764fe7a8 +size 51174 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index 57eeca6786..9eeb92ff02 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e84edf8adf1a89153dd45272d36e04561d66f2ea765ad9edecfd8d750ba99f97 -size 54237 +oid sha256:ade57e690313eaaeddc7b21b9a2661026fca70e6af1b09aa331bba3d1b1bbaf3 +size 54240 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png index 8baf81c8b1..ad548c2fbe 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0bc9521bd1576d47ca6f643adf43ce3638d40328207d908ec863c72503d34f24 -size 55682 +oid sha256:89520fd2999582229ace8fd9304644fc74ce01230a4024b90b47ce1cd61eb564 +size 55678 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_2,NEXUS_5,1.0,en].png index 40900d7e92..78402d239a 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2209c3cc4e7de32ed92b3b0da4616b54d19091d43d21c0e4013728450d0d3f7 -size 54595 +oid sha256:19420aad469fad8a218a0a8cd0f7cfbb9593d7ceb799478a7ad9b84d9c24602f +size 54598 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_3,NEXUS_5,1.0,en].png index 5777c7f77e..6207b630da 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d7bdd0ca39534b31c9d421e28337712b1cf2aaf841a766fcc6bdaf996c756bfa -size 57524 +oid sha256:e224a21a1ae913066530e8be81760795b10be388cb7fa92fe399b1e11367d7af +size 57423 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_4,NEXUS_5,1.0,en].png index 2e2bfa89cb..9a56904200 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba349f81d5c417c612cae1263504ea5b4e83dc606ec20c3942368e8992b87ad4 -size 52886 +oid sha256:cac4b5bdb9f225c5313f36caef190e3f698b734db79707928572c942fed85603 +size 52960 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index 4fb8f691f4..597ebdcb61 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fb77df9e072715ed947537e4474d504b5b5d533bfe9cd888362a421fea5b53b5 -size 44068 +oid sha256:6c0a16524e1017274eeb0f07a50a47c6351af9a901355b54cccbc9ec799e79f4 +size 45174 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png index 4fb8f691f4..597ebdcb61 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fb77df9e072715ed947537e4474d504b5b5d533bfe9cd888362a421fea5b53b5 -size 44068 +oid sha256:6c0a16524e1017274eeb0f07a50a47c6351af9a901355b54cccbc9ec799e79f4 +size 45174 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index ff0fc9708e..e1ba6b44d4 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:004a25de04aac1ca9cbbad77581e0b52a6f8fba30aa2e64c03a791c54b297d5a -size 48875 +oid sha256:abb7854a1e47764a907434cdf32e138b95c947bab682da485eea86f9726cf3b9 +size 49946 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewLightPreview_0_null_1,NEXUS_5,1.0,en].png index ff0fc9708e..e1ba6b44d4 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.preferences.impl.developer_null_DefaultGroup_DeveloperSettingsViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:004a25de04aac1ca9cbbad77581e0b52a6f8fba30aa2e64c03a791c54b297d5a -size 48875 +oid sha256:abb7854a1e47764a907434cdf32e138b95c947bab682da485eea86f9726cf3b9 +size 49946 diff --git a/tools/detekt/detekt.yml b/tools/detekt/detekt.yml index a3bad54ab3..0123535b48 100644 --- a/tools/detekt/detekt.yml +++ b/tools/detekt/detekt.yml @@ -13,14 +13,22 @@ style: FunctionOnlyReturningConstant: active: false UnusedPrivateMember: - # TODO Enable it - active: false + active: true UnusedParameter: - # TODO Enable it - active: false + active: true + UnnecessaryInnerClass: + active: true + UnnecessaryLet: + active: true + UnnecessaryParentheses: + active: true + allowForUnclearPrecedence: false + UntilInsteadOfRangeTo: + active: true + UnusedImports: + active: true UnusedPrivateProperty: - # TODO Enable it - active: false + active: true ThrowsCount: active: false LoopWithTooManyJumpStatements: @@ -72,11 +80,9 @@ complexity: naming: VariableNaming: - # TODO Enable it - active: false + active: true TopLevelPropertyNaming: - # TODO Enable it - active: false + active: true FunctionNaming: active: true ignoreAnnotated: ['Composable'] @@ -150,8 +156,3 @@ Compose: active: true UnstableCollections: active: true - ViewModelForwarding: - ## TODO Set to true later - active: false - ViewModelInjection: - active: true diff --git a/tools/gitflow/gitflow-init.sh b/tools/gitflow/gitflow-init.sh new file mode 100755 index 0000000000..5068a37575 --- /dev/null +++ b/tools/gitflow/gitflow-init.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +# +# 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. + +git flow init -d +git config gitflow.prefix.versiontag v diff --git a/tools/github/download_github_artifacts.py b/tools/github/download_github_artifacts.py new file mode 100755 index 0000000000..892a4affa6 --- /dev/null +++ b/tools/github/download_github_artifacts.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +# +# Copyright 2022 New Vector Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import argparse +import hashlib +import json +import os +# Run `pip3 install requests` if not installed yet +import requests + +# This script downloads artifacts from GitHub. +# Ref: https://docs.github.com/en/rest/actions/artifacts#get-an-artifact + +error = False + +### Arguments + +parser = argparse.ArgumentParser(description='Download artifacts from GitHub.') +parser.add_argument('-t', + '--token', + required=True, + help='The GitHub token with read access.') +parser.add_argument('-a', + '--artifactUrl', + required=True, + help='the artifact_url from GitHub.') +parser.add_argument('-f', + '--filename', + help='the filename, if not provided, will use the artifact name.') +parser.add_argument('-i', + '--ignoreErrors', + help='Ignore errors that can be ignored. Build state and number of artifacts.', + action="store_true") +parser.add_argument('-d', + '--directory', + default="", + help='the target directory, where files will be downloaded. If not provided the build number will be used to create a directory.') +parser.add_argument('-v', + '--verbose', + help="increase output verbosity.", + action="store_true") +parser.add_argument('-s', + '--simulate', + help="simulate action, do not create folder or download any file.", + action="store_true") + +args = parser.parse_args() + +if args.verbose: + print("Argument:") + print(args) + +# Split the artifact URL to get information +# Ex: https://github.com/vector-im/element-android/suites/9293388174/artifacts/435942121 +artifactUrl = args.artifactUrl +if not artifactUrl.startswith('https://github.com/'): + print("❌ Invalid parameter --artifactUrl %s. Must start with 'https://github.com/'" % artifactUrl) + exit(1) +if "/artifacts/" not in artifactUrl: + print("❌ Invalid parameter --artifactUrl %s. Must contain '/artifacts/'" % artifactUrl) + exit(1) +artifactItems = artifactUrl.split("/") +if len(artifactItems) != 9: + print("❌ Invalid parameter --artifactUrl %s. Please check the format." % (artifactUrl)) + exit(1) + +gitHubRepoOwner = artifactItems[3] +gitHubRepo = artifactItems[4] +artifactId = artifactItems[8] + +if args.verbose: + print("gitHubRepoOwner: %s, gitHubRepo: %s, artifactId: %s" % (gitHubRepoOwner, gitHubRepo, artifactId)) + +headers = { + 'Authorization': "Bearer %s" % args.token, + 'Accept': 'application/vnd.github+json' +} +base_url = "https://api.github.com/repos/%s/%s/actions/artifacts/%s" % (gitHubRepoOwner, gitHubRepo, artifactId) + +### Fetch build state + +print("Getting artifacts data of project '%s/%s' artifactId '%s'..." % (gitHubRepoOwner, gitHubRepo, artifactId)) + +if args.verbose: + print("Url: %s" % base_url) + +r = requests.get(base_url, headers=headers) +data = json.loads(r.content.decode()) + +if args.verbose: + print("Json data:") + print(data) + +if args.verbose: + print("Create subfolder %s to download artifacts..." % artifactId) + +if args.directory == "": + targetDir = artifactId +else: + targetDir = args.directory + +if not args.simulate: + os.makedirs(targetDir, exist_ok=True) + +url = data.get("archive_download_url") +if args.filename is not None: + filename = args.filename +else: + filename = data.get("name") + ".zip" + +## Print some info about the artifact origin +commitLink = "https://github.com/%s/%s/commit/%s" % (gitHubRepoOwner, gitHubRepo, data.get("workflow_run").get("head_sha")) +print("Preparing to download artifact `%s`, built from branch: `%s` (commit %s)" % (data.get("name"), data.get("workflow_run").get("head_branch"), commitLink)) + +if args.verbose: + print() + print("Artifact url: %s" % url) + +target = targetDir + "/" + filename +sizeInBytes = data.get("size_in_bytes") +print("Downloading %s to '%s' (file size is %s bytes, this may take a while)..." % (filename, targetDir, sizeInBytes)) +if not args.simulate: + # open file to write in binary mode + with open(target, "wb") as file: + # get request + response = requests.get(url, headers=headers) + # write to file + file.write(response.content) + print("Verifying file size...") + # get the file size + size = os.path.getsize(target) + if sizeInBytes != size: + # error = True + print("Warning, file size mismatch: expecting %s and get %s. This is just a warning for now..." % (sizeInBytes, size)) + +if error: + print("❌ Error(s) occurred, please check the log") + exit(1) +else: + print("Done!") diff --git a/tools/quality/check.sh b/tools/quality/check.sh new file mode 100755 index 0000000000..c3f87b3018 --- /dev/null +++ b/tools/quality/check.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# +# Copyright 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. +# + +# List of tasks to run before creating a PR, to limit the risk of getting rejected by the CI. +# Can be used as a git hook if you want. + +# exit when any command fails +set -e + +# First run the quickest script +./tools/check/check_code_quality.sh + +# Build, test and check the project, with warning as errors +# It also check that the minimal app is compiling. +./gradlew check -PallWarningsAsErrors=true diff --git a/tools/release/release.sh b/tools/release/release.sh new file mode 100755 index 0000000000..9934f43610 --- /dev/null +++ b/tools/release/release.sh @@ -0,0 +1,320 @@ +#!/usr/bin/env bash + +# +# 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. +# + +# exit when any command fails +set -e + +printf "\n================================================================================\n" +printf "| Welcome to the release script! |\n" +printf "================================================================================\n" + +printf "Checking environment...\n" +envError=0 + +# Check that bundletool is installed +if ! command -v bundletool &> /dev/null +then + printf "Fatal: bundletool is not installed. You can install it running \`brew install bundletool\`\n" + envError=1 +fi + +# Path of the key store (it's a file) +keyStorePath="${ELEMENT_X_KEYSTORE_PATH}" +if [[ -z "${keyStorePath}" ]]; then + printf "Fatal: ELEMENT_X_KEYSTORE_PATH is not defined in the environment.\n" + envError=1 +fi +# Keystore password +keyStorePassword="${ELEMENT_X_KEYSTORE_PASSWORD}" +if [[ -z "${keyStorePassword}" ]]; then + printf "Fatal: ELEMENT_X_KEYSTORE_PASSWORD is not defined in the environment.\n" + envError=1 +fi +# Key password +keyPassword="${ELEMENT_X_KEY_PASSWORD}" +if [[ -z "${keyPassword}" ]]; then + printf "Fatal: ELEMENT_X_KEY_PASSWORD is not defined in the environment.\n" + envError=1 +fi +# GitHub token +gitHubToken="${ELEMENT_GITHUB_TOKEN}" +if [[ -z "${gitHubToken}" ]]; then + printf "Fatal: ELEMENT_GITHUB_TOKEN is not defined in the environment.\n" + envError=1 +fi +# Android home +androidHome="${ANDROID_HOME}" +if [[ -z "${androidHome}" ]]; then + printf "Fatal: ANDROID_HOME is not defined in the environment.\n" + envError=1 +fi +# @elementbot:matrix.org matrix token / Not mandatory +elementBotToken="${ELEMENT_BOT_MATRIX_TOKEN}" +if [[ -z "${elementBotToken}" ]]; then + printf "Warning: ELEMENT_BOT_MATRIX_TOKEN is not defined in the environment.\n" +fi + +if [ ${envError} == 1 ]; then + exit 1 +fi + +minSdkVersion=23 +buildToolsVersion="32.0.0" +buildToolsPath="${androidHome}/build-tools/${buildToolsVersion}" + +if [[ ! -d ${buildToolsPath} ]]; then + printf "Fatal: ${buildToolsPath} folder not found, ensure that you have installed the SDK version ${buildToolsVersion}.\n" + exit 1 +fi + +# Check if git flow is enabled +gitFlowDevelop=`git config gitflow.branch.develop` +if [[ ${gitFlowDevelop} != "" ]] +then + printf "Git flow is initialized\n" +else + printf "Git flow is not initialized. Initializing...\n" + ./tools/gitflow/gitflow-init.sh +fi + +printf "OK\n" + +printf "\n================================================================================\n" +printf "Ensuring main and develop branches are up to date...\n" + +git checkout main +git pull +git checkout develop +git pull + +printf "\n================================================================================\n" +# Guessing version to propose a default version +versionsFile="./plugins/src/main/kotlin/Versions.kt" +versionMajorCandidate=`grep "val versionMajor" ${versionsFile} | cut -d " " -f6` +versionMinorCandidate=`grep "val versionMinor" ${versionsFile} | cut -d " " -f6` +versionPatchCandidate=`grep "val versionPatch" ${versionsFile} | cut -d " " -f6` +versionCandidate="${versionMajorCandidate}.${versionMinorCandidate}.${versionPatchCandidate}" + +read -p "Please enter the release version (example: ${versionCandidate}). Just press enter if ${versionCandidate} is correct. " version +version=${version:-${versionCandidate}} + +# extract major, minor and patch for future use +versionMajor=`echo ${version} | cut -d "." -f1` +versionMinor=`echo ${version} | cut -d "." -f2` +versionPatch=`echo ${version} | cut -d "." -f3` +nextPatchVersion=$((versionPatch + 2)) + +printf "\n================================================================================\n" +printf "Starting the release ${version}\n" +git flow release start ${version} + +# Note: in case the release is already started and the script is started again, checkout the release branch again. +ret=$? +if [[ $ret -ne 0 ]]; then + printf "Mmh, it seems that the release is already started. Checking out the release branch...\n" + git checkout "release/${version}" +fi + +# Ensure version is OK +versionsFileBak="${versionsFile}.bak" +cp ${versionsFile} ${versionsFileBak} +sed "s/private const val versionMajor = .*/private const val versionMajor = ${versionMajor}/" ${versionsFileBak} > ${versionsFile} +sed "s/private const val versionMinor = .*/private const val versionMinor = ${versionMinor}/" ${versionsFile} > ${versionsFileBak} +sed "s/private const val versionPatch = .*/private const val versionPatch = ${versionPatch}/" ${versionsFileBak} > ${versionsFile} +rm ${versionsFileBak} + +# This commit may have no effect because generally we do not change the version during the release. +git commit -a -m "Setting version for the release ${version}" + +printf "\n================================================================================\n" +printf "Building the bundle locally first...\n" +./gradlew clean 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." + +# 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` + +printf "\n================================================================================\n" +printf "Committing...\n" +git commit -a -m "Changelog for version ${version}" + +printf "\n================================================================================\n" +printf "Creating fastlane file...\n" +printf -v versionMajor2Digits "%02d" ${versionMajor} +printf -v versionMinor2Digits "%02d" ${versionMinor} +printf -v versionPatch2Digits "%02d" ${versionPatch} +fastlaneFile="4${versionMajor2Digits}${versionMinor2Digits}${versionPatch2Digits}0.txt" +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." +git add ${fastlanePathFile} +git commit -a -m "Adding fastlane file for version ${version}" + +printf "\n================================================================================\n" +printf "OK, finishing the release...\n" +git flow release finish "${version}" + +printf "\n================================================================================\n" +read -p "Done, push the branch 'main' and the new tag (yes/no) default to yes? " doPush +doPush=${doPush:-yes} + +if [ ${doPush} == "yes" ]; then + printf "Pushing branch 'main' and tag 'v${version}'...\n" + git push origin main + git push origin "v${version}" +else + printf "Not pushing, do not forget to push manually!\n" +fi + +printf "\n================================================================================\n" +printf "Checking out develop...\n" +git checkout develop + +# Set next version +printf "\n================================================================================\n" +printf "Setting next version on file '${versionsFile}'...\n" +cp ${versionsFile} ${versionsFileBak} +sed "s/private const val versionPatch = .*/private const val versionPatch = ${nextPatchVersion}/" ${versionsFileBak} > ${versionsFile} +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." + +printf "Committing...\n" +git commit -a -m 'version++' + +printf "\n================================================================================\n" +read -p "Done, push the branch 'develop' (yes/no) default to yes? (A rebase may be necessary in case develop got new commits) " doPush +doPush=${doPush:-yes} + +if [ ${doPush} == "yes" ]; then + printf "Pushing branch 'develop'...\n" + git push origin develop +else + printf "Not pushing, do not forget to push manually!\n" +fi + +printf "\n================================================================================\n" +printf "Wait for the GitHub action https://github.com/vector-im/element-x-android/actions/workflows/release.yml?query=branch%%3Amain to build the 'main' branch.\n" +read -p "After GHA is finished, please enter the artifact URL (for 'elementx-app-bundle-unsigned'): " artifactUrl + +printf "\n================================================================================\n" +printf "Downloading the artifact...\n" + + # Download files +targetPath="./tmp/Element/${version}" + +python3 ./tools/github/download_github_artifacts.py \ + --token ${gitHubToken} \ + --artifactUrl ${artifactUrl} \ + --directory ${targetPath} \ + --ignoreErrors + +printf "\n================================================================================\n" +printf "Unzipping the artifact...\n" + +unzip ${targetPath}/elementx-app-bundle-unsigned.zip -d ${targetPath} + +unsignedBundlePath="${targetPath}/app-release.aab" +signedBundlePath="${targetPath}/app-release-signed.aab" + +printf "\n================================================================================\n" +printf "Signing file ${unsignedBundlePath} with build-tools version ${buildToolsVersion} for min SDK version ${minSdkVersion}...\n" + +cp ${unsignedBundlePath} ${signedBundlePath} + +${buildToolsPath}/apksigner sign \ + -v \ + --ks ${keyStorePath} \ + --ks-pass pass:${keyStorePassword} \ + --ks-key-alias elementx \ + --key-pass pass:${keyPassword} \ + --min-sdk-version ${minSdkVersion} \ + ${signedBundlePath} + +printf "\n================================================================================\n" +printf "Please check the information below:\n" + +printf "Version code: " +bundletool dump manifest --bundle=${signedBundlePath} --xpath=/manifest/@android:versionCode +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." + +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} + +if [ ${doDeploy} == "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." +else + printf "Apk will not be deployed!\n" +fi + +printf "\n================================================================================\n" +githubCreateReleaseLink="https://github.com/vector-im/element-x-android/releases/new?tag=v${version}&title=Element%20X%20Android%20v${version}&body=${changelogUrlEncoded}" +printf "Creating the release on gitHub.\n" +printf -- "Open this link: %s\n" ${githubCreateReleaseLink} +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. " + +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!" +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 " +else + read -p "Send this message to the room (yes/no) default to yes? " doSend + doSend=${doSend:-yes} + if [ ${doSend} == "yes" ]; then + printf "Sending message...\n" + transactionId=`openssl rand -hex 16` + # Element Android internal + matrixRoomId="!LiSLXinTDCsepePiYW:matrix.org" + curl -X PUT --data $"{\"msgtype\":\"m.text\",\"body\":\"${message}\"}" -H "Authorization: Bearer ${elementBotToken}" https://matrix-client.matrix.org/_matrix/client/r0/rooms/${matrixRoomId}/send/m.room.message/\$local.${transactionId} + else + printf "Message not sent, please send it manually!\n" + fi +fi + +printf "\n================================================================================\n" +printf "Congratulation! Kudos for using this script! Have a nice day!\n" +printf "================================================================================\n"