diff --git a/.editorconfig b/.editorconfig index 2ab3cbeae7..a41ddcad7f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,7 +14,7 @@ ij_smart_tabs = false ij_visual_guides = none ij_wrap_on_typing = false -# Ktlint rule, for more information see https://pinterest.github.io/ktlint/1.1.1/faq/#how-do-i-enable-or-disable-a-rule +# Ktlint rule, for more information see https://pinterest.github.io/ktlint/latest/faq/#how-do-i-enable-or-disable-a-rule ktlint_standard_wrapping = disabled ktlint_standard_trailing-comma-on-call-site = disabled ktlint_standard_trailing-comma-on-declaration-site = disabled @@ -26,7 +26,16 @@ ktlint_standard_annotation = disabled ktlint_standard_parameter-list-wrapping = disabled ktlint_standard_indent = disabled ktlint_standard_blank-line-before-declaration = disabled -ktlint_function_naming_ignore_when_annotated_with=Composable +ktlint_function_naming_ignore_when_annotated_with = Composable +# Added when upgrading to 1.7.1 +ktlint_standard_function-expression-body = disabled +ktlint_standard_chain-method-continuation = disabled +ktlint_standard_class-signature = disabled +# Added when upgrading to 1.8.0 +ktlint_standard_when-entry-bracing = disabled +ktlint_standard_blank-line-between-when-conditions = disabled +ktlint_standard_mixed-condition-operators = disabled +ktlint_standard_no-unused-imports = enabled [*.java] ij_java_align_consecutive_assignments = false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fb14c3cee6..aa62b83e36 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,7 @@ jobs: group: ${{ github.ref == 'refs/heads/develop' && format('build-develop-{0}-{1}', matrix.variant, github.sha) || format('build-{0}-{1}', matrix.variant, github.ref) }} cancel-in-progress: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 @@ -46,6 +46,7 @@ jobs: ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }} ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }} ELEMENT_ANDROID_SENTRY_DSN: ${{ secrets.ELEMENT_ANDROID_SENTRY_DSN }} + ELEMENT_SDK_SENTRY_DSN: ${{ secrets.ELEMENT_SDK_SENTRY_DSN }} ELEMENT_CALL_SENTRY_DSN: ${{ secrets.ELEMENT_CALL_SENTRY_DSN }} ELEMENT_CALL_POSTHOG_API_HOST: ${{ secrets.ELEMENT_CALL_POSTHOG_API_HOST }} ELEMENT_CALL_POSTHOG_API_KEY: ${{ secrets.ELEMENT_CALL_POSTHOG_API_KEY }} @@ -53,7 +54,7 @@ jobs: run: ./gradlew :app:assembleGplayDebug app:assembleFDroidDebug -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES - name: Upload debug APKs if: ${{ matrix.variant == 'debug' }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: elementx-debug path: | @@ -61,7 +62,7 @@ jobs: app/build/outputs/apk/fdroid/debug/*-universal-debug.apk - name: Upload x86_64 APK for Maestro if: ${{ matrix.variant == 'debug' }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: elementx-apk-maestro path: | @@ -69,7 +70,7 @@ jobs: retention-days: 5 overwrite: true if-no-files-found: error - - uses: rnkdsh/action-upload-diawi@26292a7b424bdc9f4ab4ccea6202fc513f571370 # v1.5.11 + - uses: rnkdsh/action-upload-diawi@4e1421305be7cfc510d05f47850262eeaf345108 # v1.5.12 id: diawi # Do not fail the whole build if Diawi upload fails continue-on-error: true diff --git a/.github/workflows/build_enterprise.yml b/.github/workflows/build_enterprise.yml index 0d9b5949cc..1d416ffe42 100644 --- a/.github/workflows/build_enterprise.yml +++ b/.github/workflows/build_enterprise.yml @@ -27,7 +27,7 @@ jobs: group: ${{ github.ref == 'refs/heads/develop' && format('build-develop-enterprise-{0}-{1}', matrix.variant, github.sha) || format('build-enterprise-{0}-{1}', matrix.variant, github.ref) }} cancel-in-progress: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 @@ -54,6 +54,7 @@ jobs: ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }} ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }} ELEMENT_ANDROID_SENTRY_DSN: ${{ secrets.ELEMENT_ANDROID_SENTRY_DSN }} + ELEMENT_SDK_SENTRY_DSN: ${{ secrets.ELEMENT_SDK_SENTRY_DSN }} ELEMENT_CALL_SENTRY_DSN: ${{ secrets.ELEMENT_CALL_SENTRY_DSN }} ELEMENT_CALL_POSTHOG_API_HOST: ${{ secrets.ELEMENT_CALL_POSTHOG_API_HOST }} ELEMENT_CALL_POSTHOG_API_KEY: ${{ secrets.ELEMENT_CALL_POSTHOG_API_KEY }} @@ -61,7 +62,7 @@ jobs: run: ./gradlew :app:assembleGplayDebug -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES - name: Upload debug Enterprise APKs if: ${{ matrix.variant == 'debug' }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: elementx-enterprise-debug path: | diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index 0868b0729f..f7c8fccc62 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -9,7 +9,7 @@ jobs: # Skip in forks, it doesn't work even with the fallback token if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Add SSH private keys for submodule repositories uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1 with: @@ -20,7 +20,7 @@ jobs: - run: | npm install --save-dev @babel/plugin-transform-flow-strip-types - name: Danger - uses: danger/danger-js@bdccecb77e0144055fbaea9224f10cf8b1229b68 # 13.0.4 + uses: danger/danger-js@67ed2c1f42fd2fc198cc3c14b43c8f83351f4fe9 # 13.0.5 with: args: "--dangerfile ./tools/danger/dangerfile.js" env: diff --git a/.github/workflows/generate_github_pages.yml b/.github/workflows/generate_github_pages.yml index 0d03d1a928..008ac299c7 100644 --- a/.github/workflows/generate_github_pages.yml +++ b/.github/workflows/generate_github_pages.yml @@ -25,7 +25,7 @@ jobs: - name: Set up Python 3.12 uses: actions/setup-python@v6 with: - python-version: 3.13 + python-version: 3.14 - name: Run World screenshots generation script run: | ./tools/test/generateWorldScreenshots.py diff --git a/.github/workflows/gradle-wrapper-update.yml b/.github/workflows/gradle-wrapper-update.yml index f826bb8205..3b0fac5309 100644 --- a/.github/workflows/gradle-wrapper-update.yml +++ b/.github/workflows/gradle-wrapper-update.yml @@ -11,7 +11,7 @@ jobs: # Skip in forks if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-java@v5 name: Use JDK 21 if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch' diff --git a/.github/workflows/maestro-local.yml b/.github/workflows/maestro-local.yml index 7481bec0ba..4fd42534b7 100644 --- a/.github/workflows/maestro-local.yml +++ b/.github/workflows/maestro-local.yml @@ -23,7 +23,7 @@ jobs: group: ${{ format('maestro-{0}', github.ref) }} cancel-in-progress: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 @@ -44,7 +44,7 @@ jobs: ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }} ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }} - name: Upload APK as artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: elementx-apk-maestro path: | @@ -62,14 +62,14 @@ jobs: group: ${{ format('maestro-{0}', github.ref) }} cancel-in-progress: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch' with: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 ref: ${{ github.ref }} - name: Download APK artifact from previous job - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v7 with: name: elementx-apk-maestro - name: Enable KVM group perms @@ -102,7 +102,7 @@ jobs: script: | .github/workflows/scripts/maestro/maestro-local-with-screen-recording.sh app-gplay-x86_64-debug.apk - name: Upload test results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: test-results path: | diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index b494d9fe8c..0ea049b2f0 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest if: ${{ github.repository == 'element-hq/element-x-android' }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Use JDK 21 uses: actions/setup-java@v5 with: @@ -30,6 +30,7 @@ jobs: ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }} ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }} ELEMENT_ANDROID_SENTRY_DSN: ${{ secrets.ELEMENT_ANDROID_SENTRY_DSN }} + ELEMENT_SDK_SENTRY_DSN: ${{ secrets.ELEMENT_SDK_SENTRY_DSN }} ELEMENT_CALL_SENTRY_DSN: ${{ secrets.ELEMENT_CALL_SENTRY_DSN }} ELEMENT_CALL_POSTHOG_API_HOST: ${{ secrets.ELEMENT_CALL_POSTHOG_API_HOST }} ELEMENT_CALL_POSTHOG_API_KEY: ${{ secrets.ELEMENT_CALL_POSTHOG_API_KEY }} diff --git a/.github/workflows/nightlyReports.yml b/.github/workflows/nightlyReports.yml index 537565743e..b7119c7036 100644 --- a/.github/workflows/nightlyReports.yml +++ b/.github/workflows/nightlyReports.yml @@ -42,7 +42,7 @@ jobs: - name: ✅ Upload kover report if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: kover-results path: | @@ -60,7 +60,7 @@ jobs: name: Dependency analysis runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Use JDK 21 uses: actions/setup-java@v5 with: @@ -74,7 +74,7 @@ jobs: run: ./gradlew dependencyCheckAnalyze $CI_GRADLE_ARG_PROPERTIES - name: Upload dependency analysis if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: dependency-analysis path: build/reports/dependency-check-report.html diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 773bc02d93..2084c7648a 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -17,7 +17,7 @@ jobs: name: Search for forbidden patterns runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Add SSH private keys for submodule repositories uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1 if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} @@ -33,11 +33,11 @@ jobs: name: Search for invalid screenshot files runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up Python 3.12 uses: actions/setup-python@v6 with: - python-version: 3.13 + python-version: 3.14 - name: Search for invalid screenshot files run: ./tools/test/checkInvalidScreenshots.py @@ -45,7 +45,7 @@ jobs: name: Search for invalid dependencies runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Use JDK 21 uses: actions/setup-java@v5 with: @@ -58,7 +58,7 @@ jobs: - name: Set up Python 3.12 uses: actions/setup-python@v6 with: - python-version: 3.13 + python-version: 3.14 - name: Search for invalid dependencies run: ./tools/dependencies/checkDependencies.py @@ -71,7 +71,7 @@ jobs: group: ${{ github.ref == 'refs/heads/main' && format('check-konsist-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-konsist-develop-{0}', github.sha) || format('check-konsist-{0}', github.ref) }} cancel-in-progress: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 @@ -97,12 +97,45 @@ jobs: run: ./gradlew :tests:konsist:testDebugUnitTest $CI_GRADLE_ARG_PROPERTIES --no-daemon - name: Upload reports if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: konsist-report path: | **/build/reports/**/*.* + compose: + name: Compose tests + runs-on: ubuntu-latest + # Allow all jobs on main and develop. Just one per PR. + concurrency: + group: ${{ github.ref == 'refs/heads/main' && format('check-compose-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-compose-develop-{0}', github.sha) || format('check-compose-{0}', github.ref) }} + cancel-in-progress: true + steps: + - uses: actions/checkout@v6 + with: + # Ensure we are building the branch and not the branch after being merged on develop + # https://github.com/actions/checkout/issues/881 + ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }} + - name: Add SSH private keys for submodule repositories + uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1 + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} + with: + ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }} + - name: Clone submodules + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} + run: git submodule update --init --recursive + - name: Use JDK 21 + uses: actions/setup-java@v5 + with: + distribution: 'temurin' # See 'Supported distributions' for available options + java-version: '21' + - name: Configure gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-read-only: ${{ github.ref != 'refs/heads/develop' }} + - name: Run compose tests + run: ./tools/compose/check_stability.sh + lint: name: Android lint check runs-on: ubuntu-latest @@ -111,7 +144,7 @@ jobs: group: ${{ github.ref == 'refs/heads/main' && format('check-lint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-lint-develop-{0}', github.sha) || format('check-lint-{0}', github.ref) }} cancel-in-progress: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 @@ -141,7 +174,7 @@ jobs: run: ./gradlew :app:lintGplayDebug :app:lintFdroidDebug lintDebug $CI_GRADLE_ARG_PROPERTIES --continue - name: Upload reports if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: linting-report path: | @@ -155,7 +188,7 @@ jobs: group: ${{ github.ref == 'refs/heads/main' && format('check-detekt-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-detekt-develop-{0}', github.sha) || format('check-detekt-{0}', github.ref) }} cancel-in-progress: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 @@ -181,7 +214,7 @@ jobs: run: ./gradlew detekt $CI_GRADLE_ARG_PROPERTIES --no-daemon - name: Upload reports if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: detekt-report path: | @@ -195,7 +228,7 @@ jobs: group: ${{ github.ref == 'refs/heads/main' && format('check-ktlint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-ktlint-develop-{0}', github.sha) || format('check-ktlint-{0}', github.ref) }} cancel-in-progress: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 @@ -221,7 +254,7 @@ jobs: run: ./gradlew ktlintCheck $CI_GRADLE_ARG_PROPERTIES - name: Upload reports if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: ktlint-report path: | @@ -235,7 +268,7 @@ jobs: group: ${{ github.ref == 'refs/heads/main' && format('check-knit-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-knit-develop-{0}', github.sha) || format('check-knit-{0}', github.ref) }} cancel-in-progress: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 @@ -266,7 +299,7 @@ jobs: name: Check shell scripts runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Run shellcheck uses: ludeeus/action-shellcheck@2.0.0 with: @@ -278,13 +311,13 @@ jobs: needs: [konsist, lint, ktlint, detekt] if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }} - name: Download reports from previous jobs - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v7 - name: Prepare Danger if: always() run: | @@ -293,7 +326,7 @@ jobs: yarn add danger-plugin-lint-report --dev - name: Danger lint if: always() - uses: danger/danger-js@bdccecb77e0144055fbaea9224f10cf8b1229b68 # 13.0.4 + uses: danger/danger-js@67ed2c1f42fd2fc198cc3c14b43c8f83351f4fe9 # 13.0.5 with: args: "--dangerfile ./tools/danger/dangerfile-lint.js" env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2cce85bd5a..439b97ba8c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: group: ${{ format('build-release-main-gplay-{0}', github.sha) }} cancel-in-progress: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Use JDK 21 uses: actions/setup-java@v5 with: @@ -32,13 +32,14 @@ jobs: ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }} ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }} ELEMENT_ANDROID_SENTRY_DSN: ${{ secrets.ELEMENT_ANDROID_SENTRY_DSN }} + ELEMENT_SDK_SENTRY_DSN: ${{ secrets.ELEMENT_SDK_SENTRY_DSN }} ELEMENT_CALL_SENTRY_DSN: ${{ secrets.ELEMENT_CALL_SENTRY_DSN }} ELEMENT_CALL_POSTHOG_API_HOST: ${{ secrets.ELEMENT_CALL_POSTHOG_API_HOST }} ELEMENT_CALL_POSTHOG_API_KEY: ${{ secrets.ELEMENT_CALL_POSTHOG_API_KEY }} ELEMENT_CALL_RAGESHAKE_URL: ${{ secrets.ELEMENT_CALL_RAGESHAKE_URL }} run: ./gradlew bundleGplayRelease $CI_GRADLE_ARG_PROPERTIES - name: Upload bundle as artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: elementx-app-gplay-bundle-unsigned path: | @@ -52,7 +53,7 @@ jobs: group: ${{ format('build-release-main-enterprise-{0}', github.sha) }} cancel-in-progress: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Add SSH private keys for submodule repositories uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1 if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} @@ -74,7 +75,7 @@ jobs: ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }} run: ./gradlew bundleGplayRelease $CI_GRADLE_ARG_PROPERTIES - name: Upload bundle as artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: elementx-enterprise-app-gplay-bundle-unsigned path: | @@ -87,7 +88,7 @@ jobs: group: ${{ format('build-release-main-fdroid-{0}', github.sha) }} cancel-in-progress: true steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Use JDK 21 uses: actions/setup-java@v5 with: @@ -102,7 +103,7 @@ jobs: ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }} run: ./gradlew assembleFdroidRelease $CI_GRADLE_ARG_PROPERTIES - name: Upload apks as artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: elementx-app-fdroid-apks-unsigned path: | diff --git a/.github/workflows/scripts/maestro/local-recording.sh b/.github/workflows/scripts/maestro/local-recording.sh index 6534d2feca..adc83f4876 100755 --- a/.github/workflows/scripts/maestro/local-recording.sh +++ b/.github/workflows/scripts/maestro/local-recording.sh @@ -1,9 +1,10 @@ #!/bin/sh # +# Copyright (c) 2025 Element Creations Ltd. # Copyright 2024 New Vector Ltd. # -# SPDX-License-Identifier: AGPL-3.0-only +# SPDX-License-Identifier: AGPL-3.0-only. # Please see LICENSE in the repository root for full details. # diff --git a/.github/workflows/scripts/maestro/maestro-local-with-screen-recording.sh b/.github/workflows/scripts/maestro/maestro-local-with-screen-recording.sh index b94958dba4..a9f789a4f5 100755 --- a/.github/workflows/scripts/maestro/maestro-local-with-screen-recording.sh +++ b/.github/workflows/scripts/maestro/maestro-local-with-screen-recording.sh @@ -1,9 +1,10 @@ #!/bin/sh # +# Copyright (c) 2025 Element Creations Ltd. # Copyright 2024 New Vector Ltd. # -# SPDX-License-Identifier: AGPL-3.0-only +# SPDX-License-Identifier: AGPL-3.0-only. # Please see LICENSE in the repository root for full details. # diff --git a/.github/workflows/scripts/recordScreenshots.sh b/.github/workflows/scripts/recordScreenshots.sh index 19469e69e3..d29353a2c2 100755 --- a/.github/workflows/scripts/recordScreenshots.sh +++ b/.github/workflows/scripts/recordScreenshots.sh @@ -1,8 +1,9 @@ #!/bin/bash +# Copyright (c) 2025 Element Creations Ltd. # Copyright 2023-2024 New Vector Ltd. # -# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. # Please see LICENSE files in the repository root for full details. set -e diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index acfe6ba87f..c289102116 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -22,7 +22,7 @@ jobs: group: ${{ format('sonar-{0}', github.ref) }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop' }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 diff --git a/.github/workflows/sync-localazy.yml b/.github/workflows/sync-localazy.yml index 7740af4064..2086eab3aa 100644 --- a/.github/workflows/sync-localazy.yml +++ b/.github/workflows/sync-localazy.yml @@ -11,7 +11,7 @@ jobs: # Skip in forks if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Use JDK 21 uses: actions/setup-java@v5 with: @@ -24,7 +24,7 @@ jobs: - name: Set up Python 3.12 uses: actions/setup-python@v6 with: - python-version: 3.13 + python-version: 3.14 - name: Setup Localazy run: | curl -sS https://dist.localazy.com/debian/pubkey.gpg | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/localazy.gpg @@ -36,7 +36,7 @@ jobs: ./tools/localazy/importSupportedLocalesFromLocalazy.py ./tools/test/generateAllScreenshots.py - name: Create Pull Request for Strings - uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 + uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0 with: token: ${{ secrets.DANGER_GITHUB_API_TOKEN }} commit-message: Sync Strings from Localazy diff --git a/.github/workflows/sync-sas-strings.yml b/.github/workflows/sync-sas-strings.yml index 84b0cb521c..2f5f22a2d5 100644 --- a/.github/workflows/sync-sas-strings.yml +++ b/.github/workflows/sync-sas-strings.yml @@ -12,18 +12,18 @@ jobs: if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} # No concurrency required, runs every time on a schedule. steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up Python 3.12 uses: actions/setup-python@v6 with: - python-version: 3.13 + python-version: 3.14 - name: Install Prerequisite dependencies run: | pip install requests - name: Run SAS String script run: ./tools/sas/import_sas_strings.py - name: Create Pull Request for SAS Strings - uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 + uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0 with: commit-message: Sync SAS Strings title: Sync SAS Strings diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 22c302cbb3..aa11a433f4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -61,7 +61,7 @@ jobs: - name: 🚫 Upload kover failed coverage reports if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: kover-error-report path: | @@ -73,7 +73,7 @@ jobs: - name: 🚫 Upload test results on error if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: tests-and-screenshot-tests-results path: | @@ -83,7 +83,7 @@ jobs: # https://github.com/codecov/codecov-action - name: ☂️ Upload coverage reports to codecov - uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index e1b1c5c8ab..77acaadb20 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +# Copyright (c) 2025 Element Creations Ltd. +# +# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +# Please see LICENSE files in the repository root for full details. + # Built application files *.apk *.ap_ @@ -50,6 +55,7 @@ captures/ .idea/deviceManager.xml .idea/gradle.xml .idea/jarRepositories.xml +.idea/markdown.xml .idea/misc.xml .idea/modules.xml # Comment next line if keeping position of elements in Navigation Editor is relevant for you diff --git a/.idea/copyright/Element_Enterprise.xml b/.idea/copyright/Element_Enterprise.xml index e8c3019732..b556049368 100644 --- a/.idea/copyright/Element_Enterprise.xml +++ b/.idea/copyright/Element_Enterprise.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/copyright/Element_FOSS.xml b/.idea/copyright/Element_FOSS.xml index 930b4d9f5b..bfc80c44e1 100644 --- a/.idea/copyright/Element_FOSS.xml +++ b/.idea/copyright/Element_FOSS.xml @@ -1,6 +1,6 @@ - diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 3efb2d8dd4..dbbf81b44b 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - - \ No newline at end of file + diff --git a/CHANGES.md b/CHANGES.md index cbc1db0032..47ea7ec332 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,290 @@ +Changes in Element X v25.12.0 +============================= + + + +## What's Changed +### ✨ Features +* Room list: enable latest event sorter. by @bmarty in https://github.com/element-hq/element-x-android/pull/5825 +* Add room list indicators about last message by @bmarty in https://github.com/element-hq/element-x-android/pull/5824 +### 🙌 Improvements +* Change : improve room and space member list by @ganfra in https://github.com/element-hq/element-x-android/pull/5806 +* Change : security and privacy rework by @ganfra in https://github.com/element-hq/element-x-android/pull/5816 +### 🐛 Bugfixes +* Ensure confirmation dialog is displayed when an admin add other admin to a room by @bmarty in https://github.com/element-hq/element-x-android/pull/5786 +* Edit user profile cancel confirmation by @bmarty in https://github.com/element-hq/element-x-android/pull/5788 +* Fix editing owner by @bmarty in https://github.com/element-hq/element-x-android/pull/5807 +* Uris should take precedence in plain text intents by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5785 +* Fix long voice recording by @bmarty in https://github.com/element-hq/element-x-android/pull/5821 +### 🗣 Translations +* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/5792 +* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/5830 +### 🧱 Build +* Use regex to check forbidden terms by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5784 +* Update Gradle Wrapper from 8.14.3 to 9.2.1 by @ElementBot in https://github.com/element-hq/element-x-android/pull/5751 +### Dependency upgrades +* fix(deps): update dependency androidx.sqlite:sqlite-ktx to v2.6.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5769 +* fix(deps): update datastore to v1.2.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5789 +* chore(deps): update peter-evans/create-pull-request action to v7.0.9 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5793 +* fix(deps): update dependency io.nlopez.compose.rules:detekt to v0.4.28 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5795 +* fix(deps): update metro to v0.7.7 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5771 +* chore(deps): update plugin sonarqube to v7.1.0.6387 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5783 +* fix(deps): update dependency io.github.sergio-sastre.composablepreviewscanner:android to v0.7.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5799 +* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.11.24 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5796 +* fix(deps): update dependency io.sentry:sentry-android to v8.27.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5803 +* fix(deps): update dependency io.element.android:emojibase-bindings to v1.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5801 +* fix(deps): update roborazzi to v1.52.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5804 +* fix(deps): update dependency org.maplibre.gl:android-sdk to v12.2.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5814 +* chore(deps): update actions/checkout action to v6 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5805 +* fix(deps): update dependency com.google.testparameterinjector:test-parameter-injector to v1.20 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5800 +* fix(deps): update android.gradle.plugin to v8.13.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5260 +* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.11.26 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5818 +* fix(deps): update dependencyanalysis to v3.5.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5819 +* fix(deps): update dependency com.posthog:posthog-android to v3.27.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5834 +* fix(deps): update dependency io.element.android:element-call-embedded to v0.16.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5839 +* Upgrade the Rust SDK to `v25.12.2` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5838 +### Others +* misc : use newLatestEvent api from sdk by @ganfra in https://github.com/element-hq/element-x-android/pull/5809 +* Inject RoomMemberListDataSource in the presenter constructor. by @bmarty in https://github.com/element-hq/element-x-android/pull/5822 +* Add more performance checks by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5767 +* Load `JoinedRoom` in home screen, pass it to the room flow by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5817 +* Revert "fix(deps): update dependency com.posthog:posthog-android to v3.27.0" by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5836 + + +**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.11.3...v25.12.0 + +Changes in Element X v25.11.3 +============================= + + + +## What's Changed +### 🙌 Improvements +* Improve rendering notification for multi account by @bmarty in https://github.com/element-hq/element-x-android/pull/5645 +* Change : roles and permissions by @ganfra in https://github.com/element-hq/element-x-android/pull/5685 +* Improve account provider selection during the login flow by @bmarty in https://github.com/element-hq/element-x-android/pull/5692 +* Let notifications use avatar fallback. by @bmarty in https://github.com/element-hq/element-x-android/pull/5721 +* Changes : member list improvements by @ganfra in https://github.com/element-hq/element-x-android/pull/5728 +### 🐛 Bugfixes +* Do not use the bestDescription but the caption for images, when available by @bmarty in https://github.com/element-hq/element-x-android/pull/5684 +* Add the user certificate if any when creating Matrix Client. by @bmarty in https://github.com/element-hq/element-x-android/pull/5686 +* Ensure the form data are not lost when opening the log viewer. by @bmarty in https://github.com/element-hq/element-x-android/pull/5695 +* Fix password flow when using a login link by @bmarty in https://github.com/element-hq/element-x-android/pull/5693 +* Fix layout issue in text composer by @bmarty in https://github.com/element-hq/element-x-android/pull/5710 +* Fix navigation stack overflow when sharing media by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5724 +* Notification robustness by @bmarty in https://github.com/element-hq/element-x-android/pull/5726 +* Send read receipts using the current timeline, not the live timeline by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5731 +* Render Owner in the horizontal list when editing Admins. by @bmarty in https://github.com/element-hq/element-x-android/pull/5736 +* Stop overriding the homeserver when restoring a `Client` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5753 +* Revert "Stop overriding the homeserver when restoring a `Client`" by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5754 +* Try fixing forced dark mode issues on MIUI on Android 10 by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5708 +* Fix crash at startup by @bmarty in https://github.com/element-hq/element-x-android/pull/5761 +* Fix null pointer exception on room notification settings. by @bmarty in https://github.com/element-hq/element-x-android/pull/5758 +* Fix crash when viewing Pinned events by @bmarty in https://github.com/element-hq/element-x-android/pull/5764 +* Fix crash when pressing back from the showkase Activity by @bmarty in https://github.com/element-hq/element-x-android/pull/5772 +* Fix navigation issue once incoming share is handled by @bmarty in https://github.com/element-hq/element-x-android/pull/5773 +* Fix crash in work manager by @bmarty in https://github.com/element-hq/element-x-android/pull/5768 +### 🗣 Translations +* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/5704 +* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/5747 +* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/5782 +### 🧱 Build +* Module cleanup by @bmarty in https://github.com/element-hq/element-x-android/pull/5722 +* Add `NIGHTLY` env for Sentry by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5779 +### 🚧 In development 🚧 +* Space : prepare Space Settings screen by @ganfra in https://github.com/element-hq/element-x-android/pull/5668 +### Dependency upgrades +* fix(deps): update dependency androidx.core:core-splashscreen to v1.2.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5687 +* fix(deps): update dependency com.posthog:posthog-android to v3.26.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5696 +* fix(deps): update metro to v0.7.5 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5697 +* Update dependency org.matrix.rustcomponents:sdk-android to v25.11.11 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5716 +* Update plugin ktlint to v14 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5713 +* Update plugin dependencycheck to v12.1.9 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5717 +* Update dependency org.maplibre.gl:android-sdk to v12.1.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5714 +* Update dependency io.sentry:sentry-android to v8.26.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5720 +* Update sqldelight to v2.2.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5730 +* fix(deps): update dependency com.squareup.okhttp3:okhttp-bom to v5.3.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5746 +* fix(deps): update dependency com.google.firebase:firebase-bom to v34.6.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5737 +* fix(deps): update metro to v0.7.6 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5752 +* fix(deps): update dependency org.maplibre.gl:android-sdk to v12.1.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5743 +* Update dependency com.squareup.okhttp3:okhttp-bom to v5.3.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5757 +* fix(deps): update dependency com.pinterest.ktlint:ktlint-cli to v1.8.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5738 +* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.11.19 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5762 +* fix(deps): update dependencyanalysis to v3.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5776 +### Others +* Extract save change dialog by @bmarty in https://github.com/element-hq/element-x-android/pull/5679 +* Use the dedicated subdomain for the bug report URL by default by @benbz in https://github.com/element-hq/element-x-android/pull/5689 +* Convert `ComposerAlertMolecule` to use alert levels. by @kaylendog in https://github.com/element-hq/element-x-android/pull/5691 +* Improve composer alert molecule by @bmarty in https://github.com/element-hq/element-x-android/pull/5701 +* Code consistency around view event handling by @bmarty in https://github.com/element-hq/element-x-android/pull/5698 +* Update copyright holders by @bmarty in https://github.com/element-hq/element-x-android/pull/5706 +* Fix rendering notifications after receiving redundant push by @SpiritCroc in https://github.com/element-hq/element-x-android/pull/5711 +* Fix push gateway with some push provider (Sunup/autopush) by @p1gp1g in https://github.com/element-hq/element-x-android/pull/5741 +* Use new notification sound in release. by @bmarty in https://github.com/element-hq/element-x-android/pull/5748 +* Fix issue on brand color override by @bmarty in https://github.com/element-hq/element-x-android/pull/5626 +* Add media retention policy by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5749 +* Enable logging OkHttp traffic based on the current log level by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5750 +* Remove unused `slidingSyncProxy` from DB. by @bmarty in https://github.com/element-hq/element-x-android/pull/5755 +* Add some performance metrics for Sentry by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5760 + +## New Contributors +* @benbz made their first contribution in https://github.com/element-hq/element-x-android/pull/5689 +* @kaylendog made their first contribution in https://github.com/element-hq/element-x-android/pull/5691 + +**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.11.2...v25.11.3 + +Changes in Element X v25.11.2 +============================= + + + +## What's Changed +### ✨ Features +* Enable access to security and privacy by @bmarty in https://github.com/element-hq/element-x-android/pull/5566 +* Add ability to forward a media from the media viewer and the gallery by @bmarty in https://github.com/element-hq/element-x-android/pull/5622 +* Split notifications for messages in threads by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5595 +### 🙌 Improvements +* Enable `SyncNotificationsWithWorkManager` in nightly and debug builds by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5573 +* Confirm exit without saving change in room details edit screen by @bmarty in https://github.com/element-hq/element-x-android/pull/5618 +* Space : add view members entry by @ganfra in https://github.com/element-hq/element-x-android/pull/5619 +* Update notification sound by @bmarty in https://github.com/element-hq/element-x-android/pull/5667 +* Use the new notification sound only on debug and nightly build by @bmarty in https://github.com/element-hq/element-x-android/pull/5673 +* Make sure we know the session verification state before showing the options to verify the session by @bmarty in https://github.com/element-hq/element-x-android/pull/5677 +### 🐛 Bugfixes +* Improve how brand color is applied. by @bmarty in https://github.com/element-hq/element-x-android/pull/5584 +* Improve wellknown retrieval API by @bmarty in https://github.com/element-hq/element-x-android/pull/5587 +* Clearing the room list search clears the search term too by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5603 +* Delete pin code only when the last session is deleted by @bmarty in https://github.com/element-hq/element-x-android/pull/5600 +* Fix issues with WorkManager on Android 12 and below by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5606 +* Fix marking a room as read re-instantiates its timeline by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5628 +* Display only valid emojis in recent emoji list by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5612 +* Fix navigation issue. by @bmarty in https://github.com/element-hq/element-x-android/pull/5666 +* Fix forward events from media viewer from pinned media timeline by @bmarty in https://github.com/element-hq/element-x-android/pull/5669 +* Try fixing 'Timeline Event object has already been destroyed' by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5675 +* Use the SDK Client to check whether a homeserver is compatible by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5664 +### 🗣 Translations +* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/5610 +* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/5662 +### 🧱 Build +* Remove `@Inject`, not necessary anymore when class is annotated with `@ContributesBinding` by @bmarty in https://github.com/element-hq/element-x-android/pull/5589 +* Upgrade ktlint to 1.7.1 and ensure Renovate will upgrade the version by @bmarty in https://github.com/element-hq/element-x-android/pull/5638 +* Improve architecture around Nodes by @bmarty in https://github.com/element-hq/element-x-android/pull/5641 +* Move dependencies block out of the android block. by @bmarty in https://github.com/element-hq/element-x-android/pull/5674 +* Always use the handleEvent(s) function the same way. by @bmarty in https://github.com/element-hq/element-x-android/pull/5672 +### Dependency upgrades +* fix(deps): update metro to v0.7.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5576 +* fix(deps): update dependencyanalysis to v3.2.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5577 +* fix(deps): update dependency io.sentry:sentry-android to v8.24.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5586 +* fix(deps): update dependency androidx.work:work-runtime-ktx to v2.11.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5590 +* fix(deps): update dependency com.posthog:posthog-android to v3.25.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5594 +* fix(deps): update dependency com.google.crypto.tink:tink-android to v1.19.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5572 +* Update plugin sonarqube to v7.0.1.6134 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5605 +* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.10.28 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5620 +* fix(deps): update dependencyanalysis to v3.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5602 +* fix(deps): update dependency com.github.matrix-org:matrix-analytics-events to v0.29.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5621 +* fix(deps): update dependencyanalysis to v3.4.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5624 +* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.10.29 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5625 +* fix(deps): update dependency io.sentry:sentry-android to v8.25.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5629 +* fix(deps): update dependencyanalysis to v3.4.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5642 +* fix(deps): update dependency com.squareup.okhttp3:okhttp-bom to v5.3.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5644 +* chore(deps): update danger/danger-js action to v13.0.5 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5652 +* fix(deps): update dependency com.google.firebase:firebase-bom to v34.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5643 +* fix(deps): update firebaseappdistribution to v5.2.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5640 +* fix(deps): update metro to v0.7.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5663 +* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.10.31 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5657 +* Update GitHub Artifact Actions (major) by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5609 +* Update dependency io.element.android:element-call-embedded to v0.16.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5598 +* Update roborazzi to v1.51.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5676 +* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.11.4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5681 +* fix(deps): update metro to v0.7.4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5683 +### Others +* Improve code around Element .well-known configuration by @bmarty in https://github.com/element-hq/element-x-android/pull/5565 +* misc: display offline banner for all LoggedIn screens by @ganfra in https://github.com/element-hq/element-x-android/pull/5574 +* Remove icon preview duplicate by @bmarty in https://github.com/element-hq/element-x-android/pull/5588 +* Remove application navigation state usage in the push module by @bmarty in https://github.com/element-hq/element-x-android/pull/5596 +* Design : update Home TopBar and RoomList Filters by @ganfra in https://github.com/element-hq/element-x-android/pull/5599 +* Add missing tests on the analytic modules by @bmarty in https://github.com/element-hq/element-x-android/pull/5604 +* design(space): let SpaceRoomItemView divider be full width by @ganfra in https://github.com/element-hq/element-x-android/pull/5597 +* Update notification style by @bmarty in https://github.com/element-hq/element-x-android/pull/5607 +* Improve how data is handled for the WorkManager. by @bmarty in https://github.com/element-hq/element-x-android/pull/5592 +* Revert "Make sure declining a call stops observing the ringing call state" by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5615 +* Misc : space flow inject room by @ganfra in https://github.com/element-hq/element-x-android/pull/5614 +* Enable `SyncNotificationsWithWorkManager` by default in release mode apps too by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5646 +* Revert "Update notification sound" by @bmarty in https://github.com/element-hq/element-x-android/pull/5671 +* Introduce new query to count accounts by @bmarty in https://github.com/element-hq/element-x-android/pull/5678 + + +**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.11.0...v25.11.2 + +Changes in Element X v25.11.0 +============================= + +Hotfix release. + +Includes https://github.com/element-hq/element-x-android/pull/5615, which fixes an issue that prevented Element Call notifications from being displayed sometimes. + +**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.10.1...v25.11.0 + +Changes in Element X v25.10.1 +============================= + + + +## What's Changed +### ✨ Features +* Sync notifications using WorkManager by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5545 +### 🙌 Improvements +* Sort feature flags by @bmarty in https://github.com/element-hq/element-x-android/pull/5557 +### 🐛 Bugfixes +* Makes sure images are loaded when cancelling multiaccount flow by @ganfra in https://github.com/element-hq/element-x-android/pull/5502 +* Fix 'test push loop back' notification check by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5541 +* Display 'join anyway' button on room preview when the state can't be loaded by @ShadowRZ in https://github.com/element-hq/element-x-android/pull/5514 +* Fix media viewer not being dismissed with reduced motion enabled by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5555 +* Keep the cursor position in room list search when going back by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5570 +* Make sure declining a call stops observing the ringing call state by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5563 +### 🗣 Translations +* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/5515 +* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/5562 +### 🧱 Build +* Do some cleanup on our immutable annotation usage by @bmarty in https://github.com/element-hq/element-x-android/pull/5503 +* `interface TestParameterValuesProvider` is deprecated. by @bmarty in https://github.com/element-hq/element-x-android/pull/5568 +### Dependency upgrades +* fix(deps): update metro to v0.6.9 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5480 +* fix(deps): update dependency org.unifiedpush.android:connector to v3.1.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5443 +* fix(deps): update wysiwyg to v2.40.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5400 +* fix(deps): update dependency io.github.sergio-sastre.composablepreviewscanner:android to v0.7.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5510 +* fix(deps): update camera to v1.5.1 - autoclosed by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5509 +* chore(deps): update plugin dependencycheck to v12.1.7 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5518 +* chore(deps): update plugin licensee to v1.14.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5477 +* chore(deps): update dependency python to 3.14 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5475 +* fix(deps): update metro to v0.6.10 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5520 +* fix(deps): update dependency org.unifiedpush.android:connector to v3.1.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5519 +* chore(deps): update plugin gms_google_services to v4.4.4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5507 +* fix(deps): update dependency com.google.firebase:firebase-bom to v34.4.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5522 +* fix(deps): update dependency com.squareup.okhttp3:okhttp-bom to v5.2.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5524 +* fix(deps): update dependency net.zetetic:sqlcipher-android to v4.11.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5525 +* fix(deps): update dependencyanalysis to v3.1.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5523 +* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.10.13 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5527 +* chore(deps): update plugin dependencycheck to v12.1.8 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5531 +* chore(deps): update rnkdsh/action-upload-diawi action to v1.5.12 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5533 +* fix(deps): update dependency org.maplibre.gl:android-sdk to v12.0.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5548 +* fix(deps): update metro to v0.7.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5554 +* fix(deps): update dependency com.posthog:posthog-android to v3.24.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5564 +* chore(deps): update plugin sonarqube to v7 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5535 +### Others +* Import Compound tokens - fixed icons by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5506 +* Replace Uri by String in States that are used in Composable function. by @bmarty in https://github.com/element-hq/element-x-android/pull/5508 +* Let room filters follow the design. by @bmarty in https://github.com/element-hq/element-x-android/pull/5526 +* Allow uploading notification push rules in bug reports by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5538 +* Add number of accounts info in the rageshake data. by @bmarty in https://github.com/element-hq/element-x-android/pull/5532 +* design(space): match figma for Space views by @ganfra in https://github.com/element-hq/element-x-android/pull/5540 +* Extract console message logger and mutualize instance of Json by @bmarty in https://github.com/element-hq/element-x-android/pull/5552 +* Improve colors customization by @bmarty in https://github.com/element-hq/element-x-android/pull/5542 +* Fix test warning by @bmarty in https://github.com/element-hq/element-x-android/pull/5558 + + +**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.10.0...v25.10.1 + Changes in Element X v25.10.0 ============================= diff --git a/README.md b/README.md index 95e8b219f5..5406b158c9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=element-x-android&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=element-x-android) [![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=element-x-android&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=element-x-android) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=element-x-android&metric=bugs)](https://sonarcloud.io/summary/new_code?id=element-x-android) -[![codecov](https://codecov.io/github/element-hq/element-x-android/branch/develop/graph/badge.svg?token=ecwvia7amV)](https://codecov.io/github/vector-im/element-x-android) +[![codecov](https://codecov.io/github/element-hq/element-x-android/branch/develop/graph/badge.svg?token=ecwvia7amV)](https://codecov.io/github/element-hq/element-x-android) [![Element X Android Matrix room #element-x-android:matrix.org](https://img.shields.io/matrix/element-x-android:matrix.org.svg?label=%23element-x-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-x-android:matrix.org) [![Localazy](https://img.shields.io/endpoint?url=https%3A%2F%2Fconnect.localazy.com%2Fstatus%2Felement%2Fdata%3Fcontent%3Dall%26title%3Dlocalazy%26logo%3Dtrue)](https://localazy.com/p/element) @@ -102,9 +102,10 @@ If after your research you still have a question, ask at [#element-x-android:mat ## Copyright and License -Copyright (c) 2022 - 2025 New Vector Ltd +Copyright (c) 2025 Element Creations Ltd. +Copyright (c) 2022 - 2025 New Vector Ltd. -This software is dual licensed by New Vector Ltd (Element). It can be used either: +This software is dual licensed by Element Creations Ltd (Element). It can be used either: (1) for free under the terms of the GNU Affero General Public License (as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version); OR diff --git a/annotations/build.gradle.kts b/annotations/build.gradle.kts index 00d0735292..33e3cbeff2 100644 --- a/annotations/build.gradle.kts +++ b/annotations/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/annotations/src/main/kotlin/io/element/android/annotations/ContributesNode.kt b/annotations/src/main/kotlin/io/element/android/annotations/ContributesNode.kt index 06c5749736..632bdc3e81 100644 --- a/annotations/src/main/kotlin/io/element/android/annotations/ContributesNode.kt +++ b/annotations/src/main/kotlin/io/element/android/annotations/ContributesNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9be152e9e7..322ad47911 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -128,12 +129,12 @@ android { ) signingConfig = signingConfigs.getByName("debug") - postprocessing { - isRemoveUnusedCode = true - isObfuscate = false - isOptimizeCode = true - isRemoveUnusedResources = true - proguardFiles("proguard-rules.pro") + optimization { + enable = true + keepRules { + files.add(File(projectDir, "proguard-rules.pro")) + files.add(getDefaultProguardFile("proguard-android-optimize.txt")) + } } } @@ -151,10 +152,6 @@ android { matchingFallbacks += listOf("release") signingConfig = signingConfigs.getByName("nightly") - postprocessing { - initWith(release.postprocessing) - } - firebaseAppDistribution { artifactType = "APK" // We upload the universal APK to fix this error: @@ -197,6 +194,16 @@ android { buildConfigFieldStr("FLAVOR_DESCRIPTION", "FDroid") } } + + packaging { + resources.pickFirsts += setOf( + "META-INF/versions/9/OSGI-INF/MANIFEST.MF", + ) + + jniLibs { + useLegacyPackaging = project.findProperty("useLegacyPackaging")?.toString()?.toBoolean() + } + } } androidComponents { @@ -318,6 +325,7 @@ licensee { allowUrl("https://jsoup.org/license") allowUrl("https://asm.ow2.io/license.html") allowUrl("https://www.gnu.org/licenses/agpl-3.0.txt") + allowUrl("https://github.com/mhssn95/compose-color-picker/blob/main/LICENSE") ignoreDependencies("com.github.matrix-org", "matrix-analytics-events") // Ignore dependency that are not third-party licenses to us. ignoreDependencies(groupId = "io.element.android") @@ -333,7 +341,7 @@ fun Project.configureLicensesTasks(reportingExtension: ReportingExtension) { it.toString() } } - val artifactsFile = reportingExtension.file("licensee/android$capitalizedVariantName/artifacts.json") + val artifactsFile = reportingExtension.baseDirectory.file("licensee/android$capitalizedVariantName/artifacts.json") val copyArtifactsTask = project.tasks.register("copy${capitalizedVariantName}LicenseeReportToAssets") { diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index c55b1bf1c2..9207e06edc 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -41,8 +41,6 @@ static int windowAttachCount(android.view.View); } --keep class io.element.android.x.di.** { *; } - # Keep LogSessionId class and related classes (https://github.com/androidx/media/issues/2535) -keep class android.media.metrics.LogSessionId { *; } @@ -51,3 +49,27 @@ # Keep Media3 classes that use reflection (https://github.com/androidx/media/issues/2535) -keep class androidx.media3.** { *; } -dontwarn android.media.metrics.** + +# New rules after AGP 8.13.1 upgrade +-dontwarn androidx.window.extensions.WindowExtensions +-dontwarn androidx.window.extensions.WindowExtensionsProvider +-dontwarn androidx.window.extensions.area.ExtensionWindowAreaPresentation +-dontwarn androidx.window.extensions.layout.DisplayFeature +-dontwarn androidx.window.extensions.layout.FoldingFeature +-dontwarn androidx.window.extensions.layout.WindowLayoutComponent +-dontwarn androidx.window.extensions.layout.WindowLayoutInfo +-dontwarn androidx.window.sidecar.SidecarDeviceState +-dontwarn androidx.window.sidecar.SidecarDisplayFeature +-dontwarn androidx.window.sidecar.SidecarInterface$SidecarCallback +-dontwarn androidx.window.sidecar.SidecarInterface +-dontwarn androidx.window.sidecar.SidecarProvider +-dontwarn androidx.window.sidecar.SidecarWindowLayoutInfo + +# Also needed after AGP 8.13.1 upgrade, it seems like proguard is now more aggressive on removing unused code +-keep class org.matrix.rustcomponents.sdk.** { *;} +-keep class uniffi.** { *;} +-keep class io.element.android.x.di.** { *; } +-keepclasseswithmembernames,allowoptimization,allowshrinking class io.element.android.** { *; } + +# Keep Metro classes +-keep class dev.zacsweers.metro.** { *; } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 26e8c7dc23..628c428790 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,8 @@ + android:exported="false" + tools:node="merge"> + + + diff --git a/app/src/main/res/resources.properties b/app/src/main/res/resources.properties index b55c556736..8cbf8eaf6a 100644 --- a/app/src/main/res/resources.properties +++ b/app/src/main/res/resources.properties @@ -1,6 +1,7 @@ +# Copyright (c) 2025 Element Creations Ltd. # Copyright 2024 New Vector Ltd. # -# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. # Please see LICENSE files in the repository root for full details. unqualifiedResLocale=en diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index c4014b842c..af159a0b11 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,7 +1,8 @@ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index f61fb33868..d666caddea 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,7 +1,8 @@ diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 76fb597109..4190fd84f2 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,14 +1,17 @@ - + - diff --git a/app/src/main/res/xml/automotive_app_desc.xml b/app/src/main/res/xml/automotive_app_desc.xml index 745752ed0b..c466692855 100644 --- a/app/src/main/res/xml/automotive_app_desc.xml +++ b/app/src/main/res/xml/automotive_app_desc.xml @@ -1,7 +1,8 @@ diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml index 3602e38d2d..06db4ae4cb 100644 --- a/app/src/main/res/xml/backup_rules.xml +++ b/app/src/main/res/xml/backup_rules.xml @@ -1,7 +1,8 @@ diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml index 72a7d32cc0..a171646bad 100644 --- a/app/src/main/res/xml/locales_config.xml +++ b/app/src/main/res/xml/locales_config.xml @@ -9,13 +9,13 @@ - + diff --git a/app/src/test/kotlin/io/element/android/x/intent/DefaultIntentProviderTest.kt b/app/src/test/kotlin/io/element/android/x/intent/DefaultIntentProviderTest.kt index 9d6d9d4320..4598a74e51 100644 --- a/app/src/test/kotlin/io/element/android/x/intent/DefaultIntentProviderTest.kt +++ b/app/src/test/kotlin/io/element/android/x/intent/DefaultIntentProviderTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,9 +14,11 @@ import android.content.Context import android.content.Intent import com.google.common.truth.Truth.assertThat import io.element.android.libraries.deeplink.api.DeepLinkCreator +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.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_THREAD_ID @@ -31,14 +34,15 @@ import org.robolectric.RuntimeEnvironment class DefaultIntentProviderTest { @Test fun `test getViewRoomIntent with data`() { - val deepLinkCreator = lambdaRecorder { _, _, _ -> "deepLinkCreatorResult" } + val deepLinkCreator = lambdaRecorder { _, _, _, _ -> "deepLinkCreatorResult" } val sut = createDefaultIntentProvider( - deepLinkCreator = { sessionId, roomId, threadId -> deepLinkCreator.invoke(sessionId, roomId, threadId) }, + deepLinkCreator = { sessionId, roomId, threadId, eventId -> deepLinkCreator.invoke(sessionId, roomId, threadId, eventId) }, ) val result = sut.getViewRoomIntent( sessionId = A_SESSION_ID, roomId = A_ROOM_ID, threadId = A_THREAD_ID, + eventId = AN_EVENT_ID, ) result.commonAssertions() assertThat(result.data.toString()).isEqualTo("deepLinkCreatorResult") @@ -46,11 +50,12 @@ class DefaultIntentProviderTest { value(A_SESSION_ID), value(A_ROOM_ID), value(A_THREAD_ID), + value(AN_EVENT_ID), ) } private fun createDefaultIntentProvider( - deepLinkCreator: DeepLinkCreator = DeepLinkCreator { _, _, _ -> "" }, + deepLinkCreator: DeepLinkCreator = DeepLinkCreator { _, _, _, _ -> "" }, ): DefaultIntentProvider { return DefaultIntentProvider( context = RuntimeEnvironment.getApplication() as Context, diff --git a/app/src/test/kotlin/io/element/android/x/oidc/DefaultOidcRedirectUrlProviderTest.kt b/app/src/test/kotlin/io/element/android/x/oidc/DefaultOidcRedirectUrlProviderTest.kt index 5a0df57bda..18567355d2 100644 --- a/app/src/test/kotlin/io/element/android/x/oidc/DefaultOidcRedirectUrlProviderTest.kt +++ b/app/src/test/kotlin/io/element/android/x/oidc/DefaultOidcRedirectUrlProviderTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appconfig/build.gradle.kts b/appconfig/build.gradle.kts index 15b8bd51ba..45496acb77 100644 --- a/appconfig/build.gradle.kts +++ b/appconfig/build.gradle.kts @@ -2,9 +2,10 @@ import config.BuildTimeConfig import extension.buildConfigFieldStr /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { @@ -32,7 +33,7 @@ android { value = if (isEnterpriseBuild) { BuildTimeConfig.BUG_REPORT_URL ?: "" } else { - "https://riot.im/bugreports/submit" + "https://rageshakes.element.io/api/submit" }, ) buildConfigFieldStr( diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/AnalyticsConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/AnalyticsConfig.kt index 346fce4725..e32e482842 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/AnalyticsConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/AnalyticsConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/ApplicationConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/ApplicationConfig.kt index eeeb52f095..e1ae6896bb 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/ApplicationConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/ApplicationConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/AuthenticationConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/AuthenticationConfig.kt index 5061290948..7432a6904f 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/AuthenticationConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/AuthenticationConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/ElementCallConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/ElementCallConfig.kt index ca9a6391f2..ea4c2640fe 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/ElementCallConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/ElementCallConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/LearnMoreConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/LearnMoreConfig.kt index 9dee73fc94..855586892a 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/LearnMoreConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/LearnMoreConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,4 +13,5 @@ object LearnMoreConfig { const val DEVICE_VERIFICATION_URL: String = "https://element.io/help#encryption-device-verification" const val SECURE_BACKUP_URL: String = "https://element.io/help#encryption5" const val IDENTITY_CHANGE_URL: String = "https://element.io/help#encryption18" + const val HISTORY_VISIBLE_URL: String = "https://element.io/en/help#e2ee-history-sharing" } diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt index c3dbe2bea5..f2ef0bc42f 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/LockScreenConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/MatrixConfiguration.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/MatrixConfiguration.kt index a8f424bd08..76b96bf398 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/MatrixConfiguration.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/MatrixConfiguration.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/MessageComposerConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/MessageComposerConfig.kt index 68e5a8b6ad..f3893f0f61 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/MessageComposerConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/MessageComposerConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/NotificationConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/NotificationConfig.kt index 41acf5d266..cac2f8a8ed 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/NotificationConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/NotificationConfig.kt @@ -1,14 +1,15 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.appconfig -import android.graphics.Color import androidx.annotation.ColorInt +import androidx.core.graphics.toColorInt object NotificationConfig { /** @@ -27,5 +28,5 @@ object NotificationConfig { const val SHOW_QUICK_REPLY_ACTION = true @ColorInt - val NOTIFICATION_ACCENT_COLOR: Int = Color.parseColor("#FF0DBD8B") + val NOTIFICATION_ACCENT_COLOR: Int = "#FF0DBD8B".toColorInt() } diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt index b7a057dcee..c59f5d19fe 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/PushConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/PushConfig.kt index 506b17f7a8..d36fb742b0 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/PushConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/PushConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt index 1dd482cc40..1f6609ecc3 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/RageshakeConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/RoomListConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/RoomListConfig.kt index dd2a22a5ab..7369f1393c 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/RoomListConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/RoomListConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt index 7e38ec8092..d4fe7d1fc5 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/TimelineConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,19 +17,19 @@ object TimelineConfig { * Event types that will be filtered out from the timeline (i.e. not displayed). */ val excludedEvents = listOf( - StateEventType.CALL_MEMBER, - StateEventType.ROOM_ALIASES, - StateEventType.ROOM_CANONICAL_ALIAS, - StateEventType.ROOM_GUEST_ACCESS, - StateEventType.ROOM_HISTORY_VISIBILITY, - StateEventType.ROOM_JOIN_RULES, - StateEventType.ROOM_POWER_LEVELS, - StateEventType.ROOM_SERVER_ACL, - StateEventType.ROOM_TOMBSTONE, - StateEventType.SPACE_CHILD, - StateEventType.SPACE_PARENT, - StateEventType.POLICY_RULE_ROOM, - StateEventType.POLICY_RULE_SERVER, - StateEventType.POLICY_RULE_USER, + StateEventType.CallMember, + StateEventType.RoomAliases, + StateEventType.RoomCanonicalAlias, + StateEventType.RoomGuestAccess, + StateEventType.RoomHistoryVisibility, + StateEventType.RoomJoinRules, + StateEventType.RoomPowerLevels, + StateEventType.RoomServerAcl, + StateEventType.RoomTombstone, + StateEventType.SpaceChild, + StateEventType.SpaceParent, + StateEventType.PolicyRuleRoom, + StateEventType.PolicyRuleServer, + StateEventType.PolicyRuleUser, ) } diff --git a/appconfig/src/main/kotlin/io/element/android/appconfig/VoiceMessageConfig.kt b/appconfig/src/main/kotlin/io/element/android/appconfig/VoiceMessageConfig.kt index ca2b99e633..b77b2dc113 100644 --- a/appconfig/src/main/kotlin/io/element/android/appconfig/VoiceMessageConfig.kt +++ b/appconfig/src/main/kotlin/io/element/android/appconfig/VoiceMessageConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appicon/element/build.gradle.kts b/appicon/element/build.gradle.kts index fd5a2d74ca..23c21d4863 100644 --- a/appicon/element/build.gradle.kts +++ b/appicon/element/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/appicon/element/src/main/kotlin/io/element/android/appicon/element/IconPreview.kt b/appicon/element/src/main/kotlin/io/element/android/appicon/element/IconPreview.kt index c25675bffc..bffcdaecb3 100644 --- a/appicon/element/src/main/kotlin/io/element/android/appicon/element/IconPreview.kt +++ b/appicon/element/src/main/kotlin/io/element/android/appicon/element/IconPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appicon/element/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/appicon/element/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 777a587a50..9de193bab3 100644 --- a/appicon/element/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/appicon/element/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,7 +1,8 @@ diff --git a/appicon/element/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/appicon/element/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 777a587a50..9de193bab3 100644 --- a/appicon/element/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/appicon/element/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,7 +1,8 @@ diff --git a/appicon/enterprise/build.gradle.kts b/appicon/enterprise/build.gradle.kts index 462397b5b2..bc5cdea0b8 100644 --- a/appicon/enterprise/build.gradle.kts +++ b/appicon/enterprise/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/appicon/enterprise/src/main/kotlin/io/element/android/appicon/enterprise/IconPreview.kt b/appicon/enterprise/src/main/kotlin/io/element/android/appicon/enterprise/IconPreview.kt index b3f96785f4..70e0201218 100644 --- a/appicon/enterprise/src/main/kotlin/io/element/android/appicon/enterprise/IconPreview.kt +++ b/appicon/enterprise/src/main/kotlin/io/element/android/appicon/enterprise/IconPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appicon/enterprise/src/main/res/mipmap-anydpi/ic_launcher.xml b/appicon/enterprise/src/main/res/mipmap-anydpi/ic_launcher.xml index 4806e4bffd..38ef234b87 100644 --- a/appicon/enterprise/src/main/res/mipmap-anydpi/ic_launcher.xml +++ b/appicon/enterprise/src/main/res/mipmap-anydpi/ic_launcher.xml @@ -1,7 +1,8 @@ diff --git a/appicon/enterprise/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/appicon/enterprise/src/main/res/mipmap-anydpi/ic_launcher_round.xml index 11c51fdf6a..0fd0f91c19 100644 --- a/appicon/enterprise/src/main/res/mipmap-anydpi/ic_launcher_round.xml +++ b/appicon/enterprise/src/main/res/mipmap-anydpi/ic_launcher_round.xml @@ -1,7 +1,8 @@ diff --git a/appnav/build.gradle.kts b/appnav/build.gradle.kts index 063e26b9da..84d614b3d9 100644 --- a/appnav/build.gradle.kts +++ b/appnav/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -38,6 +39,7 @@ dependencies { implementation(projects.libraries.pushproviders.api) implementation(projects.libraries.designsystem) implementation(projects.libraries.matrixui) + implementation(projects.libraries.matrixmedia.api) implementation(projects.libraries.uiCommon) implementation(projects.libraries.uiStrings) implementation(projects.features.login.api) @@ -46,6 +48,7 @@ dependencies { implementation(projects.features.announcement.api) implementation(projects.features.ftue.api) + implementation(projects.features.linknewdevice.api) implementation(projects.features.share.api) implementation(projects.services.apperror.impl) @@ -59,8 +62,10 @@ dependencies { testImplementation(projects.libraries.preferences.test) testImplementation(projects.libraries.push.test) testImplementation(projects.libraries.pushproviders.test) + testImplementation(projects.features.forward.test) testImplementation(projects.features.networkmonitor.test) testImplementation(projects.features.rageshake.test) + testImplementation(projects.services.appnavstate.impl) testImplementation(projects.services.appnavstate.test) testImplementation(projects.services.analytics.test) testImplementation(projects.services.toolbox.test) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt b/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt index 2f1e4e75ea..1185016c0b 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/BackstackExt.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt index 9ae5aa38fe..0dbcb0f3f1 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -21,13 +22,13 @@ import com.bumble.appyx.core.navigation.model.permanent.PermanentNavModel import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.ParentNode import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.appnav.di.SessionGraphFactory import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.DependencyInjectionGraphOwner @@ -56,10 +57,12 @@ class LoggedInAppScopeFlowNode( plugins = plugins ), DependencyInjectionGraphOwner { interface Callback : Plugin { - fun onOpenBugReport() - fun onAddAccount() + fun navigateToBugReport() + fun navigateToAddAccount() } + private val callback: Callback = callback() + @Parcelize object NavTarget : Parcelable @@ -81,12 +84,12 @@ class LoggedInAppScopeFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { val callback = object : LoggedInFlowNode.Callback { - override fun onOpenBugReport() { - plugins().forEach { it.onOpenBugReport() } + override fun navigateToBugReport() { + callback.navigateToBugReport() } - override fun onAddAccount() { - plugins().forEach { it.onAddAccount() } + override fun navigateToAddAccount() { + callback.navigateToAddAccount() } } return createNode(buildContext, listOf(callback)) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt index c557d6e1c2..cee2296d43 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index b0fc707d07..92c3cfe3dd 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,6 +14,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.lifecycle.lifecycleScope import com.bumble.appyx.core.composable.PermanentChild @@ -23,7 +25,6 @@ import com.bumble.appyx.core.navigation.NavKey import com.bumble.appyx.core.navigation.model.permanent.PermanentNavModel import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.BackStack.State.ACTIVE import com.bumble.appyx.navmodel.backstack.BackStack.State.CREATED @@ -38,7 +39,6 @@ import com.bumble.appyx.navmodel.backstack.operation.replace import com.bumble.appyx.navmodel.backstack.operation.singleTop import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject -import im.vector.app.features.analytics.plan.JoinedRoom import io.element.android.annotations.ContributesNode import io.element.android.appnav.loggedin.LoggedInNode import io.element.android.appnav.loggedin.MediaPreviewConfigMigration @@ -46,26 +46,33 @@ import io.element.android.appnav.loggedin.SendQueues import io.element.android.appnav.room.RoomFlowNode import io.element.android.appnav.room.RoomNavigationTarget import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode +import io.element.android.compound.colors.SemanticColorsLightDark +import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.api.SessionEnterpriseService import io.element.android.features.ftue.api.FtueEntryPoint import io.element.android.features.ftue.api.state.FtueService import io.element.android.features.ftue.api.state.FtueState import io.element.android.features.home.api.HomeEntryPoint +import io.element.android.features.linknewdevice.api.LinkNewDeviceEntryPoint import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkStatus +import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorContainer import io.element.android.features.preferences.api.PreferencesEntryPoint import io.element.android.features.roomdirectory.api.RoomDescription import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint import io.element.android.features.securebackup.api.SecureBackupEntryPoint import io.element.android.features.share.api.ShareEntryPoint -import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.features.startchat.api.StartChatEntryPoint import io.element.android.features.userprofile.api.UserProfileEntryPoint import io.element.android.features.verifysession.api.IncomingVerificationEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.architecture.waitForChildAttached import io.element.android.libraries.architecture.waitForNavTargetAttached +import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.designsystem.theme.ElementThemeApp import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.annotations.SessionCoroutineScope @@ -77,10 +84,16 @@ import io.element.android.libraries.matrix.api.core.RoomIdOrAlias import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.api.permalink.PermalinkData +import io.element.android.libraries.matrix.api.room.JoinedRoom +import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener import io.element.android.libraries.matrix.api.verification.VerificationRequest +import io.element.android.libraries.preferences.api.store.AppPreferencesStore import io.element.android.libraries.push.api.notifications.conversations.NotificationConversationService import io.element.android.libraries.ui.common.nodes.emptyNode +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.api.watchers.AnalyticsRoomListStateWatcher import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first @@ -97,6 +110,7 @@ import java.util.UUID import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds import kotlin.time.toKotlinDuration +import im.vector.app.features.analytics.plan.JoinedRoom as JoinedRoomAnalyticsEvent @ContributesNode(SessionScope::class) @AssistedInject @@ -110,6 +124,7 @@ class LoggedInFlowNode( private val secureBackupEntryPoint: SecureBackupEntryPoint, private val userProfileEntryPoint: UserProfileEntryPoint, private val ftueEntryPoint: FtueEntryPoint, + private val linkNewDeviceEntryPoint: LinkNewDeviceEntryPoint, @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, private val ftueService: FtueService, @@ -122,7 +137,13 @@ class LoggedInFlowNode( private val sessionEnterpriseService: SessionEnterpriseService, private val networkMonitor: NetworkMonitor, private val notificationConversationService: NotificationConversationService, + private val syncService: SyncService, + private val enterpriseService: EnterpriseService, + private val appPreferencesStore: AppPreferencesStore, + private val buildMeta: BuildMeta, snackbarDispatcher: SnackbarDispatcher, + private val analyticsService: AnalyticsService, + private val analyticsRoomListStateWatcher: AnalyticsRoomListStateWatcher, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.Placeholder, @@ -136,10 +157,11 @@ class LoggedInFlowNode( plugins = plugins ) { interface Callback : Plugin { - fun onOpenBugReport() - fun onAddAccount() + fun navigateToBugReport() + fun navigateToAddAccount() } + private val callback: Callback = callback() private val loggedInFlowProcessor = LoggedInEventProcessor( snackbarDispatcher = snackbarDispatcher, roomMembershipObserver = matrixClient.roomMembershipObserver, @@ -185,6 +207,7 @@ class LoggedInFlowNode( } lifecycle.subscribe( onCreate = { + analyticsRoomListStateWatcher.start() appNavigationStateService.onNavigateToSession(id, matrixClient.sessionId) // TODO We do not support Space yet, so directly navigate to main space appNavigationStateService.onNavigateToSpace(id, MAIN_SPACE) @@ -198,6 +221,8 @@ class LoggedInFlowNode( matrixClient.getMaxFileUploadSize() } + analyticsService.startLongRunningTransaction(AnalyticsLongRunningTransaction.FirstRoomsDisplayed) + ftueService.state .onEach { ftueState -> when (ftueState) { @@ -219,6 +244,7 @@ class LoggedInFlowNode( appNavigationStateService.onLeavingSession(id) loggedInFlowProcessor.stopObserving() matrixClient.sessionVerificationService.setListener(null) + analyticsRoomListStateWatcher.stop() } ) setupSendingQueue() @@ -242,9 +268,9 @@ class LoggedInFlowNode( data class Room( val roomIdOrAlias: RoomIdOrAlias, val serverNames: List = emptyList(), - val trigger: JoinedRoom.Trigger? = null, + val trigger: JoinedRoomAnalyticsEvent.Trigger? = null, val roomDescription: RoomDescription? = null, - val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages(), + val initialElement: RoomNavigationTarget = RoomNavigationTarget.Root(), val targetId: UUID = UUID.randomUUID(), ) : NavTarget @@ -270,7 +296,10 @@ class LoggedInFlowNode( data object Ftue : NavTarget @Parcelize - data object RoomDirectorySearch : NavTarget + data object LinkNewDevice : NavTarget + + @Parcelize + data object RoomDirectory : NavTarget @Parcelize data class IncomingShare(val intent: Intent) : NavTarget @@ -292,50 +321,52 @@ class LoggedInFlowNode( } NavTarget.Home -> { val callback = object : HomeEntryPoint.Callback { - override fun onRoomClick(roomId: RoomId) { - backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias())) + override fun navigateToRoom(roomId: RoomId, joinedRoom: JoinedRoom?) { + backstack.push( + NavTarget.Room( + roomIdOrAlias = roomId.toRoomIdOrAlias(), + initialElement = RoomNavigationTarget.Root(joinedRoom = joinedRoom) + ) + ) } - override fun onSettingsClick() { + override fun navigateToSettings() { backstack.push(NavTarget.Settings()) } - override fun onStartChatClick() { + override fun navigateToCreateRoom() { backstack.push(NavTarget.CreateRoom) } - override fun onSetUpRecoveryClick() { + override fun navigateToSetUpRecovery() { backstack.push(NavTarget.SecureBackup(initialElement = SecureBackupEntryPoint.InitialTarget.Root)) } - override fun onSessionConfirmRecoveryKeyClick() { + override fun navigateToEnterRecoveryKey() { backstack.push(NavTarget.SecureBackup(initialElement = SecureBackupEntryPoint.InitialTarget.EnterRecoveryKey)) } - override fun onRoomSettingsClick(roomId: RoomId) { + override fun navigateToRoomSettings(roomId: RoomId) { backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), initialElement = RoomNavigationTarget.Details)) } - override fun onReportBugClick() { - plugins().forEach { it.onOpenBugReport() } + override fun navigateToBugReport() { + callback.navigateToBugReport() } } - homeEntryPoint - .nodeBuilder(this, buildContext) - .callback(callback) - .build() + homeEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + callback = callback, + ) } is NavTarget.Room -> { val joinedRoomCallback = object : JoinedRoomLoadedFlowNode.Callback { - override fun onOpenRoom(roomId: RoomId, serverNames: List) { + override fun navigateToRoom(roomId: RoomId, serverNames: List) { backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), serverNames)) } - override fun onForwardedToSingleRoom(roomId: RoomId) { - sessionCoroutineScope.launch { attachRoom(roomId.toRoomIdOrAlias(), clearBackstack = false) } - } - - override fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean) { + override fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) { when (data) { is PermalinkData.UserLink -> { // Should not happen (handled by MessagesNode) @@ -345,8 +376,8 @@ class LoggedInFlowNode( val target = NavTarget.Room( roomIdOrAlias = data.roomIdOrAlias, serverNames = data.viaParameters, - trigger = JoinedRoom.Trigger.Timeline, - initialElement = RoomNavigationTarget.Messages(data.eventId), + trigger = JoinedRoomAnalyticsEvent.Trigger.Timeline, + initialElement = RoomNavigationTarget.Root(data.eventId), ) if (pushToBackstack) { backstack.push(target) @@ -361,15 +392,10 @@ class LoggedInFlowNode( } } - override fun onOpenGlobalNotificationSettings() { + override fun navigateToGlobalNotificationSettings() { backstack.push(NavTarget.Settings(PreferencesEntryPoint.InitialTarget.NotificationSettings)) } } - val spaceCallback = object : SpaceEntryPoint.Callback { - override fun onOpenRoom(roomId: RoomId, viaParameters: List) { - backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), serverNames = viaParameters)) - } - } val inputs = RoomFlowNode.Inputs( roomIdOrAlias = navTarget.roomIdOrAlias, roomDescription = Optional.ofNullable(navTarget.roomDescription), @@ -377,114 +403,138 @@ class LoggedInFlowNode( trigger = Optional.ofNullable(navTarget.trigger), initialElement = navTarget.initialElement ) - createNode(buildContext, plugins = listOf(inputs, joinedRoomCallback, spaceCallback)) + createNode(buildContext, plugins = listOf(inputs, joinedRoomCallback)) } is NavTarget.UserProfile -> { val callback = object : UserProfileEntryPoint.Callback { - override fun onOpenRoom(roomId: RoomId) { + override fun navigateToRoom(roomId: RoomId) { backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias())) } } - userProfileEntryPoint.nodeBuilder(this, buildContext) - .params(UserProfileEntryPoint.Params(userId = navTarget.userId)) - .callback(callback) - .build() + userProfileEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = UserProfileEntryPoint.Params(userId = navTarget.userId), + callback = callback, + ) } is NavTarget.Settings -> { val callback = object : PreferencesEntryPoint.Callback { - override fun onAddAccount() { - plugins().forEach { it.onAddAccount() } + override fun navigateToAddAccount() { + callback.navigateToAddAccount() } - override fun onOpenBugReport() { - plugins().forEach { it.onOpenBugReport() } + override fun navigateToLinkNewDevice() { + backstack.push(NavTarget.LinkNewDevice) } - override fun onSecureBackupClick() { + override fun navigateToBugReport() { + callback.navigateToBugReport() + } + + override fun navigateToSecureBackup() { backstack.push(NavTarget.SecureBackup()) } - override fun onOpenRoomNotificationSettings(roomId: RoomId) { + override fun navigateToRoomNotificationSettings(roomId: RoomId) { backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), initialElement = RoomNavigationTarget.NotificationSettings)) } - override fun navigateTo(roomId: RoomId, eventId: EventId) { - backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), initialElement = RoomNavigationTarget.Messages(eventId))) + override fun navigateToEvent(roomId: RoomId, eventId: EventId) { + backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias(), initialElement = RoomNavigationTarget.Root(eventId))) } } val inputs = PreferencesEntryPoint.Params(navTarget.initialElement) - preferencesEntryPoint.nodeBuilder(this, buildContext) - .params(inputs) - .callback(callback) - .build() + preferencesEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = inputs, + callback = callback, + ) } NavTarget.CreateRoom -> { val callback = object : StartChatEntryPoint.Callback { - override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List) { + override fun onRoomCreated(roomIdOrAlias: RoomIdOrAlias, serverNames: List) { backstack.replace(NavTarget.Room(roomIdOrAlias = roomIdOrAlias, serverNames = serverNames)) } - override fun onOpenRoomDirectory() { - backstack.push(NavTarget.RoomDirectorySearch) + override fun navigateToRoomDirectory() { + backstack.push(NavTarget.RoomDirectory) } } - startChatEntryPoint - .nodeBuilder(this, buildContext) - .callback(callback) - .build() + startChatEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + callback = callback, + ) } is NavTarget.SecureBackup -> { - secureBackupEntryPoint.nodeBuilder(this, buildContext) - .params(SecureBackupEntryPoint.Params(initialElement = navTarget.initialElement)) - .callback(object : SecureBackupEntryPoint.Callback { + secureBackupEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = SecureBackupEntryPoint.Params(initialElement = navTarget.initialElement), + callback = object : SecureBackupEntryPoint.Callback { override fun onDone() { backstack.pop() } - }) - .build() + }, + ) } NavTarget.Ftue -> { ftueEntryPoint.createNode(this, buildContext) } - NavTarget.RoomDirectorySearch -> { - roomDirectoryEntryPoint.nodeBuilder(this, buildContext) - .callback(object : RoomDirectoryEntryPoint.Callback { - override fun onResultClick(roomDescription: RoomDescription) { + NavTarget.LinkNewDevice -> { + val callback = object : LinkNewDeviceEntryPoint.Callback { + override fun onDone() { + backstack.pop() + } + } + linkNewDeviceEntryPoint.createNode(this, buildContext, callback) + } + NavTarget.RoomDirectory -> { + roomDirectoryEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + callback = object : RoomDirectoryEntryPoint.Callback { + override fun navigateToRoom(roomDescription: RoomDescription) { backstack.push( NavTarget.Room( roomIdOrAlias = roomDescription.roomId.toRoomIdOrAlias(), roomDescription = roomDescription, - trigger = JoinedRoom.Trigger.RoomDirectory, + trigger = JoinedRoomAnalyticsEvent.Trigger.RoomDirectory, ) ) } - }) - .build() + }, + ) } is NavTarget.IncomingShare -> { - shareEntryPoint.nodeBuilder(this, buildContext) - .callback(object : ShareEntryPoint.Callback { + shareEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = ShareEntryPoint.Params(intent = navTarget.intent), + callback = object : ShareEntryPoint.Callback { override fun onDone(roomIds: List) { - navigateUp() - if (roomIds.size == 1) { - val targetRoomId = roomIds.first() - backstack.push(NavTarget.Room(targetRoomId.toRoomIdOrAlias())) + backstack.pop() + roomIds.singleOrNull()?.let { roomId -> + backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias())) } } - }) - .params(ShareEntryPoint.Params(intent = navTarget.intent)) - .build() + }, + ) } is NavTarget.IncomingVerificationRequest -> { - incomingVerificationEntryPoint.nodeBuilder(this, buildContext) - .params(IncomingVerificationEntryPoint.Params(navTarget.data)) - .callback(object : IncomingVerificationEntryPoint.Callback { + incomingVerificationEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = IncomingVerificationEntryPoint.Params(navTarget.data), + callback = object : IncomingVerificationEntryPoint.Callback { override fun onDone() { backstack.pop() } - }) - .build() + }, + ) } } } @@ -492,10 +542,10 @@ class LoggedInFlowNode( suspend fun attachRoom( roomIdOrAlias: RoomIdOrAlias, serverNames: List = emptyList(), - trigger: JoinedRoom.Trigger? = null, + trigger: JoinedRoomAnalyticsEvent.Trigger? = null, eventId: EventId? = null, clearBackstack: Boolean, - ) { + ): RoomFlowNode { waitForNavTargetAttached { navTarget -> navTarget is NavTarget.Home } @@ -504,12 +554,19 @@ class LoggedInFlowNode( roomIdOrAlias = roomIdOrAlias, serverNames = serverNames, trigger = trigger, - initialElement = RoomNavigationTarget.Messages( - focusedEventId = eventId - ) + initialElement = RoomNavigationTarget.Root(eventId = eventId) ) backstack.accept(AttachRoomOperation(roomNavTarget, clearBackstack)) } + + // If we don't do this check, we might be returning while a previous node with the same type is still displayed + // This means we may attach some new nodes to that one, which will be quickly replaced by the one instantiated above + return waitForChildAttached { + it is NavTarget.Room && + it.roomIdOrAlias == roomIdOrAlias && + it.initialElement is RoomNavigationTarget.Root && + it.initialElement.eventId == eventId + } } suspend fun attachUser(userId: UserId) { @@ -538,11 +595,27 @@ class LoggedInFlowNode( @Composable override fun View(modifier: Modifier) { - Box(modifier = modifier) { - val ftueState by ftueService.state.collectAsState() - BackstackView() - if (ftueState is FtueState.Complete) { - PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent) + val colors by remember { + enterpriseService.semanticColorsFlow(sessionId = matrixClient.sessionId) + }.collectAsState(SemanticColorsLightDark.default) + ElementThemeApp( + appPreferencesStore = appPreferencesStore, + compoundLight = colors.light, + compoundDark = colors.dark, + buildMeta = buildMeta, + ) { + val isOnline by syncService.isOnline.collectAsState() + ConnectivityIndicatorContainer( + isOnline = isOnline, + modifier = modifier, + ) { contentModifier -> + Box(modifier = contentModifier) { + val ftueState by ftueService.state.collectAsState() + BackstackView() + if (ftueState is FtueState.Complete) { + PermanentChild(permanentNavModel = permanentNavModel, navTarget = NavTarget.LoggedInPermanent) + } + } } } } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt index 08bdbadde6..080f3c1d77 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/NotLoggedInFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,7 +19,6 @@ import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.Assisted @@ -29,10 +29,12 @@ import io.element.android.features.login.api.LoginParams import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs import io.element.android.libraries.designsystem.utils.ForceOrientationInMobileDevices import io.element.android.libraries.designsystem.utils.ScreenOrientation -import io.element.android.libraries.matrix.ui.media.NotLoggedInImageLoaderFactory +import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder +import io.element.android.services.analytics.api.watchers.AnalyticsColdStartWatcher import kotlinx.parcelize.Parcelize @ContributesNode(AppScope::class) @@ -41,7 +43,8 @@ class NotLoggedInFlowNode( @Assisted buildContext: BuildContext, @Assisted plugins: List, private val loginEntryPoint: LoginEntryPoint, - private val notLoggedInImageLoaderFactory: NotLoggedInImageLoaderFactory, + private val imageLoaderHolder: ImageLoaderHolder, + private val analyticsColdStartWatcher: AnalyticsColdStartWatcher, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.Root, @@ -55,16 +58,19 @@ class NotLoggedInFlowNode( ) : NodeInputs interface Callback : Plugin { - fun onOpenBugReport() + fun navigateToBugReport() + fun onDone() } + private val callback: Callback = callback() private val inputs = inputs() override fun onBuilt() { super.onBuilt() + analyticsColdStartWatcher.whenLoggingIn() lifecycle.subscribe( onResume = { - SingletonImageLoader.setUnsafe(notLoggedInImageLoaderFactory.newImageLoader()) + SingletonImageLoader.setUnsafe(imageLoaderHolder.get()) }, ) } @@ -78,20 +84,23 @@ class NotLoggedInFlowNode( return when (navTarget) { NavTarget.Root -> { val callback = object : LoginEntryPoint.Callback { - override fun onReportProblem() { - plugins().forEach { it.onOpenBugReport() } + override fun navigateToBugReport() { + callback.navigateToBugReport() + } + + override fun onDone() { + callback.onDone() } } - loginEntryPoint - .nodeBuilder(this, buildContext) - .params( - LoginEntryPoint.Params( - accountProvider = inputs.loginParams?.accountProvider, - loginHint = inputs.loginParams?.loginHint, - ) - ) - .callback(callback) - .build() + loginEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = LoginEntryPoint.Params( + accountProvider = inputs.loginParams?.accountProvider, + loginHint = inputs.loginParams?.loginHint, + ), + callback = callback, + ) } } } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index 19290c5f8b..4687946367 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -31,6 +32,7 @@ import io.element.android.annotations.ContributesNode import io.element.android.appnav.di.MatrixSessionCache import io.element.android.appnav.intent.IntentResolver import io.element.android.appnav.intent.ResolvedIntent +import io.element.android.appnav.room.RoomFlowNode import io.element.android.appnav.root.RootNavStateFlowFactory import io.element.android.appnav.root.RootPresenter import io.element.android.appnav.root.RootView @@ -38,7 +40,6 @@ import io.element.android.features.announcement.api.AnnouncementService import io.element.android.features.login.api.LoginParams import io.element.android.features.login.api.accesscontrol.AccountProviderAccessControl import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint -import io.element.android.features.rageshake.api.reporter.BugReporter import io.element.android.features.signedout.api.SignedOutEntryPoint import io.element.android.libraries.accountselect.api.AccountSelectEntryPoint import io.element.android.libraries.architecture.BackstackView @@ -50,7 +51,10 @@ import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.deeplink.api.DeeplinkData import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.ThreadId +import io.element.android.libraries.matrix.api.core.asEventId import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.oidc.api.OidcAction @@ -58,6 +62,10 @@ import io.element.android.libraries.oidc.api.OidcActionFlow import io.element.android.libraries.sessionstorage.api.LoggedInState import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.ui.common.nodes.emptyNode +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.api.watchers.AnalyticsColdStartWatcher +import io.element.android.services.appnavstate.api.ROOM_OPENED_FROM_NOTIFICATION import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -80,9 +88,10 @@ class RootFlowNode( private val accountSelectEntryPoint: AccountSelectEntryPoint, private val intentResolver: IntentResolver, private val oidcActionFlow: OidcActionFlow, - private val bugReporter: BugReporter, private val featureFlagService: FeatureFlagService, private val announcementService: AnnouncementService, + private val analyticsService: AnalyticsService, + private val analyticsColdStartWatcher: AnalyticsColdStartWatcher, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.SplashScreen, @@ -92,6 +101,7 @@ class RootFlowNode( plugins = plugins ) { override fun onBuilt() { + analyticsColdStartWatcher.start() matrixSessionCache.restoreWithSavedState(buildContext.savedStateMap) super.onBuilt() observeNavState() @@ -130,7 +140,6 @@ class RootFlowNode( private fun switchToNotLoggedInFlow(params: LoginParams?) { matrixSessionCache.removeAll() - bugReporter.setLogDirectorySubfolder(null) backstack.safeRoot(NavTarget.NotLoggedInFlow(params)) } @@ -226,11 +235,11 @@ class RootFlowNode( } val inputs = LoggedInAppScopeFlowNode.Inputs(matrixClient) val callback = object : LoggedInAppScopeFlowNode.Callback { - override fun onOpenBugReport() { + override fun navigateToBugReport() { backstack.push(NavTarget.BugReport) } - override fun onAddAccount() { + override fun navigateToAddAccount() { backstack.push(NavTarget.NotLoggedInFlow(null)) } } @@ -238,9 +247,13 @@ class RootFlowNode( } is NavTarget.NotLoggedInFlow -> { val callback = object : NotLoggedInFlowNode.Callback { - override fun onOpenBugReport() { + override fun navigateToBugReport() { backstack.push(NavTarget.BugReport) } + + override fun onDone() { + backstack.pop() + } } val params = NotLoggedInFlowNode.Params( loginParams = navTarget.params, @@ -248,11 +261,13 @@ class RootFlowNode( createNode(buildContext, plugins = listOf(params, callback)) } is NavTarget.SignedOutFlow -> { - signedOutEntryPoint.nodeBuilder(this, buildContext).params( - SignedOutEntryPoint.Params( - sessionId = navTarget.sessionId - ) - ).build() + signedOutEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = SignedOutEntryPoint.Params( + sessionId = navTarget.sessionId, + ), + ) } NavTarget.SplashScreen -> emptyNode(buildContext) NavTarget.BugReport -> { @@ -261,11 +276,15 @@ class RootFlowNode( backstack.pop() } } - bugReportEntryPoint.nodeBuilder(this, buildContext).callback(callback).build() + bugReportEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + callback = callback, + ) } is NavTarget.AccountSelect -> { val callback: AccountSelectEntryPoint.Callback = object : AccountSelectEntryPoint.Callback { - override fun onSelectAccount(sessionId: SessionId) { + override fun onAccountSelected(sessionId: SessionId) { lifecycleScope.launch { if (sessionId == navTarget.currentSessionId) { // Ensure that the account selection Node is removed from the backstack @@ -286,7 +305,11 @@ class RootFlowNode( backstack.pop() } } - accountSelectEntryPoint.nodeBuilder(this, buildContext).callback(callback).build() + accountSelectEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + callback = callback, + ) } } } @@ -294,7 +317,13 @@ class RootFlowNode( suspend fun handleIntent(intent: Intent) { val resolvedIntent = intentResolver.resolve(intent) ?: return when (resolvedIntent) { - is ResolvedIntent.Navigation -> navigateTo(resolvedIntent.deeplinkData) + is ResolvedIntent.Navigation -> { + val openingRoomFromNotification = intent.getBooleanExtra(ROOM_OPENED_FROM_NOTIFICATION, false) + if (openingRoomFromNotification && resolvedIntent.deeplinkData is DeeplinkData.Room) { + analyticsService.startLongRunningTransaction(AnalyticsLongRunningTransaction.NotificationTapOpensTimeline) + } + navigateTo(resolvedIntent.deeplinkData) + } is ResolvedIntent.Login -> onLoginLink(resolvedIntent.params) is ResolvedIntent.Oidc -> onOidcAction(resolvedIntent.oidcAction) is ResolvedIntent.Permalink -> navigateTo(resolvedIntent.permalinkData) @@ -338,7 +367,7 @@ class RootFlowNode( } else { // wait for the current session to be restored val loggedInFlowNode = attachSession(latestSessionId) - if (sessionStore.getAllSessions().size > 1) { + if (sessionStore.numberOfSessions() > 1) { // Several accounts, let the user choose which one to use backstack.push( NavTarget.AccountSelect( @@ -368,7 +397,7 @@ class RootFlowNode( is PermalinkData.FallbackLink -> Unit is PermalinkData.RoomEmailInviteLink -> Unit else -> { - if (sessionStore.getAllSessions().size > 1) { + if (sessionStore.numberOfSessions() > 1) { // Several accounts, let the user choose which one to use backstack.push( NavTarget.AccountSelect( @@ -391,13 +420,19 @@ class RootFlowNode( is PermalinkData.FallbackLink -> Unit is PermalinkData.RoomEmailInviteLink -> Unit is PermalinkData.RoomLink -> { + // If there is a thread id, focus on it in the main timeline + val focusedEventId = if (permalinkData.threadId != null) { + permalinkData.threadId?.asEventId() + } else { + permalinkData.eventId + } attachRoom( roomIdOrAlias = permalinkData.roomIdOrAlias, trigger = JoinedRoom.Trigger.MobilePermalink, serverNames = permalinkData.viaParameters, - eventId = permalinkData.eventId, + eventId = focusedEventId, clearBackstack = true - ) + ).maybeAttachThread(permalinkData.threadId, permalinkData.eventId) } is PermalinkData.UserLink -> { attachUser(permalinkData.userId) @@ -405,12 +440,24 @@ class RootFlowNode( } } + private suspend fun RoomFlowNode.maybeAttachThread(threadId: ThreadId?, focusedEventId: EventId?) { + if (threadId != null) { + attachThread(threadId, focusedEventId) + } + } + private suspend fun navigateTo(deeplinkData: DeeplinkData) { Timber.d("Navigating to $deeplinkData") - attachSession(deeplinkData.sessionId).apply { + attachSession(deeplinkData.sessionId).let { loggedInFlowNode -> when (deeplinkData) { is DeeplinkData.Root -> Unit // The room list will always be shown, observing FtueState - is DeeplinkData.Room -> attachRoom(deeplinkData.roomId.toRoomIdOrAlias(), clearBackstack = true) + is DeeplinkData.Room -> { + loggedInFlowNode.attachRoom( + roomIdOrAlias = deeplinkData.roomId.toRoomIdOrAlias(), + eventId = if (deeplinkData.threadId != null) deeplinkData.threadId?.asEventId() else deeplinkData.eventId, + clearBackstack = true, + ).maybeAttachThread(deeplinkData.threadId, deeplinkData.eventId) + } } } } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/di/MatrixSessionCache.kt b/appnav/src/main/kotlin/io/element/android/appnav/di/MatrixSessionCache.kt index ce80ac7207..c6a031921f 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/di/MatrixSessionCache.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/di/MatrixSessionCache.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,12 +13,14 @@ import com.bumble.appyx.core.state.MutableSavedStateMap import com.bumble.appyx.core.state.SavedStateMap import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn +import io.element.android.libraries.androidutils.hash.hash import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analyticsproviders.api.AnalyticsUserData import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -33,10 +36,10 @@ private const val SAVE_INSTANCE_KEY = "io.element.android.x.di.MatrixClientsHold */ @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class MatrixSessionCache( private val authenticationService: MatrixAuthenticationService, private val syncOrchestratorFactory: SyncOrchestrator.Factory, + private val analyticsService: AnalyticsService, ) : MatrixClientProvider { private val sessionIdsToMatrixSession = ConcurrentHashMap() private val restoreMutex = Mutex() @@ -101,6 +104,11 @@ class MatrixSessionCache( Timber.d("Restore matrix session: $sessionId") return authenticationService.restoreSession(sessionId) .onSuccess { matrixClient -> + // Add the current homeserver (hashed) to the extra info + // This may not play well with multiple sessions, but it should work for now + analyticsService.addIndexableData(AnalyticsUserData.HOMESERVER, matrixClient.userIdServerName().hash()) + + // Add the new client to the in-memory cache onNewMatrixClient(matrixClient) } .onFailure { diff --git a/appnav/src/main/kotlin/io/element/android/appnav/di/RoomGraphFactory.kt b/appnav/src/main/kotlin/io/element/android/appnav/di/RoomGraphFactory.kt index ae0a3a81f2..2d5244cb5a 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/di/RoomGraphFactory.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/di/RoomGraphFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appnav/src/main/kotlin/io/element/android/appnav/di/SessionGraphFactory.kt b/appnav/src/main/kotlin/io/element/android/appnav/di/SessionGraphFactory.kt index 788a1633df..bc3960409e 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/di/SessionGraphFactory.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/di/SessionGraphFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appnav/src/main/kotlin/io/element/android/appnav/di/SyncOrchestrator.kt b/appnav/src/main/kotlin/io/element/android/appnav/di/SyncOrchestrator.kt index 2561fd3ac7..9b1bbd1b81 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/di/SyncOrchestrator.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/di/SyncOrchestrator.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,6 +18,9 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.childScope import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.sync.SyncState +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.api.recordTransaction +import io.element.android.services.analyticsproviders.api.AnalyticsUserData import io.element.android.services.appnavstate.api.AppForegroundStateService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.FlowPreview @@ -38,6 +42,7 @@ class SyncOrchestrator( private val appForegroundStateService: AppForegroundStateService, private val networkMonitor: NetworkMonitor, dispatchers: CoroutineDispatchers, + private val analyticsService: AnalyticsService, ) { @AssistedFactory interface Factory { @@ -68,10 +73,13 @@ class SyncOrchestrator( // Perform an initial sync if the sync service is not running, to check whether the homeserver is accessible // Otherwise, if the device is offline the sync service will never start and the SyncState will be Idle, not Offline Timber.tag(tag).d("performing initial sync attempt") - syncService.startSync() + analyticsService.recordTransaction("First sync", "syncService.startSync()") { transaction -> + syncService.startSync() - // Wait until the sync service is not idle, either it will be running or in error/offline state - syncService.syncState.first { it != SyncState.Idle } + // Wait until the sync service is not idle, either it will be running or in error/offline state + val firstState = syncService.syncState.first { it != SyncState.Idle } + transaction.putIndexableData(AnalyticsUserData.FIRST_SYNC_STATE, firstState.name) + } observeStates() } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/di/TimelineBindings.kt b/appnav/src/main/kotlin/io/element/android/appnav/di/TimelineBindings.kt new file mode 100644 index 0000000000..cb78760a0f --- /dev/null +++ b/appnav/src/main/kotlin/io/element/android/appnav/di/TimelineBindings.kt @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.appnav.di + +import io.element.android.features.messages.api.pinned.PinnedEventsTimelineProvider +import io.element.android.libraries.matrix.api.timeline.TimelineProvider + +interface TimelineBindings { + val timelineProvider: TimelineProvider + val pinnedEventsTimelineProvider: PinnedEventsTimelineProvider +} 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 187b8f84b6..3e26130c78 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/AnalyticsVerificationStateExt.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/AnalyticsVerificationStateExt.kt index 565dbdd5ad..714645dcdc 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/AnalyticsVerificationStateExt.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/AnalyticsVerificationStateExt.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvents.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvents.kt index d7020c50a5..d2a9c9298e 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvents.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInNode.kt index edc2be05db..c49ca4221d 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,10 +13,10 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.SessionScope @ContributesNode(SessionScope::class) @@ -32,18 +33,14 @@ class LoggedInNode( fun navigateToNotificationTroubleshoot() } - private fun navigateToNotificationTroubleshoot() { - plugins().forEach { - it.navigateToNotificationTroubleshoot() - } - } + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { val loggedInState = loggedInPresenter.present() LoggedInView( state = loggedInState, - navigateToNotificationTroubleshoot = ::navigateToNotificationTroubleshoot, + navigateToNotificationTroubleshoot = callback::navigateToNotificationTroubleshoot, modifier = modifier ) } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt index 1f8be2f673..184766e323 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -35,7 +36,7 @@ import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.push.api.PushService -import io.element.android.libraries.pushproviders.api.RegistrationFailure +import io.element.android.libraries.push.api.PusherRegistrationFailure import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.combine @@ -70,7 +71,17 @@ class LoggedInPresenter( when (sessionVerifiedStatus) { SessionVerifiedStatus.Unknown -> Unit SessionVerifiedStatus.Verified -> { - ensurePusherIsRegistered(pusherRegistrationState) + Timber.tag(pusherTag.value).d("Ensure pusher is registered") + pushService.ensurePusherIsRegistered(matrixClient).fold( + onSuccess = { + Timber.tag(pusherTag.value).d("Pusher registered") + pusherRegistrationState.value = AsyncData.Success(Unit) + }, + onFailure = { + Timber.tag(pusherTag.value).e(it, "Failed to register pusher") + pusherRegistrationState.value = AsyncData.Failure(it) + }, + ) } SessionVerifiedStatus.NotVerified -> { pusherRegistrationState.value = AsyncData.Failure(PusherRegistrationFailure.AccountNotVerified()) @@ -122,7 +133,7 @@ class LoggedInPresenter( ignoreRegistrationError = ignoreRegistrationError, forceNativeSlidingSyncMigration = forceNativeSlidingSyncMigration, appName = buildMeta.applicationName, - eventSink = ::handleEvent + eventSink = ::handleEvent, ) } @@ -132,59 +143,6 @@ class LoggedInPresenter( currentSlidingSyncVersion == SlidingSyncVersion.Proxy } - private suspend fun ensurePusherIsRegistered(pusherRegistrationState: MutableState>) { - Timber.tag(pusherTag.value).d("Ensure pusher is registered") - val currentPushProvider = pushService.getCurrentPushProvider() - val result = if (currentPushProvider == null) { - Timber.tag(pusherTag.value).d("Register with the first available push provider with at least one distributor") - val pushProvider = pushService.getAvailablePushProviders() - .firstOrNull { it.getDistributors().isNotEmpty() } - // Else fallback to the first available push provider (the list should never be empty) - ?: pushService.getAvailablePushProviders().firstOrNull() - ?: return Unit - .also { Timber.tag(pusherTag.value).w("No push providers available") } - .also { pusherRegistrationState.value = AsyncData.Failure(PusherRegistrationFailure.NoProvidersAvailable()) } - val distributor = pushProvider.getDistributors().firstOrNull() - ?: return Unit - .also { Timber.tag(pusherTag.value).w("No distributors available") } - .also { - // In this case, consider the push provider is chosen. - pushService.selectPushProvider(matrixClient.sessionId, pushProvider) - } - .also { pusherRegistrationState.value = AsyncData.Failure(PusherRegistrationFailure.NoDistributorsAvailable()) } - pushService.registerWith(matrixClient, pushProvider, distributor) - } else { - val currentPushDistributor = currentPushProvider.getCurrentDistributor(matrixClient.sessionId) - if (currentPushDistributor == null) { - Timber.tag(pusherTag.value).d("Register with the first available distributor") - val distributor = currentPushProvider.getDistributors().firstOrNull() - ?: return Unit - .also { Timber.tag(pusherTag.value).w("No distributors available") } - .also { pusherRegistrationState.value = AsyncData.Failure(PusherRegistrationFailure.NoDistributorsAvailable()) } - pushService.registerWith(matrixClient, currentPushProvider, distributor) - } else { - Timber.tag(pusherTag.value).d("Re-register with the current distributor") - pushService.registerWith(matrixClient, currentPushProvider, currentPushDistributor) - } - } - result.fold( - onSuccess = { - Timber.tag(pusherTag.value).d("Pusher registered") - pusherRegistrationState.value = AsyncData.Success(Unit) - }, - onFailure = { - Timber.tag(pusherTag.value).e(it, "Failed to register pusher") - if (it is RegistrationFailure) { - pusherRegistrationState.value = AsyncData.Failure( - PusherRegistrationFailure.RegistrationFailure(it.clientException, it.isRegisteringAgain) - ) - } else { - pusherRegistrationState.value = AsyncData.Failure(it) - } - } - ) - } - private fun reportCryptoStatusToAnalytics(verificationState: SessionVerifiedStatus, recoveryState: RecoveryState) { // Update first the user property, to store the current status for that posthog user val userVerificationState = verificationState.toAnalyticsUserPropertyValue() diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt index 8f039ffa2b..b066f9f867 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt index 2ba0c8de81..b2f5407519 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,6 +10,7 @@ package io.element.android.appnav.loggedin import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.push.api.PusherRegistrationFailure open class LoggedInStateProvider : PreviewParameterProvider { override val values: Sequence diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt index d6a12ea27b..62d8de8c29 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -24,6 +25,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.utils.OnLifecycleEvent import io.element.android.libraries.matrix.api.exception.isNetworkError +import io.element.android.libraries.push.api.PusherRegistrationFailure import io.element.android.libraries.ui.strings.CommonStrings @Composable diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/MediaPreviewConfigMigration.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/MediaPreviewConfigMigration.kt index 7916d48171..6b5803aa14 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/MediaPreviewConfigMigration.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/MediaPreviewConfigMigration.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SendQueues.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SendQueues.kt index bb485fd646..3f01f8337e 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SendQueues.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SendQueues.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt index e8890a5da8..7c5f196a4c 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt index 9a84049497..1bf3516900 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,8 +10,6 @@ package io.element.android.appnav.room import android.os.Parcelable import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.lifecycleScope import com.bumble.appyx.core.modality.BuildContext @@ -20,10 +19,10 @@ import com.bumble.appyx.core.node.node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.active import com.bumble.appyx.navmodel.backstack.operation.newRoot import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject -import im.vector.app.features.analytics.plan.JoinedRoom import io.element.android.annotations.ContributesNode import io.element.android.appnav.room.joined.JoinedRoomFlowNode import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode @@ -32,36 +31,42 @@ import io.element.android.features.joinroom.api.JoinRoomEntryPoint import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint.Params import io.element.android.features.roomdirectory.api.RoomDescription -import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.inputs -import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.coroutine.withPreviousValue import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomIdOrAlias +import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias -import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.ui.room.LoadingRoomState +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.LoadJoinedRoomFlow +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.NotificationTapOpensTimeline +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.OpenRoom +import io.element.android.services.analytics.api.AnalyticsService import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import timber.log.Timber import java.util.Optional import kotlin.jvm.optionals.getOrNull +import im.vector.app.features.analytics.plan.JoinedRoom as JoinedRoomAnalyticsEvent +import io.element.android.libraries.matrix.api.room.JoinedRoom as JoinedRoomInstance @ContributesNode(SessionScope::class) @AssistedInject @@ -71,12 +76,18 @@ class RoomFlowNode( private val client: MatrixClient, private val joinRoomEntryPoint: JoinRoomEntryPoint, private val roomAliasResolverEntryPoint: RoomAliasResolverEntryPoint, - private val syncService: SyncService, private val membershipObserver: RoomMembershipObserver, - private val spaceEntryPoint: SpaceEntryPoint, + private val analyticsService: AnalyticsService, ) : BaseFlowNode( backstack = BackStack( - initialElement = NavTarget.Loading, + initialElement = run { + val joinedRoom = (plugins.filterIsInstance().first().initialElement as? RoomNavigationTarget.Root)?.joinedRoom + if (joinedRoom != null) { + NavTarget.JoinedRoom(joinedRoom) + } else { + NavTarget.Loading + } + }, savedStateMap = buildContext.savedStateMap, ), buildContext = buildContext, @@ -86,7 +97,7 @@ class RoomFlowNode( val roomIdOrAlias: RoomIdOrAlias, val roomDescription: Optional, val serverNames: List, - val trigger: Optional, + val trigger: Optional, val initialElement: RoomNavigationTarget, ) : NodeInputs @@ -103,18 +114,23 @@ class RoomFlowNode( data class JoinRoom( val roomId: RoomId, val serverNames: List, - val trigger: im.vector.app.features.analytics.plan.JoinedRoom.Trigger, + val trigger: JoinedRoomAnalyticsEvent.Trigger, ) : NavTarget @Parcelize - data class JoinedRoom(val roomId: RoomId) : NavTarget - - @Parcelize - data class JoinedSpace(val spaceId: RoomId) : NavTarget + data class JoinedRoom( + val roomId: RoomId, + @IgnoredOnParcel val joinedRoom: JoinedRoomInstance? = null, + ) : NavTarget { + constructor(joinedRoom: JoinedRoomInstance) : this(joinedRoom.roomId, joinedRoom) + } } override fun onBuilt() { super.onBuilt() + val parentTransaction = analyticsService.getLongRunningTransaction(NotificationTapOpensTimeline) + val openRoomTransaction = analyticsService.startLongRunningTransaction(OpenRoom, parentTransaction) + analyticsService.startLongRunningTransaction(LoadJoinedRoomFlow, openRoomTransaction) resolveRoomId() } @@ -132,8 +148,9 @@ class RoomFlowNode( } private fun subscribeToRoomInfoFlow(roomId: RoomId, serverNames: List) { - val roomInfoFlow = client.getRoomInfoFlow(roomId) - val isSpaceFlow = roomInfoFlow.map { it.getOrNull()?.isSpace.orFalse() }.distinctUntilChanged() + val joinedRoom = (inputs.initialElement as? RoomNavigationTarget.Root)?.joinedRoom + val roomInfoFlow = joinedRoom?.roomInfoFlow?.map { Optional.of(it) } + ?: client.getRoomInfoFlow(roomId) // This observes the local membership changes for the room val membershipUpdateFlow = membershipObserver.updates @@ -146,14 +163,15 @@ class RoomFlowNode( .map { it.getOrNull()?.currentUserMembership } .distinctUntilChanged() .withPreviousValue() - combine(currentMembershipFlow, isSpaceFlow) { (previousMembership, membership), isSpace -> + currentMembershipFlow.onEach { (previousMembership, membership) -> Timber.d("Room membership: $membership") if (membership == CurrentUserMembership.JOINED) { - if (isSpace) { - backstack.newRoot(NavTarget.JoinedSpace(spaceId = roomId)) - } else { - backstack.newRoot(NavTarget.JoinedRoom(roomId)) + val currentNavTarget = backstack.active?.key?.navTarget + if (currentNavTarget is NavTarget.JoinedRoom && currentNavTarget.roomId == roomId) { + Timber.d("Already in JoinedRoom $roomId, do nothing") + return@onEach } + backstack.newRoot(NavTarget.JoinedRoom(roomId)) } else { val leavingFromCurrentDevice = membership == CurrentUserMembership.LEFT && @@ -167,7 +185,7 @@ class RoomFlowNode( NavTarget.JoinRoom( roomId = roomId, serverNames = serverNames, - trigger = inputs.trigger.getOrNull() ?: JoinedRoom.Trigger.Invite, + trigger = inputs.trigger.getOrNull() ?: JoinedRoomAnalyticsEvent.Trigger.Invite, ) ) } @@ -188,10 +206,12 @@ class RoomFlowNode( } } val params = Params(navTarget.roomAlias) - roomAliasResolverEntryPoint.nodeBuilder(this, buildContext) - .callback(callback) - .params(params) - .build() + roomAliasResolverEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = params, + callback = callback, + ) } is NavTarget.JoinRoom -> { val inputs = JoinRoomEntryPoint.Inputs( @@ -201,31 +221,32 @@ class RoomFlowNode( serverNames = navTarget.serverNames, trigger = navTarget.trigger, ) - joinRoomEntryPoint.createNode(this, buildContext, inputs) + joinRoomEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + inputs = inputs, + ) } is NavTarget.JoinedRoom -> { val roomFlowNodeCallback = plugins() val inputs = JoinedRoomFlowNode.Inputs( roomId = navTarget.roomId, - initialElement = inputs.initialElement + initialElement = inputs.initialElement, + joinedRoom = navTarget.joinedRoom, ) createNode(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback) } - is NavTarget.JoinedSpace -> { - val spaceCallback = plugins().single() - spaceEntryPoint.nodeBuilder(this, buildContext) - .inputs(SpaceEntryPoint.Inputs(roomId = navTarget.spaceId)) - .callback(spaceCallback) - .build() - } } } + suspend fun attachThread(threadId: ThreadId, focusedEventId: EventId?) { + waitForChildAttached() + .attachThread(threadId, focusedEventId) + } + private fun loadingNode(buildContext: BuildContext) = node(buildContext) { modifier -> - val isOnline by syncService.isOnline.collectAsState() LoadingRoomNodeView( state = LoadingRoomState.Loading, - hasNetworkConnection = isOnline, onBackClick = { navigateUp() }, modifier = modifier, ) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomNavigationTarget.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomNavigationTarget.kt index ab1589898b..aac916ab9d 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomNavigationTarget.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomNavigationTarget.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,11 +10,16 @@ package io.element.android.appnav.room import android.os.Parcelable import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.room.JoinedRoom +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize sealed interface RoomNavigationTarget : Parcelable { @Parcelize - data class Messages(val focusedEventId: EventId? = null) : RoomNavigationTarget + data class Root( + val eventId: EventId? = null, + @IgnoredOnParcel val joinedRoom: JoinedRoom? = null, + ) : RoomNavigationTarget @Parcelize data object Details : RoomNavigationTarget diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt index cbda7a8bfb..504bdfec58 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -34,8 +35,10 @@ import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope +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.sync.SyncService +import io.element.android.libraries.matrix.api.core.ThreadId +import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.ui.room.LoadingRoomState import io.element.android.libraries.matrix.ui.room.LoadingRoomStateFlowFactory import kotlinx.coroutines.flow.distinctUntilChanged @@ -50,7 +53,6 @@ class JoinedRoomFlowNode( @Assisted val buildContext: BuildContext, @Assisted plugins: List, loadingRoomStateFlowFactory: LoadingRoomStateFlowFactory, - private val syncService: SyncService, ) : BaseFlowNode( backstack = BackStack( @@ -62,11 +64,12 @@ class JoinedRoomFlowNode( ) { data class Inputs( val roomId: RoomId, + val joinedRoom: JoinedRoom?, val initialElement: RoomNavigationTarget, ) : NodeInputs private val inputs: Inputs = inputs() - private val loadingRoomStateStateFlow = loadingRoomStateFlowFactory.create(lifecycleScope, inputs.roomId) + private val loadingRoomStateStateFlow = loadingRoomStateFlowFactory.create(lifecycleScope, inputs.roomId, inputs.joinedRoom) sealed interface NavTarget : Parcelable { @Parcelize @@ -116,15 +119,18 @@ class JoinedRoomFlowNode( private fun loadingNode(buildContext: BuildContext, onBackClick: () -> Unit) = node(buildContext) { modifier -> val loadingRoomState by loadingRoomStateStateFlow.collectAsState() - val isOnline by syncService.isOnline.collectAsState() LoadingRoomNodeView( state = loadingRoomState, - hasNetworkConnection = isOnline, - modifier = modifier, - onBackClick = onBackClick + onBackClick = onBackClick, + modifier = modifier ) } + suspend fun attachThread(threadId: ThreadId, focusedEventId: EventId?) { + waitForChildAttached() + .attachThread(threadId, focusedEventId) + } + @Composable override fun View(modifier: Modifier) { BackstackView( diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt index f0f8dff3e7..0b3ba57776 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomLoadedFlowNode.kt @@ -1,13 +1,16 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.appnav.room.joined +import android.app.Activity import android.os.Parcelable +import androidx.activity.compose.LocalActivity import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.lifecycle.lifecycleScope @@ -16,27 +19,39 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.appnav.di.RoomGraphFactory +import io.element.android.appnav.di.TimelineBindings import io.element.android.appnav.room.RoomNavigationTarget +import io.element.android.features.forward.api.ForwardEntryPoint import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint +import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.architecture.waitForChildAttached import io.element.android.libraries.di.DependencyInjectionGraphOwner import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.room.JoinedRoom +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.LoadJoinedRoomFlow +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.LoadMessagesUi +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.OpenRoom +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.api.finishLongRunningTransaction import io.element.android.services.appnavstate.api.ActiveRoomsHolder import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.coroutines.CoroutineScope @@ -51,29 +66,27 @@ class JoinedRoomLoadedFlowNode( @Assisted plugins: List, private val messagesEntryPoint: MessagesEntryPoint, private val roomDetailsEntryPoint: RoomDetailsEntryPoint, + private val spaceEntryPoint: SpaceEntryPoint, + private val forwardEntryPoint: ForwardEntryPoint, private val appNavigationStateService: AppNavigationStateService, @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, private val matrixClient: MatrixClient, private val activeRoomsHolder: ActiveRoomsHolder, + private val analyticsService: AnalyticsService, roomGraphFactory: RoomGraphFactory, ) : BaseFlowNode( backstack = BackStack( - initialElement = when (val input = plugins.filterIsInstance().first().initialElement) { - is RoomNavigationTarget.Messages -> NavTarget.Messages(input.focusedEventId) - RoomNavigationTarget.Details -> NavTarget.RoomDetails - RoomNavigationTarget.NotificationSettings -> NavTarget.RoomNotificationSettings - }, + initialElement = initialElement(plugins), savedStateMap = buildContext.savedStateMap, ), buildContext = buildContext, plugins = plugins, ), DependencyInjectionGraphOwner { interface Callback : Plugin { - fun onOpenRoom(roomId: RoomId, serverNames: List) - fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean) - fun onForwardedToSingleRoom(roomId: RoomId) - fun onOpenGlobalNotificationSettings() + fun navigateToRoom(roomId: RoomId, serverNames: List) + fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) + fun navigateToGlobalNotificationSettings() } data class Inputs( @@ -82,12 +95,17 @@ class JoinedRoomLoadedFlowNode( ) : NodeInputs private val inputs: Inputs = inputs() - private val callbacks = plugins.filterIsInstance() + private val callback: Callback = callback() override val graph = roomGraphFactory.create(inputs.room) + // This is an ugly hack to check activity recreation + private var currentActivity: Activity? = null + init { lifecycle.subscribe( onCreate = { + val parent = analyticsService.getLongRunningTransaction(OpenRoom) + analyticsService.startLongRunningTransaction(LoadMessagesUi, parent) Timber.v("OnCreate => ${inputs.room.roomId}") appNavigationStateService.onNavigateToRoom(id, inputs.room.roomId) activeRoomsHolder.addRoom(inputs.room) @@ -95,14 +113,19 @@ class JoinedRoomLoadedFlowNode( trackVisitedRoom() }, onResume = { + analyticsService.finishLongRunningTransaction(LoadJoinedRoomFlow) sessionCoroutineScope.launch { inputs.room.subscribeToSync() } }, onDestroy = { Timber.v("OnDestroy") - activeRoomsHolder.removeRoom(inputs.room.sessionId, inputs.room.roomId) - inputs.room.destroy() + // If we're just going through an activity recreation there's no need to destroy the Room object + // Destroying it would actually cause an issue where its methods can no longer be called + if (currentActivity?.isChangingConfigurations != true) { + activeRoomsHolder.removeRoom(inputs.room.sessionId, inputs.room.roomId) + inputs.room.destroy() + } appNavigationStateService.onLeavingRoom(id) } ) @@ -118,26 +141,28 @@ class JoinedRoomLoadedFlowNode( private fun createRoomDetailsNode(buildContext: BuildContext, initialTarget: RoomDetailsEntryPoint.InitialTarget): Node { val callback = object : RoomDetailsEntryPoint.Callback { - override fun onOpenGlobalNotificationSettings() { - callbacks.forEach { it.onOpenGlobalNotificationSettings() } + override fun navigateToGlobalNotificationSettings() { + callback.navigateToGlobalNotificationSettings() } - override fun onOpenRoom(roomId: RoomId, serverNames: List) { - callbacks.forEach { it.onOpenRoom(roomId, serverNames) } + override fun navigateToRoom(roomId: RoomId, serverNames: List) { + callback.navigateToRoom(roomId, serverNames) } - override fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean) { - callbacks.forEach { it.onPermalinkClick(data, pushToBackstack) } + override fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) { + callback.handlePermalinkClick(data, pushToBackstack) } - override fun onForwardedToSingleRoom(roomId: RoomId) { - callbacks.forEach { it.onForwardedToSingleRoom(roomId) } + override fun startForwardEventFlow(eventId: EventId, fromPinnedEvents: Boolean) { + backstack.push(NavTarget.ForwardEvent(eventId, fromPinnedEvents)) } } - return roomDetailsEntryPoint.nodeBuilder(this, buildContext) - .params(RoomDetailsEntryPoint.Params(initialTarget)) - .callback(callback) - .build() + return roomDetailsEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = RoomDetailsEntryPoint.Params(initialTarget), + callback = callback, + ) } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { @@ -154,55 +179,142 @@ class JoinedRoomLoadedFlowNode( NavTarget.RoomNotificationSettings -> { createRoomDetailsNode(buildContext, RoomDetailsEntryPoint.InitialTarget.RoomNotificationSettings) } + NavTarget.RoomMemberList -> { + createRoomDetailsNode(buildContext, RoomDetailsEntryPoint.InitialTarget.RoomMemberList) + } + NavTarget.Space -> { + createSpaceNode(buildContext) + } + is NavTarget.ForwardEvent -> { + val timelineProvider = if (navTarget.fromPinnedEvents) { + (graph as TimelineBindings).pinnedEventsTimelineProvider + } else { + (graph as TimelineBindings).timelineProvider + } + val params = ForwardEntryPoint.Params(navTarget.eventId, timelineProvider) + val callback = object : ForwardEntryPoint.Callback { + override fun onDone(roomIds: List) { + backstack.pop() + roomIds.singleOrNull()?.let { roomId -> + callback.navigateToRoom(roomId, emptyList()) + } + } + } + forwardEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = params, + callback = callback, + ) + } } } + private fun createSpaceNode(buildContext: BuildContext): Node { + val callback = object : SpaceEntryPoint.Callback { + override fun navigateToRoom(roomId: RoomId, viaParameters: List) { + callback.navigateToRoom(roomId, viaParameters) + } + + override fun navigateToRoomMemberList() { + backstack.push(NavTarget.RoomMemberList) + } + } + return spaceEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + inputs = SpaceEntryPoint.Inputs(roomId = inputs.room.roomId), + callback = callback, + ) + } + private fun createMessagesNode( buildContext: BuildContext, navTarget: NavTarget.Messages, ): Node { val callback = object : MessagesEntryPoint.Callback { - override fun onRoomDetailsClick() { + override fun navigateToRoomDetails() { backstack.push(NavTarget.RoomDetails) } - override fun onUserDataClick(userId: UserId) { + override fun navigateToRoomMemberDetails(userId: UserId) { backstack.push(NavTarget.RoomMemberDetails(userId)) } - override fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean) { - callbacks.forEach { it.onPermalinkClick(data, pushToBackstack) } + override fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) { + callback.handlePermalinkClick(data, pushToBackstack) } - override fun onForwardedToSingleRoom(roomId: RoomId) { - callbacks.forEach { it.onForwardedToSingleRoom(roomId) } + override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) { + backstack.push(NavTarget.ForwardEvent(eventId, fromPinnedEvents)) + } + + override fun navigateToRoom(roomId: RoomId) { + callback.navigateToRoom(roomId, emptyList()) } } val params = MessagesEntryPoint.Params( MessagesEntryPoint.InitialTarget.Messages(navTarget.focusedEventId) ) - return messagesEntryPoint.nodeBuilder(this, buildContext) - .params(params) - .callback(callback) - .build() + return messagesEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = params, + callback = callback, + ) } sealed interface NavTarget : Parcelable { @Parcelize - data class Messages(val focusedEventId: EventId? = null) : NavTarget + data object Space : NavTarget + + @Parcelize + data class Messages( + val focusedEventId: EventId? = null, + ) : NavTarget @Parcelize data object RoomDetails : NavTarget + @Parcelize + data object RoomMemberList : NavTarget + @Parcelize data class RoomMemberDetails(val userId: UserId) : NavTarget + @Parcelize + data class ForwardEvent(val eventId: EventId, val fromPinnedEvents: Boolean) : NavTarget + @Parcelize data object RoomNotificationSettings : NavTarget } + suspend fun attachThread(threadId: ThreadId, focusedEventId: EventId?) { + val messageNode = waitForChildAttached { navTarget -> + navTarget is NavTarget.Messages + } + (messageNode as? MessagesEntryPoint.NodeProxy)?.attachThread(threadId, focusedEventId) + } + @Composable override fun View(modifier: Modifier) { + currentActivity = LocalActivity.current + BackstackView() } } + +private fun initialElement(plugins: List): JoinedRoomLoadedFlowNode.NavTarget { + val input = plugins.filterIsInstance().single() + return when (input.initialElement) { + is RoomNavigationTarget.Root -> { + if (input.room.roomInfoFlow.value.isSpace) { + JoinedRoomLoadedFlowNode.NavTarget.Space + } else { + JoinedRoomLoadedFlowNode.NavTarget.Messages(input.initialElement.eventId) + } + } + RoomNavigationTarget.Details -> JoinedRoomLoadedFlowNode.NavTarget.RoomDetails + RoomNavigationTarget.NotificationSettings -> JoinedRoomLoadedFlowNode.NavTarget.RoomNotificationSettings + } +} diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/LoadingRoomNodeView.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/LoadingRoomNodeView.kt index 938e46f915..a596052fca 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/LoadingRoomNodeView.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/LoadingRoomNodeView.kt @@ -1,15 +1,14 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.appnav.room.joined import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -21,9 +20,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme -import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorView -import io.element.android.libraries.designsystem.atomic.molecules.IconTitlePlaceholdersRowMolecule -import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -31,6 +27,7 @@ import io.element.android.libraries.designsystem.theme.components.CircularProgre import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.designsystem.utils.DelayedVisibility import io.element.android.libraries.matrix.ui.room.LoadingRoomState import io.element.android.libraries.matrix.ui.room.LoadingRoomStateProvider import io.element.android.libraries.ui.strings.CommonStrings @@ -38,17 +35,13 @@ import io.element.android.libraries.ui.strings.CommonStrings @Composable fun LoadingRoomNodeView( state: LoadingRoomState, - hasNetworkConnection: Boolean, onBackClick: () -> Unit, modifier: Modifier = Modifier, ) { Scaffold( modifier = modifier, topBar = { - Column { - ConnectivityIndicatorView(isOnline = hasNetworkConnection) - LoadingRoomTopBar(onBackClick) - } + LoadingRoomTopBar(onBackClick) }, content = { padding -> Box( @@ -66,7 +59,9 @@ fun LoadingRoomNodeView( style = ElementTheme.typography.fontBodyMdRegular, ) } else { - CircularProgressIndicator() + DelayedVisibility { + CircularProgressIndicator() + } } } }, @@ -83,9 +78,7 @@ private fun LoadingRoomTopBar( BackButton(onClick = onBackClick) }, title = { - IconTitlePlaceholdersRowMolecule(iconSize = AvatarSize.TimelineRoom.dp) }, - windowInsets = WindowInsets(0.dp), ) } @@ -94,7 +87,6 @@ private fun LoadingRoomTopBar( internal fun LoadingRoomNodeViewPreview(@PreviewParameter(LoadingRoomStateProvider::class) state: LoadingRoomState) = ElementPreview { LoadingRoomNodeView( state = state, - onBackClick = {}, - hasNetworkConnection = false + onBackClick = {} ) } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavState.kt b/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavState.kt index a98ad0e0fd..9015872e62 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavState.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavStateFlowFactory.kt b/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavStateFlowFactory.kt index dfa478d6be..9a91e9765b 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavStateFlowFactory.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/root/RootNavStateFlowFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appnav/src/main/kotlin/io/element/android/appnav/root/RootPresenter.kt b/appnav/src/main/kotlin/io/element/android/appnav/root/RootPresenter.kt index d987c2a7ec..75704c499d 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/root/RootPresenter.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/root/RootPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appnav/src/main/kotlin/io/element/android/appnav/root/RootState.kt b/appnav/src/main/kotlin/io/element/android/appnav/root/RootState.kt index 3ea7362efa..0d7f362c1e 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/root/RootState.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/root/RootState.kt @@ -1,18 +1,17 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.appnav.root -import androidx.compose.runtime.Immutable import io.element.android.features.rageshake.api.crash.CrashDetectionState import io.element.android.features.rageshake.api.detection.RageshakeDetectionState import io.element.android.services.apperror.api.AppErrorState -@Immutable data class RootState( val rageshakeDetectionState: RageshakeDetectionState, val crashDetectionState: CrashDetectionState, diff --git a/appnav/src/main/kotlin/io/element/android/appnav/root/RootStateProvider.kt b/appnav/src/main/kotlin/io/element/android/appnav/root/RootStateProvider.kt index 4d84e06070..26db205498 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/root/RootStateProvider.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/root/RootStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appnav/src/main/kotlin/io/element/android/appnav/root/RootView.kt b/appnav/src/main/kotlin/io/element/android/appnav/root/RootView.kt index bd7db5e9c2..2bc76d7a5b 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/root/RootView.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/root/RootView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appnav/src/main/res/values-hr/translations.xml b/appnav/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..f75fa613f2 --- /dev/null +++ b/appnav/src/main/res/values-hr/translations.xml @@ -0,0 +1,6 @@ + + + "Odjava i nadogradnja" + "%1$s više ne podržava stari protokol. Odjavite se i ponovno prijavite kako biste se nastavili služiti aplikacijom." + "Vaš matični poslužitelj više ne podržava stari protokol. Odjavite se i ponovno prijavite kako biste se nastavili služiti aplikacijom." + diff --git a/appnav/src/test/kotlin/io/element/android/appnav/JoinedRoomLoadedFlowNodeTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/JoinedRoomLoadedFlowNodeTest.kt index dfb2638dc1..6cd7df025b 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/JoinedRoomLoadedFlowNodeTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/JoinedRoomLoadedFlowNodeTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -19,22 +20,32 @@ import com.bumble.appyx.testing.unit.common.helper.parentNodeTestHelper import com.google.common.truth.Truth.assertThat import io.element.android.appnav.di.RoomGraphFactory import io.element.android.appnav.room.RoomNavigationTarget +import io.element.android.appnav.room.joined.FakeJoinedRoomLoadedFlowNodeCallback import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode +import io.element.android.features.forward.api.ForwardEntryPoint +import io.element.android.features.forward.test.FakeForwardEntryPoint import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint +import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.libraries.architecture.childNode import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom +import io.element.android.libraries.matrix.test.room.aRoomInfo +import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.services.appnavstate.api.ActiveRoomsHolder +import io.element.android.services.appnavstate.impl.DefaultActiveRoomsHolder import io.element.android.services.appnavstate.test.FakeAppNavigationStateService import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +@RunWith(RobolectricTestRunner::class) class JoinedRoomLoadedFlowNodeTest { @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule() @@ -42,29 +53,20 @@ class JoinedRoomLoadedFlowNodeTest { @get:Rule val mainDispatcherRule = MainDispatcherRule() - private class FakeMessagesEntryPoint : MessagesEntryPoint, MessagesEntryPoint.NodeBuilder { - var buildContext: BuildContext? = null + private class FakeMessagesEntryPoint : MessagesEntryPoint { var nodeId: String? = null var parameters: MessagesEntryPoint.Params? = null var callback: MessagesEntryPoint.Callback? = null - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): MessagesEntryPoint.NodeBuilder { - this.buildContext = buildContext - return this - } - - override fun params(params: MessagesEntryPoint.Params): MessagesEntryPoint.NodeBuilder { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: MessagesEntryPoint.Params, + callback: MessagesEntryPoint.Callback, + ): Node { parameters = params - return this - } - - override fun callback(callback: MessagesEntryPoint.Callback): MessagesEntryPoint.NodeBuilder { this.callback = callback - return this - } - - override fun build(): Node { - return node(buildContext!!) {}.also { + return node(buildContext) {}.also { nodeId = it.id } } @@ -79,22 +81,26 @@ class JoinedRoomLoadedFlowNodeTest { private class FakeRoomDetailsEntryPoint : RoomDetailsEntryPoint { var nodeId: String? = null - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): RoomDetailsEntryPoint.NodeBuilder { - return object : RoomDetailsEntryPoint.NodeBuilder { - override fun params(params: RoomDetailsEntryPoint.Params): RoomDetailsEntryPoint.NodeBuilder { - return this - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: RoomDetailsEntryPoint.Params, + callback: RoomDetailsEntryPoint.Callback, + ) = node(buildContext) {}.also { + nodeId = it.id + } + } - override fun callback(callback: RoomDetailsEntryPoint.Callback): RoomDetailsEntryPoint.NodeBuilder { - return this - } + private class FakeSpaceEntryPoint : SpaceEntryPoint { + var nodeId: String? = null - override fun build(): Node { - return node(buildContext) {}.also { - nodeId = it.id - } - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + inputs: SpaceEntryPoint.Inputs, + callback: SpaceEntryPoint.Callback, + ) = node(buildContext) {}.also { + nodeId = it.id } } @@ -102,27 +108,33 @@ class JoinedRoomLoadedFlowNodeTest { plugins: List, messagesEntryPoint: MessagesEntryPoint = FakeMessagesEntryPoint(), roomDetailsEntryPoint: RoomDetailsEntryPoint = FakeRoomDetailsEntryPoint(), - activeRoomsHolder: ActiveRoomsHolder = ActiveRoomsHolder(), + spaceEntryPoint: SpaceEntryPoint = FakeSpaceEntryPoint(), + forwardEntryPoint: ForwardEntryPoint = FakeForwardEntryPoint(), + activeRoomsHolder: ActiveRoomsHolder = DefaultActiveRoomsHolder(), + matrixClient: FakeMatrixClient = FakeMatrixClient(), ) = JoinedRoomLoadedFlowNode( buildContext = BuildContext.root(savedStateMap = null), plugins = plugins, messagesEntryPoint = messagesEntryPoint, roomDetailsEntryPoint = roomDetailsEntryPoint, + spaceEntryPoint = spaceEntryPoint, + forwardEntryPoint = forwardEntryPoint, appNavigationStateService = FakeAppNavigationStateService(), - sessionCoroutineScope = this, + sessionCoroutineScope = backgroundScope, roomGraphFactory = FakeRoomGraphFactory(), - matrixClient = FakeMatrixClient(), + matrixClient = matrixClient, activeRoomsHolder = activeRoomsHolder, + analyticsService = FakeAnalyticsService(), ) @Test - fun `given a room flow node when initialized then it loads messages entry point`() = runTest { + fun `given a room flow node when initialized then it loads messages entry point if room is not space`() = runTest { // GIVEN - val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(updateMembersResult = {})) + val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(updateMembersResult = {}, initialRoomInfo = aRoomInfo(isSpace = false))) val fakeMessagesEntryPoint = FakeMessagesEntryPoint() - val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Messages()) + val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Root()) val roomFlowNode = createJoinedRoomLoadedFlowNode( - plugins = listOf(inputs), + plugins = listOf(inputs, FakeJoinedRoomLoadedFlowNodeCallback()), messagesEntryPoint = fakeMessagesEntryPoint, ) // WHEN @@ -135,21 +147,41 @@ class JoinedRoomLoadedFlowNodeTest { assertThat(messagesNode.id).isEqualTo(fakeMessagesEntryPoint.nodeId) } + @Test + fun `given a room flow node when initialized then it loads space entry point if room is space`() = runTest { + // GIVEN + val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(updateMembersResult = {}, initialRoomInfo = aRoomInfo(isSpace = true))) + val spaceEntryPoint = FakeSpaceEntryPoint() + val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Root()) + val roomFlowNode = createJoinedRoomLoadedFlowNode( + plugins = listOf(inputs, FakeJoinedRoomLoadedFlowNodeCallback()), + spaceEntryPoint = spaceEntryPoint, + ) + // WHEN + val roomFlowNodeTestHelper = roomFlowNode.parentNodeTestHelper() + + // THEN + assertThat(roomFlowNode.backstack.activeElement).isEqualTo(JoinedRoomLoadedFlowNode.NavTarget.Space) + roomFlowNodeTestHelper.assertChildHasLifecycle(JoinedRoomLoadedFlowNode.NavTarget.Space, Lifecycle.State.CREATED) + val spaceNode = roomFlowNode.childNode(JoinedRoomLoadedFlowNode.NavTarget.Space)!! + assertThat(spaceNode.id).isEqualTo(spaceEntryPoint.nodeId) + } + @Test fun `given a room flow node when callback on room details is triggered then it loads room details entry point`() = runTest { // GIVEN val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(updateMembersResult = {})) val fakeMessagesEntryPoint = FakeMessagesEntryPoint() val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint() - val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Messages()) + val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Root()) val roomFlowNode = createJoinedRoomLoadedFlowNode( - plugins = listOf(inputs), + plugins = listOf(inputs, FakeJoinedRoomLoadedFlowNodeCallback()), messagesEntryPoint = fakeMessagesEntryPoint, roomDetailsEntryPoint = fakeRoomDetailsEntryPoint, ) val roomFlowNodeTestHelper = roomFlowNode.parentNodeTestHelper() // WHEN - fakeMessagesEntryPoint.callback?.onRoomDetailsClick() + fakeMessagesEntryPoint.callback?.navigateToRoomDetails() // THEN roomFlowNodeTestHelper.assertChildHasLifecycle(JoinedRoomLoadedFlowNode.NavTarget.RoomDetails, Lifecycle.State.CREATED) val roomDetailsNode = roomFlowNode.childNode(JoinedRoomLoadedFlowNode.NavTarget.RoomDetails)!! @@ -162,10 +194,10 @@ class JoinedRoomLoadedFlowNodeTest { val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(updateMembersResult = {})) val fakeMessagesEntryPoint = FakeMessagesEntryPoint() val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint() - val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Messages()) - val activeRoomsHolder = ActiveRoomsHolder() + val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Root()) + val activeRoomsHolder = DefaultActiveRoomsHolder() val roomFlowNode = createJoinedRoomLoadedFlowNode( - plugins = listOf(inputs), + plugins = listOf(inputs, FakeJoinedRoomLoadedFlowNodeCallback()), messagesEntryPoint = fakeMessagesEntryPoint, roomDetailsEntryPoint = fakeRoomDetailsEntryPoint, activeRoomsHolder = activeRoomsHolder, @@ -185,12 +217,12 @@ class JoinedRoomLoadedFlowNodeTest { val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(updateMembersResult = {})) val fakeMessagesEntryPoint = FakeMessagesEntryPoint() val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint() - val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Messages()) - val activeRoomsHolder = ActiveRoomsHolder().apply { + val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Root()) + val activeRoomsHolder = DefaultActiveRoomsHolder().apply { addRoom(room) } val roomFlowNode = createJoinedRoomLoadedFlowNode( - plugins = listOf(inputs), + plugins = listOf(inputs, FakeJoinedRoomLoadedFlowNodeCallback()), messagesEntryPoint = fakeMessagesEntryPoint, roomDetailsEntryPoint = fakeRoomDetailsEntryPoint, activeRoomsHolder = activeRoomsHolder, 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 2a343a1592..73d55135fb 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appnav/src/test/kotlin/io/element/android/appnav/SyncOrchestratorTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/SyncOrchestratorTest.kt index f26df83a56..7309ca6ac2 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/SyncOrchestratorTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/SyncOrchestratorTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,6 +13,7 @@ import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.features.networkmonitor.test.FakeNetworkMonitor import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.matrix.test.sync.FakeSyncService +import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.services.appnavstate.test.FakeAppForegroundStateService import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.lambda.lambdaRecorder @@ -389,5 +391,6 @@ class SyncOrchestratorTest { networkMonitor = networkMonitor, appForegroundStateService = appForegroundStateService, dispatchers = testCoroutineDispatchers(), + analyticsService = FakeAnalyticsService(), ) } diff --git a/appnav/src/test/kotlin/io/element/android/appnav/di/MatrixSessionCacheTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/di/MatrixSessionCacheTest.kt index 6df80ee95d..36fa471dd0 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/di/MatrixSessionCacheTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/di/MatrixSessionCacheTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,32 +15,35 @@ import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService +import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.services.appnavstate.test.FakeAppForegroundStateService import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test +@OptIn(ExperimentalCoroutinesApi::class) class MatrixSessionCacheTest { @Test fun `test getOrNull`() = runTest { - val fakeAuthenticationService = FakeMatrixAuthenticationService() - val matrixSessionCache = MatrixSessionCache(fakeAuthenticationService, createSyncOrchestratorFactory()) + val matrixSessionCache = createMatrixSessionCache() assertThat(matrixSessionCache.getOrNull(A_SESSION_ID)).isNull() } @Test fun `test getSyncOrchestratorOrNull`() = runTest { val fakeAuthenticationService = FakeMatrixAuthenticationService() - val matrixSessionCache = MatrixSessionCache(fakeAuthenticationService, createSyncOrchestratorFactory()) + val matrixSessionCache = createMatrixSessionCache(fakeAuthenticationService) // With no matrix client there is no sync orchestrator assertThat(matrixSessionCache.getOrNull(A_SESSION_ID)).isNull() assertThat(matrixSessionCache.getSyncOrchestrator(A_SESSION_ID)).isNull() // But as soon as we receive a client, we can get the sync orchestrator - val fakeMatrixClient = FakeMatrixClient(sessionCoroutineScope = backgroundScope) + val fakeMatrixClient = FakeMatrixClient(sessionCoroutineScope = backgroundScope, userIdServerNameLambda = { A_SESSION_ID.value }) fakeAuthenticationService.givenMatrixClient(fakeMatrixClient) assertThat(matrixSessionCache.getOrRestore(A_SESSION_ID).getOrNull()).isEqualTo(fakeMatrixClient) assertThat(matrixSessionCache.getSyncOrchestrator(A_SESSION_ID)).isNotNull() @@ -48,8 +52,8 @@ class MatrixSessionCacheTest { @Test fun `test getOrRestore`() = runTest { val fakeAuthenticationService = FakeMatrixAuthenticationService() - val matrixSessionCache = MatrixSessionCache(fakeAuthenticationService, createSyncOrchestratorFactory()) - val fakeMatrixClient = FakeMatrixClient(sessionCoroutineScope = backgroundScope) + val matrixSessionCache = createMatrixSessionCache(fakeAuthenticationService) + val fakeMatrixClient = FakeMatrixClient(sessionCoroutineScope = backgroundScope, userIdServerNameLambda = { A_SESSION_ID.value }) fakeAuthenticationService.givenMatrixClient(fakeMatrixClient) assertThat(matrixSessionCache.getOrNull(A_SESSION_ID)).isNull() assertThat(matrixSessionCache.getOrRestore(A_SESSION_ID).getOrNull()).isEqualTo(fakeMatrixClient) @@ -61,8 +65,8 @@ class MatrixSessionCacheTest { @Test fun `test remove`() = runTest { val fakeAuthenticationService = FakeMatrixAuthenticationService() - val matrixSessionCache = MatrixSessionCache(fakeAuthenticationService, createSyncOrchestratorFactory()) - val fakeMatrixClient = FakeMatrixClient(sessionCoroutineScope = backgroundScope) + val matrixSessionCache = createMatrixSessionCache(fakeAuthenticationService) + val fakeMatrixClient = FakeMatrixClient(sessionCoroutineScope = backgroundScope, userIdServerNameLambda = { A_SESSION_ID.value }) fakeAuthenticationService.givenMatrixClient(fakeMatrixClient) assertThat(matrixSessionCache.getOrRestore(A_SESSION_ID).getOrNull()).isEqualTo(fakeMatrixClient) assertThat(matrixSessionCache.getOrNull(A_SESSION_ID)).isEqualTo(fakeMatrixClient) @@ -74,8 +78,8 @@ class MatrixSessionCacheTest { @Test fun `test remove all`() = runTest { val fakeAuthenticationService = FakeMatrixAuthenticationService() - val matrixSessionCache = MatrixSessionCache(fakeAuthenticationService, createSyncOrchestratorFactory()) - val fakeMatrixClient = FakeMatrixClient(sessionCoroutineScope = backgroundScope) + val matrixSessionCache = createMatrixSessionCache(fakeAuthenticationService) + val fakeMatrixClient = FakeMatrixClient(sessionCoroutineScope = backgroundScope, userIdServerNameLambda = { A_SESSION_ID.value }) fakeAuthenticationService.givenMatrixClient(fakeMatrixClient) assertThat(matrixSessionCache.getOrRestore(A_SESSION_ID).getOrNull()).isEqualTo(fakeMatrixClient) assertThat(matrixSessionCache.getOrNull(A_SESSION_ID)).isEqualTo(fakeMatrixClient) @@ -87,8 +91,8 @@ class MatrixSessionCacheTest { @Test fun `test save and restore`() = runTest { val fakeAuthenticationService = FakeMatrixAuthenticationService() - val matrixSessionCache = MatrixSessionCache(fakeAuthenticationService, createSyncOrchestratorFactory()) - val fakeMatrixClient = FakeMatrixClient(sessionCoroutineScope = backgroundScope) + val matrixSessionCache = createMatrixSessionCache(fakeAuthenticationService) + val fakeMatrixClient = FakeMatrixClient(sessionCoroutineScope = backgroundScope, userIdServerNameLambda = { A_SESSION_ID.value }) fakeAuthenticationService.givenMatrixClient(fakeMatrixClient) matrixSessionCache.getOrRestore(A_SESSION_ID) val savedStateMap = MutableSavedStateMapImpl { true } @@ -107,28 +111,45 @@ class MatrixSessionCacheTest { @Test fun `test AuthenticationService listenToNewMatrixClients emits a Client value and we save it`() = runTest { val fakeAuthenticationService = FakeMatrixAuthenticationService() - val matrixSessionCache = MatrixSessionCache(fakeAuthenticationService, createSyncOrchestratorFactory()) + val matrixSessionCache = createMatrixSessionCache(fakeAuthenticationService, createSyncOrchestratorFactory()) assertThat(matrixSessionCache.getOrNull(A_SESSION_ID)).isNull() - fakeAuthenticationService.givenMatrixClient(FakeMatrixClient(sessionId = A_SESSION_ID, sessionCoroutineScope = backgroundScope)) val loginSucceeded = fakeAuthenticationService.login("user", "pass") assertThat(loginSucceeded.isSuccess).isTrue() + + runCurrent() + assertThat(matrixSessionCache.getOrNull(A_SESSION_ID)).isNotNull() } - private fun TestScope.createSyncOrchestratorFactory() = object : SyncOrchestrator.Factory { - override fun create( - syncService: SyncService, - sessionCoroutineScope: CoroutineScope, - ): SyncOrchestrator { - return SyncOrchestrator( - syncService = syncService, - sessionCoroutineScope = sessionCoroutineScope, - appForegroundStateService = FakeAppForegroundStateService(), - networkMonitor = FakeNetworkMonitor(), - dispatchers = testCoroutineDispatchers(), - ) + private fun TestScope.createMatrixSessionCache( + authenticationService: FakeMatrixAuthenticationService = FakeMatrixAuthenticationService(), + syncOrchestratorFactory: SyncOrchestrator.Factory = createSyncOrchestratorFactory(), + analyticsService: FakeAnalyticsService = FakeAnalyticsService(), + ) = MatrixSessionCache( + authenticationService = authenticationService, + syncOrchestratorFactory = syncOrchestratorFactory, + analyticsService = analyticsService, + ) + + private fun TestScope.createSyncOrchestratorFactory(): SyncOrchestrator.Factory { + val dispatchers = testCoroutineDispatchers() + + return object : SyncOrchestrator.Factory { + override fun create( + syncService: SyncService, + sessionCoroutineScope: CoroutineScope, + ): SyncOrchestrator { + return SyncOrchestrator( + syncService = syncService, + sessionCoroutineScope = sessionCoroutineScope, + appForegroundStateService = FakeAppForegroundStateService(), + networkMonitor = FakeNetworkMonitor(), + dispatchers = dispatchers, + analyticsService = FakeAnalyticsService(), + ) + } } } } diff --git a/appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt index def1f33253..bf673602c1 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/intent/IntentResolverTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,6 +18,7 @@ import io.element.android.features.login.test.FakeLoginIntentResolver import io.element.android.libraries.deeplink.api.DeeplinkData import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.permalink.PermalinkData +import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_THREAD_ID @@ -67,6 +69,7 @@ class IntentResolverTest { sessionId = A_SESSION_ID, roomId = A_ROOM_ID, threadId = null, + eventId = null, ) ) val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply { @@ -79,6 +82,7 @@ class IntentResolverTest { sessionId = A_SESSION_ID, roomId = A_ROOM_ID, threadId = null, + eventId = null, ) ) ) @@ -91,6 +95,7 @@ class IntentResolverTest { sessionId = A_SESSION_ID, roomId = A_ROOM_ID, threadId = A_THREAD_ID, + eventId = null, ) ) val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply { @@ -103,6 +108,59 @@ class IntentResolverTest { sessionId = A_SESSION_ID, roomId = A_ROOM_ID, threadId = A_THREAD_ID, + eventId = null, + ) + ) + ) + } + + @Test + fun `test resolve navigation intent event`() { + val sut = createIntentResolver( + deeplinkParserResult = DeeplinkData.Room( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + threadId = null, + eventId = AN_EVENT_ID, + ) + ) + val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply { + action = Intent.ACTION_VIEW + } + val result = sut.resolve(intent) + assertThat(result).isEqualTo( + ResolvedIntent.Navigation( + deeplinkData = DeeplinkData.Room( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + threadId = null, + eventId = AN_EVENT_ID, + ) + ) + ) + } + + @Test + fun `test resolve navigation intent thread and event`() { + val sut = createIntentResolver( + deeplinkParserResult = DeeplinkData.Room( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + threadId = A_THREAD_ID, + eventId = AN_EVENT_ID, + ) + ) + val intent = Intent(RuntimeEnvironment.getApplication(), Activity::class.java).apply { + action = Intent.ACTION_VIEW + } + val result = sut.resolve(intent) + assertThat(result).isEqualTo( + ResolvedIntent.Navigation( + deeplinkData = DeeplinkData.Room( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + threadId = A_THREAD_ID, + eventId = AN_EVENT_ID, ) ) ) diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/AnalyticsVerificationStateMappingTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/AnalyticsVerificationStateMappingTest.kt index e965d15c6e..50b9df5820 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/AnalyticsVerificationStateMappingTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/AnalyticsVerificationStateMappingTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 47cbda4b91..849dfa85b2 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -33,6 +34,7 @@ import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.libraries.matrix.test.sync.FakeSyncService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService import io.element.android.libraries.push.api.PushService +import io.element.android.libraries.push.api.PusherRegistrationFailure import io.element.android.libraries.push.test.FakePushService import io.element.android.libraries.pushproviders.api.Distributor import io.element.android.libraries.pushproviders.api.PushProvider @@ -41,7 +43,6 @@ import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.consumeItemsUntilPredicate -import io.element.android.tests.testutils.lambda.any import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value @@ -114,7 +115,9 @@ class LoggedInPresenterTest { encryptionService = encryptionService, ), syncService = FakeSyncService(initialSyncState = SyncState.Running), - pushService = FakePushService(), + pushService = FakePushService( + ensurePusherIsRegisteredResult = { Result.success(Unit) }, + ), sessionVerificationService = verificationService, analyticsService = analyticsService, encryptionService = encryptionService, @@ -138,10 +141,10 @@ class LoggedInPresenterTest { @Test fun `present - ensure default pusher is not registered if session is not verified`() = runTest { - val lambda = lambdaRecorder> { _, _, _ -> + val lambda = lambdaRecorder> { Result.success(Unit) } - val pushService = createFakePushService(registerWithLambda = lambda) + val pushService = createFakePushService(ensurePusherIsRegisteredResult = lambda) val verificationService = FakeSessionVerificationService( initialSessionVerifiedStatus = SessionVerifiedStatus.NotVerified ) @@ -152,21 +155,18 @@ class LoggedInPresenterTest { val finalState = awaitFirstItem() assertThat(finalState.pusherRegistrationState.errorOrNull()) .isInstanceOf(PusherRegistrationFailure.AccountNotVerified::class.java) - lambda.assertions() - .isNeverCalled() + lambda.assertions().isNeverCalled() } } @Test fun `present - ensure default pusher is registered with default provider`() = runTest { - val lambda = lambdaRecorder> { _, _, _ -> - Result.success(Unit) - } + val lambda = lambdaRecorder> { Result.success(Unit) } val sessionVerificationService = FakeSessionVerificationService( initialSessionVerifiedStatus = SessionVerifiedStatus.Verified ) val pushService = createFakePushService( - registerWithLambda = lambda, + ensurePusherIsRegisteredResult = lambda, ) createLoggedInPresenter( pushService = pushService, @@ -179,27 +179,17 @@ class LoggedInPresenterTest { assertThat(finalState.pusherRegistrationState.isSuccess()).isTrue() lambda.assertions() .isCalledOnce() - .with( - // MatrixClient - any(), - // PushProvider with highest priority (lower index) - value(pushService.getAvailablePushProviders()[0]), - // First distributor - value(pushService.getAvailablePushProviders()[0].getDistributors()[0]), - ) } } @Test fun `present - ensure default pusher is registered with default provider - fail to register`() = runTest { - val lambda = lambdaRecorder> { _, _, _ -> - Result.failure(AN_EXCEPTION) - } + val lambda = lambdaRecorder> { Result.failure(AN_EXCEPTION) } val sessionVerificationService = FakeSessionVerificationService( initialSessionVerifiedStatus = SessionVerifiedStatus.Verified ) val pushService = createFakePushService( - registerWithLambda = lambda, + ensurePusherIsRegisteredResult = lambda, ) createLoggedInPresenter( pushService = pushService, @@ -212,158 +202,36 @@ class LoggedInPresenterTest { assertThat(finalState.pusherRegistrationState.isFailure()).isTrue() lambda.assertions() .isCalledOnce() - .with( - // MatrixClient - any(), - // PushProvider with highest priority (lower index) - value(pushService.getAvailablePushProviders()[0]), - // First distributor - value(pushService.getAvailablePushProviders()[0].getDistributors()[0]), - ) + // Reset the error and do not show again + finalState.eventSink(LoggedInEvents.CloseErrorDialog(doNotShowAgain = false)) + val lastState = awaitItem() + assertThat(lastState.pusherRegistrationState.isUninitialized()).isTrue() + assertThat(lastState.ignoreRegistrationError).isFalse() } } @Test - fun `present - ensure current provider is registered with current distributor`() = runTest { - val lambda = lambdaRecorder> { _, _, _ -> - Result.success(Unit) - } - val sessionVerificationService = FakeSessionVerificationService( - initialSessionVerifiedStatus = SessionVerifiedStatus.Verified - ) - val distributor = Distributor("aDistributorValue1", "aDistributorName1") - val pushProvider = FakePushProvider( - index = 0, - name = "aFakePushProvider0", - distributors = listOf( - Distributor("aDistributorValue0", "aDistributorName0"), - distributor, - ), - currentDistributor = { distributor }, - ) - val pushService = createFakePushService( - pushProvider1 = pushProvider, - currentPushProvider = { pushProvider }, - registerWithLambda = lambda, - ) - createLoggedInPresenter( - pushService = pushService, - sessionVerificationService = sessionVerificationService, - matrixClient = FakeMatrixClient( - accountManagementUrlResult = { Result.success(null) }, - ), - ).test { - val finalState = awaitFirstItem() - assertThat(finalState.pusherRegistrationState.isSuccess()).isTrue() - lambda.assertions() - .isCalledOnce() - .with( - // MatrixClient - any(), - // Current push provider - value(pushProvider), - // Current distributor - value(distributor), - ) - } - } - - @Test - fun `present - if current push provider does not have current distributor, the first one is used`() = runTest { - val lambda = lambdaRecorder> { _, _, _ -> - Result.success(Unit) - } - val sessionVerificationService = FakeSessionVerificationService( - initialSessionVerifiedStatus = SessionVerifiedStatus.Verified - ) - val pushProvider = FakePushProvider( - index = 0, - name = "aFakePushProvider0", - distributors = listOf( - Distributor("aDistributorValue0", "aDistributorName0"), - Distributor("aDistributorValue1", "aDistributorName1"), - ), - currentDistributor = { null }, - ) - val pushService = createFakePushService( - pushProvider0 = pushProvider, - currentPushProvider = { pushProvider }, - registerWithLambda = lambda, - ) - createLoggedInPresenter( - pushService = pushService, - sessionVerificationService = sessionVerificationService, - matrixClient = FakeMatrixClient( - accountManagementUrlResult = { Result.success(null) }, - ), - ).test { - val finalState = awaitFirstItem() - assertThat(finalState.pusherRegistrationState.isSuccess()).isTrue() - lambda.assertions() - .isCalledOnce() - .with( - // MatrixClient - any(), - // PushProvider with highest priority (lower index) - value(pushService.getAvailablePushProviders()[0]), - // First distributor - value(pushService.getAvailablePushProviders()[0].getDistributors()[0]), - ) - } - } - - @Test - fun `present - if current push provider does not have distributors, nothing happen`() = runTest { - val lambda = lambdaRecorder> { _, _, _ -> - Result.success(Unit) - } - val sessionVerificationService = FakeSessionVerificationService( - initialSessionVerifiedStatus = SessionVerifiedStatus.Verified - ) - val pushProvider = FakePushProvider( - index = 0, - name = "aFakePushProvider0", - distributors = emptyList(), - ) - val pushService = createFakePushService( - pushProvider0 = pushProvider, - currentPushProvider = { pushProvider }, - registerWithLambda = lambda, - ) - createLoggedInPresenter( - pushService = pushService, - sessionVerificationService = sessionVerificationService, - ).test { - val finalState = awaitFirstItem() - assertThat(finalState.pusherRegistrationState.errorOrNull()) - .isInstanceOf(PusherRegistrationFailure.NoDistributorsAvailable::class.java) - lambda.assertions() - .isNeverCalled() - } - } - - @Test - fun `present - case no push provider available provider`() = runTest { - val lambda = lambdaRecorder> { _, _, _ -> - Result.success(Unit) - } - val sessionVerificationService = FakeSessionVerificationService(SessionVerifiedStatus.Verified) + fun `present - ensure default pusher is registered with default provider - fail to register - do not show again`() = runTest { + val lambda = lambdaRecorder> { Result.failure(AN_EXCEPTION) } val setIgnoreRegistrationErrorLambda = lambdaRecorder { _, _ -> } + val sessionVerificationService = FakeSessionVerificationService( + initialSessionVerifiedStatus = SessionVerifiedStatus.Verified + ) val pushService = createFakePushService( - pushProvider0 = null, - pushProvider1 = null, - registerWithLambda = lambda, + ensurePusherIsRegisteredResult = lambda, setIgnoreRegistrationErrorLambda = setIgnoreRegistrationErrorLambda, ) createLoggedInPresenter( pushService = pushService, sessionVerificationService = sessionVerificationService, + matrixClient = FakeMatrixClient( + accountManagementUrlResult = { Result.success(null) }, + ), ).test { val finalState = awaitFirstItem() - assertThat(finalState.pusherRegistrationState.errorOrNull()) - .isInstanceOf(PusherRegistrationFailure.NoProvidersAvailable::class.java) + assertThat(finalState.pusherRegistrationState.isFailure()).isTrue() lambda.assertions() - .isNeverCalled() + .isCalledOnce() // Reset the error and do not show again finalState.eventSink(LoggedInEvents.CloseErrorDialog(doNotShowAgain = true)) skipItems(1) @@ -381,95 +249,6 @@ class LoggedInPresenterTest { } } - @Test - fun `present - case one push provider but no distributor available`() = runTest { - val lambda = lambdaRecorder> { _, _, _ -> - Result.success(Unit) - } - val selectPushProviderLambda = lambdaRecorder { _, _ -> } - val sessionVerificationService = FakeSessionVerificationService( - initialSessionVerifiedStatus = SessionVerifiedStatus.Verified - ) - val pushProvider = FakePushProvider( - index = 0, - name = "aFakePushProvider", - distributors = emptyList(), - ) - val pushService = createFakePushService( - pushProvider0 = pushProvider, - pushProvider1 = null, - registerWithLambda = lambda, - selectPushProviderLambda = selectPushProviderLambda, - ) - createLoggedInPresenter( - pushService = pushService, - sessionVerificationService = sessionVerificationService, - ).test { - val finalState = awaitFirstItem() - assertThat(finalState.pusherRegistrationState.errorOrNull()) - .isInstanceOf(PusherRegistrationFailure.NoDistributorsAvailable::class.java) - lambda.assertions() - .isNeverCalled() - selectPushProviderLambda.assertions() - .isCalledOnce() - .with( - // SessionId - value(A_SESSION_ID), - // PushProvider - value(pushProvider), - ) - // Reset the error - finalState.eventSink(LoggedInEvents.CloseErrorDialog(doNotShowAgain = false)) - val lastState = awaitItem() - assertThat(lastState.pusherRegistrationState.isUninitialized()).isTrue() - } - } - - @Test - fun `present - case two push providers but first one does not have distributor - second one will be used`() = runTest { - val lambda = lambdaRecorder> { _, _, _ -> - Result.success(Unit) - } - val sessionVerificationService = FakeSessionVerificationService( - initialSessionVerifiedStatus = SessionVerifiedStatus.Verified - ) - val pushProvider0 = FakePushProvider( - index = 0, - name = "aFakePushProvider0", - distributors = emptyList(), - ) - val distributor = Distributor("aDistributorValue1", "aDistributorName1") - val pushProvider1 = FakePushProvider( - index = 1, - name = "aFakePushProvider1", - distributors = listOf(distributor), - ) - val pushService = createFakePushService( - pushProvider0 = pushProvider0, - pushProvider1 = pushProvider1, - registerWithLambda = lambda, - ) - createLoggedInPresenter( - pushService = pushService, - sessionVerificationService = sessionVerificationService, - matrixClient = FakeMatrixClient( - accountManagementUrlResult = { Result.success(null) }, - ), - ).test { - val finalState = awaitFirstItem() - assertThat(finalState.pusherRegistrationState.isSuccess()).isTrue() - lambda.assertions().isCalledOnce() - .with( - // MatrixClient - any(), - // PushProvider with the distributor - value(pushProvider1), - // First distributor of second push provider - value(distributor), - ) - } - } - private fun createFakePushService( pushProvider0: PushProvider? = FakePushProvider( index = 0, @@ -483,16 +262,16 @@ class LoggedInPresenterTest { distributors = listOf(Distributor("aDistributorValue1", "aDistributorName1")), currentDistributor = { null }, ), - registerWithLambda: (MatrixClient, PushProvider, Distributor) -> Result = { _, _, _ -> + ensurePusherIsRegisteredResult: () -> Result = { Result.success(Unit) }, selectPushProviderLambda: (SessionId, PushProvider) -> Unit = { _, _ -> lambdaError() }, - currentPushProvider: () -> PushProvider? = { null }, + currentPushProvider: (SessionId) -> PushProvider? = { null }, setIgnoreRegistrationErrorLambda: (SessionId, Boolean) -> Unit = { _, _ -> lambdaError() }, ): PushService { return FakePushService( availablePushProviders = listOfNotNull(pushProvider0, pushProvider1), - registerWithLambda = registerWithLambda, + ensurePusherIsRegisteredResult = ensurePusherIsRegisteredResult, currentPushProvider = currentPushProvider, selectPushProviderLambda = selectPushProviderLambda, setIgnoreRegistrationErrorLambda = setIgnoreRegistrationErrorLambda, diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/MediaPreviewConfigMigrationTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/MediaPreviewConfigMigrationTest.kt index 459abc64d4..8e081bdad7 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/MediaPreviewConfigMigrationTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/MediaPreviewConfigMigrationTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/SendQueuesTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/SendQueuesTest.kt index c117dfb8f7..3c5d8dc076 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/SendQueuesTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/SendQueuesTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/appnav/src/test/kotlin/io/element/android/appnav/room/LoadingBaseRoomStateFlowFactoryTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/room/LoadingBaseRoomStateFlowFactoryTest.kt index 91e0b9f968..14128ac33a 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/room/LoadingBaseRoomStateFlowFactoryTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/room/LoadingBaseRoomStateFlowFactoryTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -22,6 +23,19 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class LoadingBaseRoomStateFlowFactoryTest { + @Test + fun `flow should emit only Loaded when we already pass a JoinedRoom`() = runTest { + val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(sessionId = A_SESSION_ID, roomId = A_ROOM_ID)) + val matrixClient = FakeMatrixClient(A_SESSION_ID) + val flowFactory = LoadingRoomStateFlowFactory(matrixClient) + flowFactory + .create(lifecycleScope = this, roomId = A_ROOM_ID, joinedRoom = room) + .test { + assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loaded(room)) + ensureAllEventsConsumed() + } + } + @Test fun `flow should emit Loading and then Loaded when there is a room in cache`() = runTest { val room = FakeJoinedRoom(baseRoom = FakeBaseRoom(sessionId = A_SESSION_ID, roomId = A_ROOM_ID)) @@ -30,7 +44,7 @@ class LoadingBaseRoomStateFlowFactoryTest { } val flowFactory = LoadingRoomStateFlowFactory(matrixClient) flowFactory - .create(this, A_ROOM_ID) + .create(lifecycleScope = this, roomId = A_ROOM_ID, joinedRoom = null) .test { assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading) assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loaded(room)) @@ -44,7 +58,7 @@ class LoadingBaseRoomStateFlowFactoryTest { val matrixClient = FakeMatrixClient(A_SESSION_ID, roomListService = roomListService) val flowFactory = LoadingRoomStateFlowFactory(matrixClient) flowFactory - .create(this, A_ROOM_ID) + .create(lifecycleScope = this, roomId = A_ROOM_ID, joinedRoom = null) .test { assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading) matrixClient.givenGetRoomResult(A_ROOM_ID, room) @@ -59,7 +73,7 @@ class LoadingBaseRoomStateFlowFactoryTest { val matrixClient = FakeMatrixClient(A_SESSION_ID, roomListService = roomListService) val flowFactory = LoadingRoomStateFlowFactory(matrixClient) flowFactory - .create(this, A_ROOM_ID) + .create(lifecycleScope = this, roomId = A_ROOM_ID, joinedRoom = null) .test { assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading) roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1)) diff --git a/appnav/src/test/kotlin/io/element/android/appnav/room/joined/FakeJoinedRoomLoadedFlowNodeCallback.kt b/appnav/src/test/kotlin/io/element/android/appnav/room/joined/FakeJoinedRoomLoadedFlowNodeCallback.kt new file mode 100644 index 0000000000..40778ae353 --- /dev/null +++ b/appnav/src/test/kotlin/io/element/android/appnav/room/joined/FakeJoinedRoomLoadedFlowNodeCallback.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.appnav.room.joined + +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.permalink.PermalinkData +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeJoinedRoomLoadedFlowNodeCallback : JoinedRoomLoadedFlowNode.Callback { + override fun navigateToRoom(roomId: RoomId, serverNames: List) = lambdaError() + override fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) = lambdaError() + override fun navigateToGlobalNotificationSettings() = lambdaError() +} diff --git a/build.gradle.kts b/build.gradle.kts index b7662aeb56..61e17c149c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,10 @@ +import org.gradle.accessors.dm.LibrariesForLibs + /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -27,6 +30,8 @@ tasks.register("clean").configure { delete(rootProject.layout.buildDirectory) } +private val ktLintVersion = the().versions.ktlint.get() + allprojects { // Detekt apply { @@ -41,7 +46,7 @@ allprojects { config.from(files("$rootDir/tools/detekt/detekt.yml")) } dependencies { - detektPlugins("io.nlopez.compose.rules:detekt:0.4.27") + detektPlugins("io.nlopez.compose.rules:detekt:0.5.3") detektPlugins(project(":tests:detekt-rules")) } @@ -56,14 +61,12 @@ allprojects { // See https://github.com/JLLeitschuh/ktlint-gradle#configuration configure { - // See https://github.com/pinterest/ktlint/releases/ - // TODO Regularly check for new version here ^ - version.set("1.1.1") - android.set(true) - ignoreFailures.set(false) - enableExperimentalRules.set(true) + version = ktLintVersion + android = true + ignoreFailures = false + enableExperimentalRules = true // display the corresponding rule - verbose.set(true) + verbose = true reporters { reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.PLAIN) // To have XML report for Danger diff --git a/codegen/build.gradle.kts b/codegen/build.gradle.kts index 640c9ee366..5c71c170bf 100644 --- a/codegen/build.gradle.kts +++ b/codegen/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/codegen/src/main/kotlin/io/element/android/codegen/ContributesNodeProcessor.kt b/codegen/src/main/kotlin/io/element/android/codegen/ContributesNodeProcessor.kt index 15bb4afbe0..7ddf80b92c 100644 --- a/codegen/src/main/kotlin/io/element/android/codegen/ContributesNodeProcessor.kt +++ b/codegen/src/main/kotlin/io/element/android/codegen/ContributesNodeProcessor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/codegen/src/main/kotlin/io/element/android/codegen/ContributesNodeProcessorProvider.kt b/codegen/src/main/kotlin/io/element/android/codegen/ContributesNodeProcessorProvider.kt index 9631167419..8412fbf847 100644 --- a/codegen/src/main/kotlin/io/element/android/codegen/ContributesNodeProcessorProvider.kt +++ b/codegen/src/main/kotlin/io/element/android/codegen/ContributesNodeProcessorProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/enterprise b/enterprise index ffc02b8d0f..6207ddc1cb 160000 --- a/enterprise +++ b/enterprise @@ -1 +1 @@ -Subproject commit ffc02b8d0f35188c3ef8a876dc1532bfe3e533da +Subproject commit 6207ddc1cb7dac7fdb6212c0158497a1d9752c75 diff --git a/fastlane/metadata/android/en-US/changelogs/202510010.txt b/fastlane/metadata/android/en-US/changelogs/202510010.txt new file mode 100644 index 0000000000..3c916fed88 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/202510010.txt @@ -0,0 +1,2 @@ +Main changes in this version: bug fixes around notifications and UX improvements. +Full changelog: https://github.com/element-hq/element-x-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/202511000.txt b/fastlane/metadata/android/en-US/changelogs/202511000.txt new file mode 100644 index 0000000000..8afd7460fc --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/202511000.txt @@ -0,0 +1,2 @@ +Main changes in this version: fixes an issue that prevented Element Call notifications from being displayed sometimes. +Full changelog: https://github.com/element-hq/element-x-android/releases diff --git a/fastlane/metadata/android/en-US/changelogs/202511020.txt b/fastlane/metadata/android/en-US/changelogs/202511020.txt new file mode 100644 index 0000000000..a4b397f1bb --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/202511020.txt @@ -0,0 +1,2 @@ +Main changes in this version: bug fixes and improvements. +Full changelog: https://github.com/element-hq/element-x-android/releases \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/202511030.txt b/fastlane/metadata/android/en-US/changelogs/202511030.txt new file mode 100644 index 0000000000..a4b397f1bb --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/202511030.txt @@ -0,0 +1,2 @@ +Main changes in this version: bug fixes and improvements. +Full changelog: https://github.com/element-hq/element-x-android/releases \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/202512000.txt b/fastlane/metadata/android/en-US/changelogs/202512000.txt new file mode 100644 index 0000000000..e287435043 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/202512000.txt @@ -0,0 +1,6 @@ +Main changes in this version: +- Improve the room security and privacy screens. +- Better room list sorting. +- Fixed crashes when recording long voice messages. +- Improved the UX when opening a room from the room list. +Full changelog: https://github.com/element-hq/element-x-android/releases \ No newline at end of file diff --git a/features/analytics/api/build.gradle.kts b/features/analytics/api/build.gradle.kts index 4bf21e265e..5c7f0b223a 100644 --- a/features/analytics/api/build.gradle.kts +++ b/features/analytics/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsEntryPoint.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsEntryPoint.kt index 37a78fd727..5d5c1ba286 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsEntryPoint.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsOptInEvents.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsOptInEvents.kt index 74d26c4f04..4181f52ba8 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsOptInEvents.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/AnalyticsOptInEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesState.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesState.kt index 4cb3767c72..20a180446c 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesState.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesStateProvider.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesStateProvider.kt index 647b205722..02e07a86a2 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesStateProvider.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt index 70461f22d3..e91c77079f 100644 --- a/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt +++ b/features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/analytics/api/src/main/res/values-hr/translations.xml b/features/analytics/api/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..ac1e83c87b --- /dev/null +++ b/features/analytics/api/src/main/res/values-hr/translations.xml @@ -0,0 +1,7 @@ + + + "Podijelite anonimne podatke o korištenju kako biste nam pomogli u otkrivanju problema." + "Možete pročitati sve naše uvjete %1$s ." + "ovdje" + "Dijeljenje analitičkih podataka" + diff --git a/features/analytics/impl/build.gradle.kts b/features/analytics/impl/build.gradle.kts index ddf093d9c3..cdb172f888 100644 --- a/features/analytics/impl/build.gradle.kts +++ b/features/analytics/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInNode.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInNode.kt index b952bdb4e1..545f306a68 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInNode.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt index fd590955c8..d7bdb85e35 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -27,7 +28,7 @@ class AnalyticsOptInPresenter( override fun present(): AnalyticsOptInState { val localCoroutineScope = rememberCoroutineScope() - fun handleEvents(event: AnalyticsOptInEvents) { + fun handleEvent(event: AnalyticsOptInEvents) { when (event) { is AnalyticsOptInEvents.EnableAnalytics -> localCoroutineScope.setIsEnabled(event.isEnabled) } @@ -39,7 +40,7 @@ class AnalyticsOptInPresenter( return AnalyticsOptInState( applicationName = buildMeta.applicationName, hasPolicyLink = AnalyticsConfig.POLICY_LINK.isNotEmpty(), - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInState.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInState.kt index a0913bdb4f..b2ebd37e20 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInState.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInStateProvider.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInStateProvider.kt index ec37542b31..30a396c178 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInStateProvider.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt index f04f4dc273..4fc9ce5c6b 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/DefaultAnalyticsEntryPoint.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/DefaultAnalyticsEntryPoint.kt index 134535e881..fb85387950 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/DefaultAnalyticsEntryPoint.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/DefaultAnalyticsEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,12 +12,10 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.analytics.api.AnalyticsEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultAnalyticsEntryPoint : AnalyticsEntryPoint { override fun createNode(parentNode: Node, buildContext: BuildContext): Node { return parentNode.createNode(buildContext) diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/di/AnalyticsModule.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/di/AnalyticsModule.kt index e5d3342e2d..32a3d5826a 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/di/AnalyticsModule.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/di/AnalyticsModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenter.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenter.kt index fee6188d23..110cb98d41 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenter.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -30,7 +31,7 @@ class AnalyticsPreferencesPresenter( val localCoroutineScope = rememberCoroutineScope() val isEnabled = analyticsService.userConsentFlow.collectAsState(initial = false) - fun handleEvents(event: AnalyticsOptInEvents) { + fun handleEvent(event: AnalyticsOptInEvents) { when (event) { is AnalyticsOptInEvents.EnableAnalytics -> localCoroutineScope.setIsEnabled(event.isEnabled) } @@ -40,7 +41,7 @@ class AnalyticsPreferencesPresenter( applicationName = buildMeta.applicationName, isEnabled = isEnabled.value, policyUrl = AnalyticsConfig.POLICY_LINK, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/analytics/impl/src/main/res/values-hr/translations.xml b/features/analytics/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..b85046c7a9 --- /dev/null +++ b/features/analytics/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,10 @@ + + + "Nećemo bilježiti niti profilirati nikakve osobne podatke" + "Podijelite anonimne podatke o korištenju kako biste nam pomogli u otkrivanju problema." + "Možete pročitati sve naše uvjete %1$s ." + "ovdje" + "Ovo možete isključiti u bilo kojem trenutku" + "Nećemo dijeliti vaše podatke s trećim stranama" + "Pomozite nam poboljšati %1$s" + 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 e9319fd7ba..6521c7f597 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/DefaultAnalyticsEntryPointTest.kt b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/DefaultAnalyticsEntryPointTest.kt index b5ec819632..0340bd3d68 100644 --- a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/DefaultAnalyticsEntryPointTest.kt +++ b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/DefaultAnalyticsEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 8ba915f03c..174935a1ad 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/announcement/api/build.gradle.kts b/features/announcement/api/build.gradle.kts index 0fa87f039e..0c2f5bb7b9 100644 --- a/features/announcement/api/build.gradle.kts +++ b/features/announcement/api/build.gradle.kts @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/features/announcement/api/src/main/kotlin/io/element/android/features/announcement/api/Announcement.kt b/features/announcement/api/src/main/kotlin/io/element/android/features/announcement/api/Announcement.kt index 21b83e7449..0bf35650a0 100644 --- a/features/announcement/api/src/main/kotlin/io/element/android/features/announcement/api/Announcement.kt +++ b/features/announcement/api/src/main/kotlin/io/element/android/features/announcement/api/Announcement.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/announcement/api/src/main/kotlin/io/element/android/features/announcement/api/AnnouncementService.kt b/features/announcement/api/src/main/kotlin/io/element/android/features/announcement/api/AnnouncementService.kt index a98c0af199..42c66aa29d 100644 --- a/features/announcement/api/src/main/kotlin/io/element/android/features/announcement/api/AnnouncementService.kt +++ b/features/announcement/api/src/main/kotlin/io/element/android/features/announcement/api/AnnouncementService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/announcement/impl/build.gradle.kts b/features/announcement/impl/build.gradle.kts index 222d080d6e..443e343e43 100644 --- a/features/announcement/impl/build.gradle.kts +++ b/features/announcement/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/AnnouncementPresenter.kt b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/AnnouncementPresenter.kt index 432aad4a0a..508f1e44a0 100644 --- a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/AnnouncementPresenter.kt +++ b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/AnnouncementPresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/AnnouncementState.kt b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/AnnouncementState.kt index c8ea728d64..e762dd607f 100644 --- a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/AnnouncementState.kt +++ b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/AnnouncementState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/DefaultAnnouncementService.kt b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/DefaultAnnouncementService.kt index 6d616f14e9..0e5c30178c 100644 --- a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/DefaultAnnouncementService.kt +++ b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/DefaultAnnouncementService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,7 +17,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.announcement.api.Announcement import io.element.android.features.announcement.api.AnnouncementService import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementState @@ -29,7 +29,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first @ContributesBinding(AppScope::class) -@Inject class DefaultAnnouncementService( private val announcementStore: AnnouncementStore, private val announcementPresenter: Presenter, diff --git a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/di/AnnouncementModule.kt b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/di/AnnouncementModule.kt index 4fbc9118bc..4cfc073271 100644 --- a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/di/AnnouncementModule.kt +++ b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/di/AnnouncementModule.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementEvents.kt b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementEvents.kt index 9741608b1e..3b968d09a6 100644 --- a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementEvents.kt +++ b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementPresenter.kt b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementPresenter.kt index 4d1b2f3e4b..7c4bc7b5eb 100644 --- a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementPresenter.kt +++ b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementPresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -24,7 +25,7 @@ class SpaceAnnouncementPresenter( override fun present(): SpaceAnnouncementState { val localCoroutineScope = rememberCoroutineScope() - fun handleEvents(event: SpaceAnnouncementEvents) { + fun handleEvent(event: SpaceAnnouncementEvents) { when (event) { SpaceAnnouncementEvents.Continue -> localCoroutineScope.launch { announcementStore.setAnnouncementStatus(Announcement.Space, AnnouncementStatus.Shown) @@ -33,7 +34,7 @@ class SpaceAnnouncementPresenter( } return SpaceAnnouncementState( - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } } diff --git a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementState.kt b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementState.kt index 7628ed27ae..9407fad872 100644 --- a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementState.kt +++ b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementStateProvider.kt b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementStateProvider.kt index d994edf3d8..27f48cc7ed 100644 --- a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementStateProvider.kt +++ b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementView.kt b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementView.kt index 2a8c5257aa..3fe6ec4456 100644 --- a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementView.kt +++ b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -80,7 +81,7 @@ private fun SpaceAnnouncementHeader( showBetaLabel = true, subTitle = stringResource(id = R.string.screen_space_announcement_subtitle), iconStyle = BigIcon.Style.Default( - vectorIcon = CompoundIcons.WorkspaceSolid(), + vectorIcon = CompoundIcons.SpaceSolid(), usePrimaryTint = true, ), ) diff --git a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/store/AnnouncementStatus.kt b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/store/AnnouncementStatus.kt index e275a140e1..5f3dc7d0e4 100644 --- a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/store/AnnouncementStatus.kt +++ b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/store/AnnouncementStatus.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/store/AnnouncementStore.kt b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/store/AnnouncementStore.kt index 3385165c52..c818e90c4a 100644 --- a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/store/AnnouncementStore.kt +++ b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/store/AnnouncementStore.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/store/DefaultAnnouncementStore.kt b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/store/DefaultAnnouncementStore.kt index 37acf9f6b4..ad166e4ef5 100644 --- a/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/store/DefaultAnnouncementStore.kt +++ b/features/announcement/impl/src/main/kotlin/io/element/android/features/announcement/impl/store/DefaultAnnouncementStore.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,6 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.intPreferencesKey import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.announcement.api.Announcement import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory import kotlinx.coroutines.flow.Flow @@ -21,7 +21,6 @@ private val spaceAnnouncementKey = intPreferencesKey("spaceAnnouncement") private val newNotificationSoundKey = intPreferencesKey("newNotificationSound") @ContributesBinding(AppScope::class) -@Inject class DefaultAnnouncementStore( preferenceDataStoreFactory: PreferenceDataStoreFactory, ) : AnnouncementStore { diff --git a/features/announcement/impl/src/main/res/values-bg/translations.xml b/features/announcement/impl/src/main/res/values-bg/translations.xml new file mode 100644 index 0000000000..853cf5f027 --- /dev/null +++ b/features/announcement/impl/src/main/res/values-bg/translations.xml @@ -0,0 +1,4 @@ + + + "Присъединете се към обществени пространства" + diff --git a/features/announcement/impl/src/main/res/values-cs/translations.xml b/features/announcement/impl/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..cf7ead1962 --- /dev/null +++ b/features/announcement/impl/src/main/res/values-cs/translations.xml @@ -0,0 +1,11 @@ + + + "Zobrazit prostory, které jste vytvořili nebo ke kterým jste se připojili" + "Přijmout nebo odmítnout pozvánky do prostorů" + "Objevte všechny místnosti, do kterých můžete vstoupit ve svých prostorech" + "Připojit se k veřejným prostorům" + "Opustit všechny prostory, ke kterým jste se připojili" + "Filtrování, vytváření a správa prostorů bude brzy k dispozici." + "Vítejte v beta verzi prostorů! S touto první verzí můžete:" + "Představujeme prostory" + diff --git a/features/announcement/impl/src/main/res/values-da/translations.xml b/features/announcement/impl/src/main/res/values-da/translations.xml index 933ae95d6d..d07871b19e 100644 --- a/features/announcement/impl/src/main/res/values-da/translations.xml +++ b/features/announcement/impl/src/main/res/values-da/translations.xml @@ -1,11 +1,11 @@ - "Se klynger, du har oprettet eller tilmeldt dig" - "Acceptere eller afvise invitationer til klynger" - "Finde alle rum, du kan deltage i, i dine klynger" - "Deltage i offentlige klynger" - "Forlade de klynger, du har tilsluttet dig" - "Oprettelse og administration af klynger kommer snart." - "Velkommen til betaversionen af Klynger! Med denne første version kan du:" - "Introduktion til Klynger" + "Se grupper, du har oprettet eller tilmeldt dig" + "Acceptere eller afvise invitationer til grupper" + "Finde alle rum, du kan deltage i, i dine grupper" + "Deltage i offentlige grupper" + "Forlade de grupper, du har tilsluttet dig" + "Filtrering, oprettelse og administration af grupper kommer snart." + "Velkommen til betaversionen af Grupper! Med denne første version kan du:" + "Introduktion til Grupper" diff --git a/features/announcement/impl/src/main/res/values-de/translations.xml b/features/announcement/impl/src/main/res/values-de/translations.xml index c38a9b5cd9..11f5f3a99c 100644 --- a/features/announcement/impl/src/main/res/values-de/translations.xml +++ b/features/announcement/impl/src/main/res/values-de/translations.xml @@ -5,7 +5,7 @@ "Chats innerhalb deiner Spaces entdecken, um ihnen beizutreten" "Öffentlichen Spaces beitreten" "Spaces verlassen, bei denen du Mitglied bist" - "Das Erstellen und Verwalten von Spaces ist bald verfügbar." + "Das Filtern, Erstellen und Verwalten von Spaces ist bald verfügbar." "Willkommen bei der Beta-Version von Spaces! Mit dieser ersten Version kannst du:" "Einführung in Spaces" diff --git a/features/announcement/impl/src/main/res/values-et/translations.xml b/features/announcement/impl/src/main/res/values-et/translations.xml new file mode 100644 index 0000000000..ee2ba9c3b4 --- /dev/null +++ b/features/announcement/impl/src/main/res/values-et/translations.xml @@ -0,0 +1,11 @@ + + + "Vaadata kogukondi, mille oled loonud või millega oled liitunud" + "Nõustuda kutsetega liitumiseks kogukonnaga või sellest keelduda" + "Uurida neis kogukondades leiduvaid jututube ning nendega liituda" + "Liituda avalike kogukondadega" + "Lahkuda kogukonnast, millega oled liitunud" + "Kogukondade filtreerimine, loomine ja haldamine lisandub peagi" + "Tere tulemast kasutama kogukondade beetaversiooni! Selles esimeses versioonis saad sa:" + "Võtame kasutusele kogukonnad" + diff --git a/features/announcement/impl/src/main/res/values-fa/translations.xml b/features/announcement/impl/src/main/res/values-fa/translations.xml new file mode 100644 index 0000000000..2e8902ae23 --- /dev/null +++ b/features/announcement/impl/src/main/res/values-fa/translations.xml @@ -0,0 +1,11 @@ + + + "دیدن فضاهایی که ساخته یا پیوسته‌اید" + "پذیرش یا رد دعوت‌ها به فضاها" + "کشف تمامی اتاق‌هایی که می‌توانید در فضاهایتان بپیوندید" + "پیوستن به فضاهای عمومی" + "ترک هر فضایی که پیوسته‌اید" + "پالایش، ایجاد و مدیریت کردن فضاها به زودی." + "به نگارش آزمایشی فضاها خوش آمدید! در این نگارش می‌توانید:" + "معرّفی فضاها" + diff --git a/features/announcement/impl/src/main/res/values-fi/translations.xml b/features/announcement/impl/src/main/res/values-fi/translations.xml index a5de5465ec..8e7674487f 100644 --- a/features/announcement/impl/src/main/res/values-fi/translations.xml +++ b/features/announcement/impl/src/main/res/values-fi/translations.xml @@ -5,7 +5,7 @@ "Löytää kaikki huoneet, joihin voit liittyä tiloissasi" "Liittyä julkisiin tiloihin" "Poistua mistä tahansa tilasta, johon olet liittynyt" - "Tilojen luominen ja hallinta on tulossa pian." + "Tilojen suodatus, luominen ja hallinta on tulossa pian." "Tervetuloa tilojen beetaversioon! Tämän ensimmäisen version avulla voit:" "Esittelyssä tilat" diff --git a/features/announcement/impl/src/main/res/values-fr/translations.xml b/features/announcement/impl/src/main/res/values-fr/translations.xml index 04a35b0fc0..7e042c65ff 100644 --- a/features/announcement/impl/src/main/res/values-fr/translations.xml +++ b/features/announcement/impl/src/main/res/values-fr/translations.xml @@ -5,7 +5,7 @@ "Découvrir les salons que vous pouvez joindre depuis vos espaces" "Rejoindre les espaces publics" "Quitter les espaces dont vous êtes membre." - "La création et la gestion des espaces seront bientôt disponibles." + "Le filtrage, la création et la gestion des espaces seront bientôt disponibles." "Bienvenue dans la version bêta des espaces! Avec cette première version, vous pourrez :" "Ajout des espaces" diff --git a/features/announcement/impl/src/main/res/values-hr/translations.xml b/features/announcement/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..e78f29f19a --- /dev/null +++ b/features/announcement/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,11 @@ + + + "Pregledajte prostore koje ste stvorili ili kojima ste se pridružili" + "Prihvatite ili odbijte pozivnice za prostore" + "Otkrijte sve sobe kojima se možete pridružiti u svojim prostorima" + "Pridružite se javnim prostorima" + "Napustite sve prostore kojima ste se pridružili" + "Uskoro stiže filtriranje i stvaranje prostora te upravljanje njima." + "Dobrodošli u beta inačicu prostora! S ovom prvom inačicom možete:" + "Predstavljamo prostore" + diff --git a/features/announcement/impl/src/main/res/values-hu/translations.xml b/features/announcement/impl/src/main/res/values-hu/translations.xml new file mode 100644 index 0000000000..b09f70419b --- /dev/null +++ b/features/announcement/impl/src/main/res/values-hu/translations.xml @@ -0,0 +1,11 @@ + + + "Az Ön által létrehozott vagy csatlakozott térek megtekintése" + "A meghívások elfogadására vagy elutasítására a terekhez" + "Szobák felfedezése a terekben, amelyekhez csatlakozhat" + "Csatlakozás nyilvános terekhez" + "Terek elhagyása" + "Terek szűrése, készítése és kezelése hamarosan érkezik." + "Üdvözöljük a tér béta verziójában! Ezzel az első verzióval a következőket teheti:" + "Bemutatkoznak a terek" + diff --git a/features/announcement/impl/src/main/res/values-it/translations.xml b/features/announcement/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..584ddcdf21 --- /dev/null +++ b/features/announcement/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,11 @@ + + + "Visualizza gli spazi che hai creato o a cui partecipi" + "Accetta o rifiuta gli inviti agli spazi" + "Scopri tutte le stanze a cui puoi partecipare nei tuoi spazi" + "Unisciti agli spazi pubblici" + "Lascia tutti gli spazi a cui ti sei unito" + "A breve saranno disponibili le funzionalità di filtraggio, creazione e gestione degli spazi." + "Benvenuti alla versione beta degli Spazi! Con questa prima versione potrete:" + "Ti presentiamo gli Spazi" + diff --git a/features/announcement/impl/src/main/res/values-nb/translations.xml b/features/announcement/impl/src/main/res/values-nb/translations.xml index 0765e557f8..553ff9f997 100644 --- a/features/announcement/impl/src/main/res/values-nb/translations.xml +++ b/features/announcement/impl/src/main/res/values-nb/translations.xml @@ -5,7 +5,7 @@ "Oppdag alle rom du kan bli med i i dine områder" "Bli med i offentlige områder" "Forlat områder du har blitt med i" - "Oppretting og administrasjon av områder kommer snart." + "Oppretting, filtrering og administrasjon av områder kommer snart." "Velkommen til betaversjonen av Områder! Med denne første versjonen kan du:" "Vi introduserer Områder" diff --git a/features/announcement/impl/src/main/res/values-pl/translations.xml b/features/announcement/impl/src/main/res/values-pl/translations.xml new file mode 100644 index 0000000000..4308bdd81d --- /dev/null +++ b/features/announcement/impl/src/main/res/values-pl/translations.xml @@ -0,0 +1,11 @@ + + + "Wyświetlić przestrzenie, które stworzyłeś lub do których dołączyłeś" + "Akceptować lub odrzucać zaproszenia" + "Odkrywać wszystkie pokoje, do których możesz dołączyć w swoich przestrzeniach" + "Dołączać do przestrzeni publicznych" + "Opuszczać jakąkolwiek przestrzeń, do której dołączyłeś" + "Filtrowanie, tworzenie i zarządzanie przestrzeniami pojawi się wkrótce." + "Witamy w wersji beta przestrzeni! W tej wersji możesz:" + "Przedstawiamy przestrzenie" + diff --git a/features/announcement/impl/src/main/res/values-pt-rBR/translations.xml b/features/announcement/impl/src/main/res/values-pt-rBR/translations.xml new file mode 100644 index 0000000000..32a9bf85af --- /dev/null +++ b/features/announcement/impl/src/main/res/values-pt-rBR/translations.xml @@ -0,0 +1,11 @@ + + + "Visualizar espaços que criou ou entrou" + "Aceitar ou recusar convites aos espaços" + "Descobrir quaisquer salas que você pode entrar nos espaços" + "Entrar espaços públicos" + "Sair de quaisquer espaços que entrou" + "Filtrar, criar, e gerenciar espaços virão em breve." + "Boas-vindas à versão beta dos Espaços! Com essa primeira versão, você pode:" + "Apresentando Espaços" + diff --git a/features/announcement/impl/src/main/res/values-ro/translations.xml b/features/announcement/impl/src/main/res/values-ro/translations.xml index 48fa06fca4..716f1faeb2 100644 --- a/features/announcement/impl/src/main/res/values-ro/translations.xml +++ b/features/announcement/impl/src/main/res/values-ro/translations.xml @@ -5,7 +5,7 @@ "Descoperiți toate camerele la care vă puteți alătura în spațiile dumneavoastră." "Alăturați-vă spațiilor publice" "Părăsiți spațiile la care v-ați alăturat." - "Crearea și gestionarea spațiilor vor fi disponibile în curând." + "Filtrarea, crearea și gestionarea spațiilor vor fi disponibile în curând." "Bun venit la versiunea beta a Spațiilor! Cu această primă versiune puteți:" "Vă prezentăm Spații" diff --git a/features/announcement/impl/src/main/res/values-ru/translations.xml b/features/announcement/impl/src/main/res/values-ru/translations.xml index 8b930b2ca0..7ff445bd69 100644 --- a/features/announcement/impl/src/main/res/values-ru/translations.xml +++ b/features/announcement/impl/src/main/res/values-ru/translations.xml @@ -5,7 +5,7 @@ "Откройте для себя все комнаты, к которым вы можете присоединиться в своих пространствах." "Присоединиться к публичному пространству" "Покинуть все пространства, к которым вы присоединились" - "Создание и управление пространствами станет доступно в ближайшее время." + "Работа с пространствами скоро станет доступна" "Добро пожаловать в бета-версию Spaces! В этой первой версии вы сможете:" - "Знакомство с пространствами" + "Представляем пространства" diff --git a/features/announcement/impl/src/main/res/values-sk/translations.xml b/features/announcement/impl/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..0b305499a7 --- /dev/null +++ b/features/announcement/impl/src/main/res/values-sk/translations.xml @@ -0,0 +1,11 @@ + + + "Zobraziť priestory, ktoré ste vytvorili alebo ku ktorým ste sa pripojili" + "Prijímať alebo odmietať pozvánky do priestorov" + "Objaviť všetky miestnosti, do ktorých sa môžete pripojiť vo svojich priestoroch" + "Pripojiť sa k verejnému priestoru" + "Opustiť akékoľvek priestory, ku ktorým ste sa pridali" + "Filtrovanie, vytváranie a správa priestorov bude čoskoro k dispozícii." + "Vitajte v beta verzii priestorov! S touto prvou verziou môžete:" + "Predstavujeme priestory" + diff --git a/features/announcement/impl/src/main/res/values-zh-rTW/translations.xml b/features/announcement/impl/src/main/res/values-zh-rTW/translations.xml new file mode 100644 index 0000000000..a5b82752bc --- /dev/null +++ b/features/announcement/impl/src/main/res/values-zh-rTW/translations.xml @@ -0,0 +1,11 @@ + + + "檢視您建立或加入的空間" + "接受或拒絕空間邀請" + "探索空間內您可以加入的任何聊天室" + "加入公開空間" + "離開任何您已加入的空間" + "篩選、建立與管理空間功能即將推出。" + "歡迎使用空間的測試版!此初始版本可讓您:" + "介紹空間" + diff --git a/features/announcement/impl/src/main/res/values-zh/translations.xml b/features/announcement/impl/src/main/res/values-zh/translations.xml index 66608eb6e5..e01e63b2ae 100644 --- a/features/announcement/impl/src/main/res/values-zh/translations.xml +++ b/features/announcement/impl/src/main/res/values-zh/translations.xml @@ -2,4 +2,10 @@ "查看您创建或加入的空间" "接受或拒绝空间邀请" + "发现您可以加入空间的所有房间" + "加入公共空间" + "离开你加入的所有空间" + "筛选、创建及管理空间功能即将上线。" + "欢迎使用 Spaces 测试版!使用首个版本,您可以:" + "Spaces 简介" diff --git a/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/AnnouncementPresenterTest.kt b/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/AnnouncementPresenterTest.kt index 9290773b30..18deb8b2fd 100644 --- a/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/AnnouncementPresenterTest.kt +++ b/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/AnnouncementPresenterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/DefaultAnnouncementServiceTest.kt b/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/DefaultAnnouncementServiceTest.kt index 990f506223..e16619129c 100644 --- a/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/DefaultAnnouncementServiceTest.kt +++ b/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/DefaultAnnouncementServiceTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementPresenterTest.kt b/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementPresenterTest.kt index 2c35bccf41..672f677407 100644 --- a/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementPresenterTest.kt +++ b/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementPresenterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementViewTest.kt b/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementViewTest.kt index 96b98668a4..ad3d83f1b5 100644 --- a/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementViewTest.kt +++ b/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/spaces/SpaceAnnouncementViewTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/store/InMemoryAnnouncementStore.kt b/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/store/InMemoryAnnouncementStore.kt index f7d438784a..ab3e85124f 100644 --- a/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/store/InMemoryAnnouncementStore.kt +++ b/features/announcement/impl/src/test/kotlin/io/element/android/features/announcement/impl/store/InMemoryAnnouncementStore.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/announcement/test/build.gradle.kts b/features/announcement/test/build.gradle.kts index 9387dc0caf..d9e9251798 100644 --- a/features/announcement/test/build.gradle.kts +++ b/features/announcement/test/build.gradle.kts @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/features/announcement/test/src/main/kotlin/io/element/android/features/rageshake/test/logs/FakeAnnouncementService.kt b/features/announcement/test/src/main/kotlin/io/element/android/features/rageshake/test/logs/FakeAnnouncementService.kt index 74c2adf95f..fefb61d722 100644 --- a/features/announcement/test/src/main/kotlin/io/element/android/features/rageshake/test/logs/FakeAnnouncementService.kt +++ b/features/announcement/test/src/main/kotlin/io/element/android/features/rageshake/test/logs/FakeAnnouncementService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/cachecleaner/api/build.gradle.kts b/features/cachecleaner/api/build.gradle.kts index 81f689bb63..705107298a 100644 --- a/features/cachecleaner/api/build.gradle.kts +++ b/features/cachecleaner/api/build.gradle.kts @@ -1,9 +1,8 @@ -import extension.setupDependencyInjection - /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,8 +14,6 @@ android { namespace = "io.element.android.features.cachecleaner.api" } -setupDependencyInjection() - dependencies { implementation(projects.libraries.architecture) implementation(libs.androidx.startup) diff --git a/features/cachecleaner/api/src/main/kotlin/io/element/android/features/cachecleaner/api/CacheCleaner.kt b/features/cachecleaner/api/src/main/kotlin/io/element/android/features/cachecleaner/api/CacheCleaner.kt index 192886ef17..b5a5396b16 100644 --- a/features/cachecleaner/api/src/main/kotlin/io/element/android/features/cachecleaner/api/CacheCleaner.kt +++ b/features/cachecleaner/api/src/main/kotlin/io/element/android/features/cachecleaner/api/CacheCleaner.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/cachecleaner/impl/build.gradle.kts b/features/cachecleaner/impl/build.gradle.kts index 8603de6322..3321e2a80f 100644 --- a/features/cachecleaner/impl/build.gradle.kts +++ b/features/cachecleaner/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/cachecleaner/api/src/main/kotlin/io/element/android/features/cachecleaner/api/CacheCleanerBindings.kt b/features/cachecleaner/impl/src/main/kotlin/io/element/android/features/cachecleaner/impl/CacheCleanerBindings.kt similarity index 60% rename from features/cachecleaner/api/src/main/kotlin/io/element/android/features/cachecleaner/api/CacheCleanerBindings.kt rename to features/cachecleaner/impl/src/main/kotlin/io/element/android/features/cachecleaner/impl/CacheCleanerBindings.kt index 9be2a10525..2137b7b905 100644 --- a/features/cachecleaner/api/src/main/kotlin/io/element/android/features/cachecleaner/api/CacheCleanerBindings.kt +++ b/features/cachecleaner/impl/src/main/kotlin/io/element/android/features/cachecleaner/impl/CacheCleanerBindings.kt @@ -1,14 +1,16 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.cachecleaner.api +package io.element.android.features.cachecleaner.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesTo +import io.element.android.features.cachecleaner.api.CacheCleaner @ContributesTo(AppScope::class) interface CacheCleanerBindings { diff --git a/features/cachecleaner/impl/src/main/kotlin/io/element/android/features/cachecleaner/impl/DefaultCacheCleaner.kt b/features/cachecleaner/impl/src/main/kotlin/io/element/android/features/cachecleaner/impl/DefaultCacheCleaner.kt index fc06174806..44351412f0 100644 --- a/features/cachecleaner/impl/src/main/kotlin/io/element/android/features/cachecleaner/impl/DefaultCacheCleaner.kt +++ b/features/cachecleaner/impl/src/main/kotlin/io/element/android/features/cachecleaner/impl/DefaultCacheCleaner.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.features.cachecleaner.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.cachecleaner.api.CacheCleaner import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.extensions.runCatchingExceptions @@ -24,7 +24,6 @@ import java.io.File * Default implementation of [CacheCleaner]. */ @ContributesBinding(AppScope::class) -@Inject class DefaultCacheCleaner( @AppCoroutineScope private val coroutineScope: CoroutineScope, diff --git a/features/cachecleaner/impl/src/test/kotlin/io/element/android/features/cachecleaner/impl/DefaultCacheCleanerTest.kt b/features/cachecleaner/impl/src/test/kotlin/io/element/android/features/cachecleaner/impl/DefaultCacheCleanerTest.kt index 997c0fe703..6094f1e16d 100644 --- a/features/cachecleaner/impl/src/test/kotlin/io/element/android/features/cachecleaner/impl/DefaultCacheCleanerTest.kt +++ b/features/cachecleaner/impl/src/test/kotlin/io/element/android/features/cachecleaner/impl/DefaultCacheCleanerTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/api/build.gradle.kts b/features/call/api/build.gradle.kts index c9c86e8948..1480a31fd3 100644 --- a/features/call/api/build.gradle.kts +++ b/features/call/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/api/src/main/kotlin/io/element/android/features/call/api/CallType.kt b/features/call/api/src/main/kotlin/io/element/android/features/call/api/CallType.kt index 2c279b7725..5beb9f7c54 100644 --- a/features/call/api/src/main/kotlin/io/element/android/features/call/api/CallType.kt +++ b/features/call/api/src/main/kotlin/io/element/android/features/call/api/CallType.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/api/src/main/kotlin/io/element/android/features/call/api/CurrentCall.kt b/features/call/api/src/main/kotlin/io/element/android/features/call/api/CurrentCall.kt index 42fd4e873d..a6932a148e 100644 --- a/features/call/api/src/main/kotlin/io/element/android/features/call/api/CurrentCall.kt +++ b/features/call/api/src/main/kotlin/io/element/android/features/call/api/CurrentCall.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/api/src/main/kotlin/io/element/android/features/call/api/CurrentCallService.kt b/features/call/api/src/main/kotlin/io/element/android/features/call/api/CurrentCallService.kt index f7abf845c5..25720596ba 100644 --- a/features/call/api/src/main/kotlin/io/element/android/features/call/api/CurrentCallService.kt +++ b/features/call/api/src/main/kotlin/io/element/android/features/call/api/CurrentCallService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/api/src/main/kotlin/io/element/android/features/call/api/ElementCallEntryPoint.kt b/features/call/api/src/main/kotlin/io/element/android/features/call/api/ElementCallEntryPoint.kt index 8de7a3839b..caa557f4de 100644 --- a/features/call/api/src/main/kotlin/io/element/android/features/call/api/ElementCallEntryPoint.kt +++ b/features/call/api/src/main/kotlin/io/element/android/features/call/api/ElementCallEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/build.gradle.kts b/features/call/impl/build.gradle.kts index 9efe5ba75b..e77c09e19a 100644 --- a/features/call/impl/build.gradle.kts +++ b/features/call/impl/build.gradle.kts @@ -4,9 +4,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -73,7 +74,7 @@ dependencies { implementation(projects.libraries.designsystem) implementation(projects.libraries.featureflag.api) implementation(projects.libraries.matrix.impl) - implementation(projects.libraries.matrixui) + implementation(projects.libraries.matrixmedia.api) implementation(projects.libraries.network) implementation(projects.libraries.preferences.api) implementation(projects.libraries.push.api) @@ -93,8 +94,10 @@ dependencies { testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.preferences.test) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.matrixmedia.test) testImplementation(projects.libraries.push.test) testImplementation(projects.services.analytics.test) + testImplementation(projects.services.appnavstate.impl) testImplementation(projects.services.appnavstate.test) testImplementation(projects.services.toolbox.test) } diff --git a/features/call/impl/src/main/AndroidManifest.xml b/features/call/impl/src/main/AndroidManifest.xml index dfc3c13632..daf1a910c9 100644 --- a/features/call/impl/src/main/AndroidManifest.xml +++ b/features/call/impl/src/main/AndroidManifest.xml @@ -1,7 +1,8 @@ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/DefaultElementCallEntryPoint.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/DefaultElementCallEntryPoint.kt index a1c07462f8..9ed479f5b8 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/DefaultElementCallEntryPoint.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/DefaultElementCallEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.features.call.impl import android.content.Context import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.call.api.CallType import io.element.android.features.call.api.ElementCallEntryPoint import io.element.android.features.call.impl.notifications.CallNotificationData @@ -21,7 +21,6 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId @ContributesBinding(AppScope::class) -@Inject class DefaultElementCallEntryPoint( @ApplicationContext private val context: Context, private val activeCallManager: ActiveCallManager, diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/data/WidgetMessage.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/data/WidgetMessage.kt index 5001d598d9..421ac9d37a 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/data/WidgetMessage.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/data/WidgetMessage.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/di/CallBindings.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/di/CallBindings.kt index 887da8d188..66efe82d38 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/di/CallBindings.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/di/CallBindings.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/notifications/CallNotificationData.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/notifications/CallNotificationData.kt index 0bfbfb0411..dcc434e84d 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/notifications/CallNotificationData.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/notifications/CallNotificationData.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/notifications/RingingCallNotificationCreator.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/notifications/RingingCallNotificationCreator.kt index 2f8ce3e53a..e7d270ef8e 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/notifications/RingingCallNotificationCreator.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/notifications/RingingCallNotificationCreator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.call.impl.notifications @@ -21,6 +22,8 @@ import io.element.android.features.call.api.CallType import io.element.android.features.call.impl.receivers.DeclineCallBroadcastReceiver import io.element.android.features.call.impl.ui.IncomingCallActivity import io.element.android.features.call.impl.utils.IntentProvider +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.matrix.api.MatrixClientProvider @@ -69,11 +72,19 @@ class RingingCallNotificationCreator( ): Notification? { val matrixClient = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return null val imageLoader = imageLoaderHolder.get(matrixClient) - val largeIcon = notificationBitmapLoader.getUserIcon(roomAvatarUrl, imageLoader) + val userIcon = notificationBitmapLoader.getUserIcon( + avatarData = AvatarData( + id = roomId.value, + name = roomName, + url = roomAvatarUrl, + size = AvatarSize.RoomDetailsHeader, + ), + imageLoader = imageLoader, + ) val caller = Person.Builder() .setName(senderDisplayName) - .setIcon(largeIcon) + .setIcon(userIcon) .setImportant(true) .build() @@ -122,12 +133,8 @@ class RingingCallNotificationCreator( .setWhen(timestamp) .setOngoing(true) .setShowWhen(false) - .apply { - if (textContent != null) { - setContentText(textContent) - // Else the content text is set by the style (will be "Incoming call") - } - } + // If textContent is null, the content text is set by the style (will be "Incoming call") + .setContentText(textContent) .setSound(Settings.System.DEFAULT_RINGTONE_URI, AudioManager.STREAM_RING) .setTimeoutAfter(ElementCallConfig.RINGING_CALL_DURATION_SECONDS.seconds.inWholeMilliseconds) .setContentIntent(answerIntent) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureEvents.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureEvents.kt index 79b075cf92..9522d44b22 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureEvents.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenter.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenter.kt index 8cca46f9f4..5125b464dc 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenter.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureState.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureState.kt index 1bd84b409a..b1fef4f28b 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureState.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureStateProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureStateProvider.kt index f2afcd0196..6324820eec 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureStateProvider.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PictureInPictureStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PipSupportProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PipSupportProvider.kt index 96e68fabf3..54109f0180 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PipSupportProvider.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PipSupportProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,6 @@ import android.os.Build import androidx.annotation.ChecksSdkIntAtLeast import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.di.annotations.ApplicationContext @@ -23,7 +23,6 @@ interface PipSupportProvider { } @ContributesBinding(AppScope::class) -@Inject class DefaultPipSupportProvider( @ApplicationContext private val context: Context, ) : PipSupportProvider { diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PipView.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PipView.kt index 593d37a1f2..90f74a37fd 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PipView.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/pip/PipView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt index 22b5f0477d..d2cbb0184d 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/receivers/DeclineCallBroadcastReceiver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/services/CallForegroundService.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/services/CallForegroundService.kt index b779465a34..89c9fdb52c 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/services/CallForegroundService.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/services/CallForegroundService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -21,7 +22,6 @@ import androidx.core.app.NotificationManagerCompat import androidx.core.app.PendingIntentCompat import androidx.core.app.ServiceCompat import androidx.core.content.ContextCompat -import androidx.core.graphics.drawable.IconCompat import io.element.android.features.call.impl.R import io.element.android.features.call.impl.ui.ElementCallActivity import io.element.android.libraries.core.extensions.runCatchingExceptions @@ -68,7 +68,7 @@ class CallForegroundService : Service() { val callActivityIntent = Intent(this, ElementCallActivity::class.java) val pendingIntent = PendingIntentCompat.getActivity(this, 0, callActivityIntent, 0, false) val notification = NotificationCompat.Builder(this, foregroundServiceChannel.id) - .setSmallIcon(IconCompat.createWithResource(this, CommonDrawables.ic_notification)) + .setSmallIcon(CommonDrawables.ic_notification) .setContentTitle(getString(R.string.call_foreground_service_title_android)) .setContentText(getString(R.string.call_foreground_service_message_android)) .setContentIntent(pendingIntent) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenEvents.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenEvents.kt index 45bfa81e38..8fbbce896f 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenEvents.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt index 06929093f5..7fbcf1ba71 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -64,6 +65,7 @@ class CallScreenPresenter( private val appForegroundStateService: AppForegroundStateService, @AppCoroutineScope private val appCoroutineScope: CoroutineScope, + private val widgetMessageSerializer: WidgetMessageSerializer, ) : Presenter { @AssistedFactory interface Factory { @@ -160,7 +162,7 @@ class CallScreenPresenter( } } - fun handleEvents(event: CallScreenEvents) { + fun handleEvent(event: CallScreenEvents) { when (event) { is CallScreenEvents.Hangup -> { val widgetId = callWidgetDriver.value?.id @@ -200,7 +202,7 @@ class CallScreenPresenter( userAgent = userAgent, isCallActive = isWidgetLoaded, isInWidgetMode = isInWidgetMode, - eventSink = { handleEvents(it) }, + eventSink = ::handleEvent, ) } @@ -258,7 +260,7 @@ class CallScreenPresenter( } private fun parseMessage(message: String): WidgetMessage? { - return WidgetMessageSerializer.deserialize(message).getOrNull() + return widgetMessageSerializer.deserialize(message).getOrNull() } private fun sendHangupMessage(widgetId: String, messageInterceptor: WidgetMessageInterceptor) { @@ -269,7 +271,7 @@ class CallScreenPresenter( action = WidgetMessage.Action.HangUp, data = null, ) - messageInterceptor.sendMessage(WidgetMessageSerializer.serialize(message)) + messageInterceptor.sendMessage(widgetMessageSerializer.serialize(message)) } private fun CoroutineScope.close(widgetDriver: MatrixWidgetDriver?, navigator: CallScreenNavigator) = launch(dispatchers.io) { diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenState.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenState.kt index 5b1288db42..c07594aebb 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenState.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt index 77680a8e00..3e72f96f87 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt index 88d2e81f8b..f8657a9ece 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenView.kt @@ -1,14 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.call.impl.ui import android.annotation.SuppressLint -import android.util.Log import android.view.ViewGroup import android.webkit.ConsoleMessage import android.webkit.JavascriptInterface @@ -60,6 +60,7 @@ interface CallScreenNavigator { internal fun CallScreenView( state: CallScreenState, pipState: PictureInPictureState, + onConsoleMessage: (ConsoleMessage) -> Unit, requestPermissions: (Array, RequestPermissionCallback) -> Unit, modifier: Modifier = Modifier, ) { @@ -108,6 +109,7 @@ internal fun CallScreenView( val callback: RequestPermissionCallback = { request.grant(it) } requestPermissions(androidPermissions.toTypedArray(), callback) }, + onConsoleMessage = onConsoleMessage, onCreateWebView = { webView -> webView.addBackHandler(onBackPressed = ::handleBack) val interceptor = WebViewWidgetMessageInterceptor( @@ -174,6 +176,7 @@ private fun CallWebView( url: AsyncData, userAgent: String, onPermissionsRequest: (PermissionRequest) -> Unit, + onConsoleMessage: (ConsoleMessage) -> Unit, onCreateWebView: (WebView) -> Unit, onDestroyWebView: (WebView) -> Unit, modifier: Modifier = Modifier, @@ -188,7 +191,11 @@ private fun CallWebView( factory = { context -> WebView(context).apply { onCreateWebView(this) - setup(userAgent, onPermissionsRequest) + setup( + userAgent = userAgent, + onPermissionsRequested = onPermissionsRequest, + onConsoleMessage = onConsoleMessage, + ) } }, update = { webView -> @@ -208,6 +215,7 @@ private fun CallWebView( private fun WebView.setup( userAgent: String, onPermissionsRequested: (PermissionRequest) -> Unit, + onConsoleMessage: (ConsoleMessage) -> Unit, ) { layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, @@ -232,35 +240,7 @@ private fun WebView.setup( } override fun onConsoleMessage(consoleMessage: ConsoleMessage): Boolean { - val priority = when (consoleMessage.messageLevel()) { - ConsoleMessage.MessageLevel.ERROR -> Log.ERROR - ConsoleMessage.MessageLevel.WARNING -> Log.WARN - else -> Log.DEBUG - } - - val message = buildString { - append(consoleMessage.sourceId()) - append(":") - append(consoleMessage.lineNumber()) - append(" ") - append(consoleMessage.message()) - } - - if (message.contains("password=")) { - // Avoid logging any messages that contain "password" to prevent leaking sensitive information - return true - } - - Timber.tag("WebView").log( - priority = priority, - message = buildString { - append(consoleMessage.sourceId()) - append(":") - append(consoleMessage.lineNumber()) - append(" ") - append(consoleMessage.message()) - }, - ) + onConsoleMessage(consoleMessage) return true } } @@ -286,6 +266,7 @@ internal fun CallScreenViewPreview( state = state, pipState = aPictureInPictureState(), requestPermissions = { _, _ -> }, + onConsoleMessage = {}, ) } diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallTypeExtension.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallTypeExtension.kt new file mode 100644 index 0000000000..0c18c3e1a4 --- /dev/null +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallTypeExtension.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.call.impl.ui + +import io.element.android.features.call.api.CallType +import io.element.android.libraries.matrix.api.core.SessionId + +fun CallType.getSessionId(): SessionId? { + return when (this) { + is CallType.ExternalUrl -> null + is CallType.RoomCall -> sessionId + } +} diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt index bf210b0a42..bf4f836294 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -23,14 +24,17 @@ import androidx.appcompat.app.AppCompatActivity import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.core.app.PictureInPictureModeChangedInfo import androidx.core.content.IntentCompat import androidx.core.util.Consumer import androidx.lifecycle.Lifecycle import dev.zacsweers.metro.Inject +import io.element.android.compound.colors.SemanticColorsLightDark import io.element.android.features.call.api.CallType import io.element.android.features.call.api.CallType.ExternalUrl import io.element.android.features.call.impl.DefaultElementCallEntryPoint @@ -42,6 +46,7 @@ import io.element.android.features.call.impl.pip.PipView import io.element.android.features.call.impl.services.CallForegroundService import io.element.android.features.call.impl.utils.CallIntentDataParser import io.element.android.features.enterprise.api.EnterpriseService +import io.element.android.libraries.androidutils.browser.ConsoleMessageLogger import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.bindings import io.element.android.libraries.audio.api.AudioFocus @@ -65,6 +70,7 @@ class ElementCallActivity : @Inject lateinit var pictureInPicturePresenter: PictureInPicturePresenter @Inject lateinit var buildMeta: BuildMeta @Inject lateinit var audioFocus: AudioFocus + @Inject lateinit var consoleMessageLogger: ConsoleMessageLogger private lateinit var presenter: Presenter @@ -103,9 +109,13 @@ class ElementCallActivity : setContent { val pipState = pictureInPicturePresenter.present() ListenToAndroidEvents(pipState) + val colors by remember(webViewTarget.value?.getSessionId()) { + enterpriseService.semanticColorsFlow(sessionId = webViewTarget.value?.getSessionId()) + }.collectAsState(SemanticColorsLightDark.default) ElementThemeApp( appPreferencesStore = appPreferencesStore, - enterpriseService = enterpriseService, + compoundLight = colors.light, + compoundDark = colors.dark, buildMeta = buildMeta, ) { val state = presenter.present() @@ -119,6 +129,9 @@ class ElementCallActivity : CallScreenView( state = state, pipState = pipState, + onConsoleMessage = { + consoleMessageLogger.log("ElementCall", it) + }, requestPermissions = { permissions, callback -> requestPermissionCallback = callback requestPermissionsLauncher.launch(permissions) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt index daf0e26797..714360a702 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallActivity.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,9 +12,13 @@ import android.os.Bundle import android.view.WindowManager import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.core.content.IntentCompat import androidx.lifecycle.lifecycleScope import dev.zacsweers.metro.Inject +import io.element.android.compound.colors.SemanticColorsLightDark import io.element.android.features.call.api.CallType import io.element.android.features.call.api.ElementCallEntryPoint import io.element.android.features.call.impl.di.CallBindings @@ -78,9 +83,13 @@ class IncomingCallActivity : AppCompatActivity() { val notificationData = intent?.let { IntentCompat.getParcelableExtra(it, EXTRA_NOTIFICATION_DATA, CallNotificationData::class.java) } if (notificationData != null) { setContent { + val colors by remember { + enterpriseService.semanticColorsFlow(sessionId = notificationData.sessionId) + }.collectAsState(SemanticColorsLightDark.default) ElementThemeApp( appPreferencesStore = appPreferencesStore, - enterpriseService = enterpriseService, + compoundLight = colors.light, + compoundDark = colors.dark, buildMeta = buildMeta, ) { IncomingCallScreen( diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallScreen.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallScreen.kt index 9483b91a14..682c4cec73 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallScreen.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/IncomingCallScreen.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/LanguageTagProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/LanguageTagProvider.kt index 74016fd210..c9abd7b735 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/LanguageTagProvider.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/LanguageTagProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalConfiguration import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject interface LanguageTagProvider { @Composable @@ -19,7 +19,6 @@ interface LanguageTagProvider { } @ContributesBinding(AppScope::class) -@Inject class DefaultLanguageTagProvider : LanguageTagProvider { @Composable override fun provideLanguageTag(): String? { diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt index 34f46d1ea0..4183e22531 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,7 +18,6 @@ import coil3.SingletonImageLoader import coil3.annotation.DelicateCoilApi import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.appconfig.ElementCallConfig import io.element.android.features.call.api.CallType @@ -87,7 +87,6 @@ interface ActiveCallManager { @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DefaultActiveCallManager( @ApplicationContext context: Context, @AppCoroutineScope @@ -101,7 +100,7 @@ class DefaultActiveCallManager( private val imageLoaderHolder: ImageLoaderHolder, private val systemClock: SystemClock, ) : ActiveCallManager { - private val tag = "DefaultActiveCallManager" + private val tag = "ActiveCallManager" private var timedOutCallJob: Job? = null @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @@ -178,8 +177,8 @@ class DefaultActiveCallManager( suspend fun incomingCallTimedOut(displayMissedCallNotification: Boolean) = mutex.withLock { Timber.tag(tag).d("Incoming call timed out") - val previousActiveCall = activeCall.value ?: return - val notificationData = (previousActiveCall.callState as? CallState.Ringing)?.notificationData ?: return + val previousActiveCall = activeCall.value ?: return@withLock + val notificationData = (previousActiveCall.callState as? CallState.Ringing)?.notificationData ?: return@withLock activeCall.value = null if (activeWakeLock?.isHeld == true) { Timber.tag(tag).d("Releasing partial wakelock after timeout") @@ -197,11 +196,11 @@ class DefaultActiveCallManager( Timber.tag(tag).d("Hung up call: $callType") val currentActiveCall = activeCall.value ?: run { Timber.tag(tag).w("No active call, ignoring hang up") - return + return@withLock } if (currentActiveCall.callType != callType) { Timber.tag(tag).w("Call type $callType does not match the active call type, ignoring") - return + return@withLock } if (currentActiveCall.callState is CallState.Ringing) { // Decline the call diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/CallIntentDataParser.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/CallIntentDataParser.kt index 09fcfe3940..f5433c15a0 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/CallIntentDataParser.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/CallIntentDataParser.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/CallWidgetProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/CallWidgetProvider.kt index ec4c7a79d4..6ce73bc5e6 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/CallWidgetProvider.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/CallWidgetProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallAnalyticCredentialsProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallAnalyticCredentialsProvider.kt index aafb7fdde0..6ba075b7fc 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallAnalyticCredentialsProvider.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallAnalyticCredentialsProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,12 +10,10 @@ package io.element.android.features.call.impl.utils import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.call.impl.BuildConfig import io.element.android.libraries.matrix.api.widget.CallAnalyticCredentialsProvider @ContributesBinding(AppScope::class) -@Inject class DefaultCallAnalyticCredentialsProvider : CallAnalyticCredentialsProvider { override val posthogUserId: String? = BuildConfig.POSTHOG_USER_ID.takeIf { it.isNotBlank() } override val posthogApiHost: String? = BuildConfig.POSTHOG_API_HOST.takeIf { it.isNotBlank() } diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt index dc217bc118..1728a0cace 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.features.call.impl.utils import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.matrix.api.core.RoomId @@ -23,7 +23,6 @@ import kotlinx.coroutines.flow.firstOrNull private const val EMBEDDED_CALL_WIDGET_BASE_URL = "https://appassets.androidplatform.net/element-call/index.html" @ContributesBinding(AppScope::class) -@Inject class DefaultCallWidgetProvider( private val matrixClientsProvider: MatrixClientProvider, private val appPreferencesStore: AppPreferencesStore, diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCurrentCallService.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCurrentCallService.kt index 3ae65e338b..1c8c0b342b 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCurrentCallService.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCurrentCallService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.features.call.impl.utils import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.features.call.api.CurrentCall import io.element.android.features.call.api.CurrentCallService @@ -17,7 +17,6 @@ import kotlinx.coroutines.flow.MutableStateFlow @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DefaultCurrentCallService : CurrentCallService { override val currentCall = MutableStateFlow(CurrentCall.None) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/IntentProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/IntentProvider.kt index 774367a52d..0f74ba86d4 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/IntentProvider.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/IntentProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/PipController.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/PipController.kt index f1e3e2756b..b259816f9d 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/PipController.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/PipController.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewAudioManager.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewAudioManager.kt index 7745cda13b..d4811d65d5 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewAudioManager.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewAudioManager.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -44,6 +45,13 @@ class WebViewAudioManager( private val coroutineScope: CoroutineScope, private val onInvalidAudioDeviceAdded: (InvalidAudioDeviceReason) -> Unit, ) { + private val json by lazy { + Json { + encodeDefaults = true + explicitNulls = false + } + } + /** * Whether to disable bluetooth audio devices. This must be done on Android versions lower than Android 12, * since the WebView approach breaks when using the legacy Bluetooth audio APIs. @@ -308,11 +316,7 @@ class WebViewAudioManager( devices: List = listAudioDevices().map(SerializableAudioDevice::fromAudioDeviceInfo), ) { Timber.d("Updating available audio devices") - val jsonSerializer = Json { - encodeDefaults = true - explicitNulls = false - } - val deviceList = jsonSerializer.encodeToString(devices) + val deviceList = json.encodeToString(devices) webView.evaluateJavascript("controls.setAvailableOutputDevices($deviceList);", { Timber.d("Audio: setAvailableOutputDevices result: $it") }) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewPipController.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewPipController.kt index f46db3ca2b..12f8dfd00f 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewPipController.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewPipController.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewWidgetMessageInterceptor.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewWidgetMessageInterceptor.kt index 82500c5937..f7ab2c57af 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewWidgetMessageInterceptor.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WebViewWidgetMessageInterceptor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WidgetMessageInterceptor.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WidgetMessageInterceptor.kt index 91ea0add66..ea158c5ddc 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WidgetMessageInterceptor.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WidgetMessageInterceptor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WidgetMessageSerializer.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WidgetMessageSerializer.kt index a21cadab7a..9a489c61ce 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WidgetMessageSerializer.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WidgetMessageSerializer.kt @@ -1,24 +1,27 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.call.impl.utils +import dev.zacsweers.metro.Inject import io.element.android.features.call.impl.data.WidgetMessage +import io.element.android.libraries.androidutils.json.JsonProvider import io.element.android.libraries.core.extensions.runCatchingExceptions -import kotlinx.serialization.json.Json - -object WidgetMessageSerializer { - private val coder = Json { ignoreUnknownKeys = true } +@Inject +class WidgetMessageSerializer( + private val json: JsonProvider, +) { fun deserialize(message: String): Result { - return runCatchingExceptions { coder.decodeFromString(WidgetMessage.serializer(), message) } + return runCatchingExceptions { json().decodeFromString(WidgetMessage.serializer(), message) } } fun serialize(message: WidgetMessage): String { - return coder.encodeToString(WidgetMessage.serializer(), message) + return json().encodeToString(WidgetMessage.serializer(), message) } } diff --git a/features/call/impl/src/main/res/values-hr/translations.xml b/features/call/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..e58dc362fa --- /dev/null +++ b/features/call/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,8 @@ + + + "Poziv u tijeku" + "Dodirnite za povratak u poziv" + "☎️ Poziv u tijeku" + "Element Call ne podržava korištenje Bluetooth audiouređaja u ovoj inačici Androida. Odaberite drugi audiouređaj." + "Dolazni Element Call" + diff --git a/features/call/impl/src/main/res/values/do_not_translate.xml b/features/call/impl/src/main/res/values/do_not_translate.xml index d998d4b1c6..deb835b35c 100644 --- a/features/call/impl/src/main/res/values/do_not_translate.xml +++ b/features/call/impl/src/main/res/values/do_not_translate.xml @@ -1,7 +1,8 @@ diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/DefaultElementCallEntryPointTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/DefaultElementCallEntryPointTest.kt index 7d0f15b540..bfc6565d11 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/DefaultElementCallEntryPointTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/DefaultElementCallEntryPointTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/MapWebkitPermissionsTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/MapWebkitPermissionsTest.kt index 57de826164..e356358437 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/MapWebkitPermissionsTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/MapWebkitPermissionsTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/FakePipController.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/FakePipController.kt index 9c5406a8a5..5153f79c31 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/FakePipController.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/FakePipController.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/FakePipSupportProvider.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/FakePipSupportProvider.kt index 43fc0a562a..c17e31261c 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/FakePipSupportProvider.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/FakePipSupportProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/FakePipView.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/FakePipView.kt index a15fbbefa0..55e94312bf 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/FakePipView.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/FakePipView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt index d870382e48..c087fa3c35 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/impl/pip/PictureInPicturePresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/notifications/RingingCallNotificationCreatorTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/notifications/RingingCallNotificationCreatorTest.kt index 0af482fc49..28e2747ba9 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/notifications/RingingCallNotificationCreatorTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/notifications/RingingCallNotificationCreatorTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,13 +13,14 @@ import androidx.test.platform.app.InstrumentationRegistry import coil3.ImageLoader import com.google.common.truth.Truth.assertThat import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator +import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClientProvider -import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder +import io.element.android.libraries.matrix.ui.media.test.FakeImageLoaderHolder import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader import io.element.android.tests.testutils.lambda.lambdaRecorder import kotlinx.coroutines.test.runTest @@ -52,7 +54,7 @@ class RingingCallNotificationCreatorTest { @Test fun `createNotification - tries to load the avatar URL`() = runTest { - val getUserIconLambda = lambdaRecorder { _, _ -> null } + val getUserIconLambda = lambdaRecorder { _, _ -> null } val notificationCreator = createRingingCallNotificationCreator( matrixClientProvider = FakeMatrixClientProvider(getClient = { Result.success(FakeMatrixClient()) }), notificationBitmapLoader = FakeNotificationBitmapLoader(getUserIconResult = getUserIconLambda) diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt index 47a91afa1c..09aaaf8271 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallScreenPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,9 +17,11 @@ import io.element.android.features.call.api.CallType import io.element.android.features.call.impl.ui.CallScreenEvents import io.element.android.features.call.impl.ui.CallScreenNavigator import io.element.android.features.call.impl.ui.CallScreenPresenter +import io.element.android.features.call.impl.utils.WidgetMessageSerializer import io.element.android.features.call.utils.FakeActiveCallManager import io.element.android.features.call.utils.FakeCallWidgetProvider import io.element.android.features.call.utils.FakeWidgetMessageInterceptor +import io.element.android.libraries.androidutils.json.DefaultJsonProvider import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.sync.SyncState @@ -50,7 +53,8 @@ import org.junit.Rule import org.junit.Test import kotlin.time.Duration.Companion.seconds -@OptIn(ExperimentalCoroutinesApi::class) class CallScreenPresenterTest { +@OptIn(ExperimentalCoroutinesApi::class) +class CallScreenPresenterTest { @get:Rule val warmUpRule = WarmUpRule() @@ -409,6 +413,7 @@ import kotlin.time.Duration.Companion.seconds languageTagProvider = FakeLanguageTagProvider("en-US"), appForegroundStateService = appForegroundStateService, appCoroutineScope = backgroundScope, + widgetMessageSerializer = WidgetMessageSerializer(DefaultJsonProvider()), ) } } diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallTypeTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallTypeTest.kt new file mode 100644 index 0000000000..0c91b2159a --- /dev/null +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/CallTypeTest.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.call.ui + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.call.api.CallType +import io.element.android.features.call.impl.ui.getSessionId +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_SESSION_ID +import org.junit.Test + +class CallTypeTest { + @Test + fun `getSessionId returns null for ExternalUrl`() { + assertThat(CallType.ExternalUrl("aURL").getSessionId()).isNull() + } + + @Test + fun `getSessionId returns the sessionId for RoomCall`() { + assertThat( + CallType.RoomCall( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + ).getSessionId() + ).isEqualTo(A_SESSION_ID) + } + + @Test + fun `ExternalUrl stringification does not contain the URL`() { + assertThat(CallType.ExternalUrl("aURL").toString()).isEqualTo("ExternalUrl") + } + + @Test + fun `RoomCall stringification does not contain the URL`() { + assertThat(CallType.RoomCall(A_SESSION_ID, A_ROOM_ID).toString()) + .isEqualTo("RoomCall(sessionId=$A_SESSION_ID, roomId=$A_ROOM_ID)") + } +} diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/FakeCallScreenNavigator.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/FakeCallScreenNavigator.kt index 3e1d47bb57..431b746362 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/FakeCallScreenNavigator.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/FakeCallScreenNavigator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/FakeLanguageTagProvider.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/FakeLanguageTagProvider.kt index 5f80db1540..f68842813b 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/FakeLanguageTagProvider.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/ui/FakeLanguageTagProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/CallIntentDataParserTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/CallIntentDataParserTest.kt index bafbe3f570..43f7f931f1 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/CallIntentDataParserTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/CallIntentDataParserTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt index 3d1c35df4d..df14b4b423 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -33,9 +34,9 @@ import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.aRoomInfo +import io.element.android.libraries.matrix.ui.media.test.FakeImageLoaderHolder import io.element.android.libraries.push.api.notifications.ForegroundServiceType import io.element.android.libraries.push.api.notifications.NotificationIdProvider -import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder import io.element.android.libraries.push.test.notifications.FakeOnMissedCallNotificationHandler import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader import io.element.android.services.appnavstate.test.FakeAppForegroundStateService @@ -415,6 +416,7 @@ class DefaultActiveCallManagerTest { verify { notificationManagerCompat.cancel(any()) } } + @OptIn(ExperimentalCoroutinesApi::class) @Test fun `IncomingCall - ignore expired ring lifetime`() = runTest { diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt index a93dd5f91c..95d5398704 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -22,6 +23,7 @@ import io.element.android.libraries.matrix.test.widget.FakeMatrixWidgetDriver import io.element.android.libraries.preferences.api.store.AppPreferencesStore import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore import io.element.android.services.appnavstate.api.ActiveRoomsHolder +import io.element.android.services.appnavstate.impl.DefaultActiveRoomsHolder import kotlinx.coroutines.test.runTest import org.junit.Test @@ -85,7 +87,7 @@ class DefaultCallWidgetProviderTest { // No room from the client givenGetRoomResult(A_ROOM_ID, null) } - val activeRoomsHolder = ActiveRoomsHolder().apply { + val activeRoomsHolder = DefaultActiveRoomsHolder().apply { // A current active room with the same room id addRoom( FakeJoinedRoom( @@ -129,7 +131,7 @@ class DefaultCallWidgetProviderTest { matrixClientProvider: MatrixClientProvider = FakeMatrixClientProvider(), appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(), callWidgetSettingsProvider: CallWidgetSettingsProvider = FakeCallWidgetSettingsProvider(), - activeRoomsHolder: ActiveRoomsHolder = ActiveRoomsHolder(), + activeRoomsHolder: ActiveRoomsHolder = DefaultActiveRoomsHolder(), ) = DefaultCallWidgetProvider( matrixClientsProvider = matrixClientProvider, appPreferencesStore = appPreferencesStore, diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt index 90527370e7..74bd1c36ac 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeActiveCallManager.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeCallWidgetProvider.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeCallWidgetProvider.kt index 257b92b709..11e6d9e399 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeCallWidgetProvider.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeCallWidgetProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeWidgetMessageInterceptor.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeWidgetMessageInterceptor.kt index c41339b8df..6b68b1519c 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeWidgetMessageInterceptor.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/FakeWidgetMessageInterceptor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/test/build.gradle.kts b/features/call/test/build.gradle.kts index 4fe9b95384..76fbf9915e 100644 --- a/features/call/test/build.gradle.kts +++ b/features/call/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/test/src/main/kotlin/io/element/android/features/call/test/CallNotificationData.kt b/features/call/test/src/main/kotlin/io/element/android/features/call/test/CallNotificationData.kt index 2c56ab299b..2c7d1914c7 100644 --- a/features/call/test/src/main/kotlin/io/element/android/features/call/test/CallNotificationData.kt +++ b/features/call/test/src/main/kotlin/io/element/android/features/call/test/CallNotificationData.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeCurrentCallService.kt b/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeCurrentCallService.kt index 995b0d0a54..45f527720c 100644 --- a/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeCurrentCallService.kt +++ b/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeCurrentCallService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeElementCallEntryPoint.kt b/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeElementCallEntryPoint.kt index cd2e617b4d..fdf3ca566b 100644 --- a/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeElementCallEntryPoint.kt +++ b/features/call/test/src/main/kotlin/io/element/android/features/call/test/FakeElementCallEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesState.kt b/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesState.kt deleted file mode 100644 index 027ef76e69..0000000000 --- a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesState.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.changeroommemberroles.impl - -import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.designsystem.theme.components.SearchBarResultState -import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.api.room.RoomMember -import io.element.android.libraries.matrix.api.user.MatrixUser -import io.element.android.libraries.matrix.ui.room.PowerLevelRoomMemberComparator -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList - -data class ChangeRolesState( - val role: RoomMember.Role, - val query: String?, - val isSearchActive: Boolean, - val searchResults: SearchBarResultState, - val selectedUsers: ImmutableList, - val hasPendingChanges: Boolean, - val exitState: AsyncAction, - val savingState: AsyncAction, - val canChangeMemberRole: (UserId) -> Boolean, - val eventSink: (ChangeRolesEvent) -> Unit, -) - -data class MembersByRole( - val owners: ImmutableList, - val admins: ImmutableList, - val moderators: ImmutableList, - val members: ImmutableList, -) { - constructor(members: List) : this( - owners = members.filter { it.role is RoomMember.Role.Owner }.sorted(), - admins = members.filter { it.role == RoomMember.Role.Admin }.sorted(), - moderators = members.filter { it.role == RoomMember.Role.Moderator }.sorted(), - members = members.filter { it.role == RoomMember.Role.User }.sorted(), - ) - - fun isEmpty() = owners.isEmpty() && admins.isEmpty() && moderators.isEmpty() && members.isEmpty() -} - -private fun Iterable.sorted(): ImmutableList { - return sortedWith(PowerLevelRoomMemberComparator()).toImmutableList() -} diff --git a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/DefaultChangeRoomMemberRolesEntyPoint.kt b/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/DefaultChangeRoomMemberRolesEntyPoint.kt deleted file mode 100644 index 8a9117776d..0000000000 --- a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/DefaultChangeRoomMemberRolesEntyPoint.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.changeroommemberroles.impl - -import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node -import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject -import io.element.android.features.changeroommemberroes.api.ChangeRoomMemberRolesEntryPoint -import io.element.android.features.changeroommemberroes.api.ChangeRoomMemberRolesListType -import io.element.android.libraries.architecture.createNode -import io.element.android.libraries.di.SessionScope -import io.element.android.libraries.matrix.api.room.JoinedRoom - -@ContributesBinding(SessionScope::class) -@Inject -class DefaultChangeRoomMemberRolesEntyPoint : ChangeRoomMemberRolesEntryPoint { - override fun builder(parentNode: Node, buildContext: BuildContext): ChangeRoomMemberRolesEntryPoint.Builder { - return object : ChangeRoomMemberRolesEntryPoint.Builder { - private lateinit var changeRoomMemberRolesListType: ChangeRoomMemberRolesListType - private lateinit var room: JoinedRoom - - override fun room(room: JoinedRoom): ChangeRoomMemberRolesEntryPoint.Builder { - this.room = room - return this - } - - override fun listType(changeRoomMemberRolesListType: ChangeRoomMemberRolesListType): ChangeRoomMemberRolesEntryPoint.Builder { - this.changeRoomMemberRolesListType = changeRoomMemberRolesListType - return this - } - - override fun build(): Node { - return parentNode.createNode( - buildContext = buildContext, - plugins = listOf( - ChangeRoomMemberRolesRootNode.Inputs(joinedRoom = room, listType = changeRoomMemberRolesListType), - ) - ) - } - } - } -} diff --git a/features/createroom/api/build.gradle.kts b/features/createroom/api/build.gradle.kts index 7f39051b0e..b4d7d2a995 100644 --- a/features/createroom/api/build.gradle.kts +++ b/features/createroom/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt b/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt index 5fc71f2a3b..1c6a9f04db 100644 --- a/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt +++ b/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,12 +15,11 @@ import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.matrix.api.core.RoomId interface CreateRoomEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - - interface NodeBuilder { - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: Callback, + ): Node interface Callback : Plugin { fun onRoomCreated(roomId: RoomId) diff --git a/features/createroom/impl/build.gradle.kts b/features/createroom/impl/build.gradle.kts index 712ce110f8..1adaac1f6d 100644 --- a/features/createroom/impl/build.gradle.kts +++ b/features/createroom/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt index 8f46103ba5..7fea6fc0e5 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,6 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.replace import dev.zacsweers.metro.Assisted @@ -24,6 +24,7 @@ import io.element.android.features.createroom.impl.addpeople.AddPeopleNode import io.element.android.features.createroom.impl.configureroom.ConfigureRoomNode import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.RoomId @@ -42,9 +43,7 @@ class CreateRoomFlowNode( buildContext = buildContext, plugins = plugins ) { - private fun onRoomCreated(roomId: RoomId) { - plugins().forEach { it.onRoomCreated(roomId) } - } + private val callback: CreateRoomEntryPoint.Callback = callback() override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { @@ -60,7 +59,7 @@ class CreateRoomFlowNode( val inputs = AddPeopleNode.Inputs(navTarget.roomId) val callback: AddPeopleNode.Callback = object : AddPeopleNode.Callback { override fun onFinish() { - onRoomCreated(navTarget.roomId) + callback.onRoomCreated(navTarget.roomId) } } createNode(buildContext, plugins = listOf(inputs, callback)) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPoint.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPoint.kt index 0d62542504..2261d294cf 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPoint.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPoint.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,28 +10,18 @@ package io.element.android.features.createroom.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.createroom.api.CreateRoomEntryPoint import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope @ContributesBinding(SessionScope::class) -@Inject class DefaultCreateRoomEntryPoint : CreateRoomEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): CreateRoomEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : CreateRoomEntryPoint.NodeBuilder { - override fun callback(callback: CreateRoomEntryPoint.Callback): CreateRoomEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: CreateRoomEntryPoint.Callback, + ): Node { + return parentNode.createNode(buildContext, listOf(callback)) } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleNode.kt index 9ea89912cb..2e5c16e62c 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleNode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,13 +13,13 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.invitepeople.api.InvitePeoplePresenter import io.element.android.features.invitepeople.api.InvitePeopleRenderer import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.RoomId @@ -39,10 +40,7 @@ class AddPeopleNode( fun onFinish() } - private fun onFinish() { - plugins().forEach { it.onFinish() } - } - + private val callback: Callback = callback() private val roomId = inputs().roomId private val invitePeoplePresenter = invitePeoplePresenterFactory.create( joinedRoom = null, @@ -54,7 +52,7 @@ class AddPeopleNode( val state = invitePeoplePresenter.present() AddPeopleView( state = state, - onFinish = ::onFinish, + onFinish = callback::onFinish, ) { invitePeopleRenderer.Render(state, Modifier) } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt index 1c9a5f84d3..0a7309a2b0 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt index cd5470bc24..fed8404143 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt index 9e721d28bd..43ceee3594 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,11 +14,11 @@ import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.services.analytics.api.AnalyticsService @@ -42,9 +43,7 @@ class ConfigureRoomNode( ) } - private fun onCreateRoomSuccess(roomId: RoomId) { - plugins().forEach { it.onCreateRoomSuccess(roomId) } - } + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { @@ -53,7 +52,7 @@ class ConfigureRoomNode( state = state, modifier = modifier, onBackClick = this::navigateUp, - onCreateRoomSuccess = ::onCreateRoomSuccess, + onCreateRoomSuccess = callback::onCreateRoomSuccess, ) } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt index 29b9842abe..dbec30c0c5 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,6 +18,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.core.net.toUri import dev.zacsweers.metro.Inject import im.vector.app.features.analytics.plan.CreatedRoom import io.element.android.libraries.architecture.AsyncAction @@ -38,7 +40,7 @@ import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidityEf import io.element.android.libraries.mediapickers.api.PickerProvider import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider import io.element.android.libraries.mediaupload.api.MediaPreProcessor -import io.element.android.libraries.permissions.api.PermissionsEvents +import io.element.android.libraries.permissions.api.PermissionsEvent import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.services.analytics.api.AnalyticsService import kotlinx.collections.immutable.toImmutableList @@ -115,7 +117,7 @@ class ConfigureRoomPresenter( localCoroutineScope.createRoom(config, createRoomAction) } - fun handleEvents(event: ConfigureRoomEvents) { + fun handleEvent(event: ConfigureRoomEvents) { when (event) { is ConfigureRoomEvents.RoomNameChanged -> dataStore.setRoomName(event.name) is ConfigureRoomEvents.TopicChanged -> dataStore.setTopic(event.topic) @@ -130,7 +132,7 @@ class ConfigureRoomPresenter( cameraPhotoPicker.launch() } else { pendingPermissionRequest = true - cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions) + cameraPermissionState.eventSink(PermissionsEvent.RequestPermissions) } AvatarAction.Remove -> dataStore.setAvatarUri(uri = null) } @@ -148,7 +150,7 @@ class ConfigureRoomPresenter( cameraPermissionState = cameraPermissionState, homeserverName = homeserverName, roomAddressValidity = roomAddressValidity.value, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } @@ -157,7 +159,7 @@ class ConfigureRoomPresenter( createRoomAction: MutableState> ) = launch { suspend { - val avatarUrl = config.avatarUri?.let { uploadAvatar(it) } + val avatarUrl = config.avatarUri?.let { uploadAvatar(it.toUri()) } val params = if (config.roomVisibility is RoomVisibilityState.Public) { CreateRoomParameters( name = config.roomName, diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt index 90022a9204..127aa20ccb 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt index 7db8744408..7f760b46fb 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt index 8c56607a22..6715a7c1e9 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt @@ -1,13 +1,13 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.createroom.impl.configureroom -import android.net.Uri import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -192,7 +192,7 @@ private fun ConfigureRoomToolbar( @Composable private fun RoomNameWithAvatar( - avatarUri: Uri?, + avatarUri: String?, roomName: String, onAvatarClick: () -> Unit, onChangeRoomName: (String) -> Unit, diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/CreateRoomConfig.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/CreateRoomConfig.kt index 9ec71f5b76..b9d05a144f 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/CreateRoomConfig.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/CreateRoomConfig.kt @@ -1,13 +1,13 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.createroom.impl.configureroom -import android.net.Uri import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -15,7 +15,7 @@ import kotlinx.collections.immutable.persistentListOf data class CreateRoomConfig( val roomName: String? = null, val topic: String? = null, - val avatarUri: Uri? = null, + val avatarUri: String? = null, val invites: ImmutableList = persistentListOf(), val roomVisibility: RoomVisibilityState = RoomVisibilityState.Private, ) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/CreateRoomConfigStore.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/CreateRoomConfigStore.kt index f2701216c3..5e8637ddd6 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/CreateRoomConfigStore.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/CreateRoomConfigStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -62,7 +63,7 @@ class CreateRoomConfigStore( fun setAvatarUri(uri: Uri?, cached: Boolean = false) { cachedAvatarUri = uri.takeIf { cached } createRoomConfigFlow.getAndUpdate { config -> - config.copy(avatarUri = uri) + config.copy(avatarUri = uri?.toString()) } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccess.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccess.kt index ef35b654cc..9d8167cce2 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccess.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccess.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccessItem.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccessItem.kt index b3da88bd31..2d37be9103 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccessItem.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAccessItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAddress.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAddress.kt index 7e6da51711..a8e4e39999 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAddress.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomAddress.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityItem.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityItem.kt index 13e0ad7ea7..b92dee4d6e 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityItem.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityState.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityState.kt index 8337a81eae..82af5a5614 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityState.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/RoomVisibilityState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/createroom/impl/src/main/res/values-de/translations.xml b/features/createroom/impl/src/main/res/values-de/translations.xml index d7df703abb..9c48001c92 100644 --- a/features/createroom/impl/src/main/res/values-de/translations.xml +++ b/features/createroom/impl/src/main/res/values-de/translations.xml @@ -14,7 +14,7 @@ Du kannst dies jederzeit in den Einstellungen des Chats ändern." "Jeder kann den Beitritt zum Chat erbitten, aber ein Admin oder Moderator muss die Anfrage akzeptieren." "Beitritt beantragen" "Du benötigst eine Chat-Adresse, damit dieser Chat im öffentlichen Verzeichnis sichtbar ist." - "Chat-Adresse" + "Chatroom Adresse" "Chat-Name" " Sichtbarkeit des Chats" "Chat erstellen" diff --git a/features/createroom/impl/src/main/res/values-el/translations.xml b/features/createroom/impl/src/main/res/values-el/translations.xml index a3084a4e70..37ccd49d0e 100644 --- a/features/createroom/impl/src/main/res/values-el/translations.xml +++ b/features/createroom/impl/src/main/res/values-el/translations.xml @@ -14,7 +14,7 @@ "Οποιοσδήποτε μπορεί να ζητήσει να συμμετάσχει στην αίθουσα, αλλά ένας διαχειριστής ή ένας συντονιστής θα πρέπει να αποδεχτεί το αίτημα" "Αίτημα συμμετοχής" "Για να είναι ορατή αυτή η αίθουσα στον δημόσιο κατάλογο αιθουσών, θα χρειαστείτε μια διεύθυνση αίθουσας." - "Διεύθυνση αίθουσας" + "Διεύθυνση δωματίου" "Όνομα αίθουσας" "Ορατότητα αίθουσας" "Δημιουργία αίθουσας" diff --git a/features/createroom/impl/src/main/res/values-es/translations.xml b/features/createroom/impl/src/main/res/values-es/translations.xml index 521f761174..64806977c0 100644 --- a/features/createroom/impl/src/main/res/values-es/translations.xml +++ b/features/createroom/impl/src/main/res/values-es/translations.xml @@ -14,7 +14,6 @@ Puedes cambiar esto en cualquier momento en los ajustes de la sala." "Cualquiera puede solicitar unirse a la sala, pero un administrador o un moderador tendrá que aceptar la solicitud" "Solicitud para unirse" "Para que esta sala sea visible en el directorio de salas públicas, necesitarás una dirección de sala." - "Dirección de la sala" "Nombre de la sala" "Visibilidad de la sala" "Crear una sala" diff --git a/features/createroom/impl/src/main/res/values-et/translations.xml b/features/createroom/impl/src/main/res/values-et/translations.xml index 6a1d9dc58a..98449f4817 100644 --- a/features/createroom/impl/src/main/res/values-et/translations.xml +++ b/features/createroom/impl/src/main/res/values-et/translations.xml @@ -9,7 +9,7 @@ Sa võid seda jututoa seadistustest alati muuta." "Avalik jututuba" "Kõik võivad selle jututoaga liituda" - "Kõik" + "Kõik kasutajad" "Ligipääs jututoale" "Kõik võivad paluda selle jututoaga liitumist, kuid peakasutaja või moderaator peavad selle kinnitama" "Küsi võimalust liitumiseks" diff --git a/features/createroom/impl/src/main/res/values-fi/translations.xml b/features/createroom/impl/src/main/res/values-fi/translations.xml index 31589ab997..df541d3dee 100644 --- a/features/createroom/impl/src/main/res/values-fi/translations.xml +++ b/features/createroom/impl/src/main/res/values-fi/translations.xml @@ -1,7 +1,7 @@ "Uusi huone" - "Kutsu ihmisiä" + "Kutsu henkilöitä" "Huoneen luomisessa tapahtui virhe" "Vain kutsutut henkilöt pääsevät tähän huoneeseen. Kaikki viestit ovat päästä päähän salattuja." "Yksityinen huone" diff --git a/features/createroom/impl/src/main/res/values-hr/translations.xml b/features/createroom/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..1d6d7e5af7 --- /dev/null +++ b/features/createroom/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,22 @@ + + + "Nova soba" + "Pozovi osobe" + "Došlo je do pogreške prilikom stvaranja sobe" + "Samo pozvane osobe mogu pristupiti ovoj sobi. Sve su poruke sveobuhvatno šifrirane." + "Privatna soba" + "Svatko može pronaći ovu sobu. +To možete u svakom trenutku promijeniti u postavkama sobe." + "Javna soba" + "Svatko se može pridružiti ovoj sobi" + "Svatko" + "Pristup sobi" + "Svatko može zatražiti pridruživanje sobi, ali administrator ili moderator morat će prihvatiti zahtjev." + "Zatraži pridruživanje" + "Da bi ova soba bila vidljiva u javnom direktoriju soba, trebat će vam adresa sobe." + "Adresa sobe" + "Naziv sobe" + "Vidljivost sobe" + "Stvori sobu" + "Tema (neobavezno)" + diff --git a/features/createroom/impl/src/main/res/values-hu/translations.xml b/features/createroom/impl/src/main/res/values-hu/translations.xml index 8833afcdec..24f71983ce 100644 --- a/features/createroom/impl/src/main/res/values-hu/translations.xml +++ b/features/createroom/impl/src/main/res/values-hu/translations.xml @@ -14,7 +14,7 @@ Ezt bármikor módosíthatja a szobabeállításokban." "Bárki kérheti, hogy csatlakozzon a szobához, de egy adminisztrátornak vagy moderátornak el kell fogadnia a kérést" "Csatlakozás kérése" "Ahhoz, hogy ez a szoba látható legyen a nyilvános szobák címtárában, meg kell adnia a szoba címét." - "A szoba címe" + "Szoba címe" "Szoba neve" "Szoba láthatósága" "Szoba létrehozása" diff --git a/features/createroom/impl/src/main/res/values-ko/translations.xml b/features/createroom/impl/src/main/res/values-ko/translations.xml index cac10c0b1f..3dfdc46e7c 100644 --- a/features/createroom/impl/src/main/res/values-ko/translations.xml +++ b/features/createroom/impl/src/main/res/values-ko/translations.xml @@ -14,7 +14,6 @@ "누구나 방에 참여 요청을 할 수 있지만, 관리자나 운영자가 요청을 수락해야 합니다." "참가 요청" "이 방이 공개 방 디렉토리에 표시되려면 방 주소가 필요합니다." - "방 주소" "방 이름" "방 표시 여부" "방 만들기" diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPointTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPointTest.kt index 61c7c052c5..35b6637bbf 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPointTest.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -38,9 +39,11 @@ class DefaultCreateRoomEntryPointTest { val callback = object : CreateRoomEntryPoint.Callback { override fun onRoomCreated(roomId: RoomId) = lambdaError() } - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + callback = callback, + ) assertThat(result.plugins).contains(callback) } } diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/startchat/impl/configureroom/ConfigureRoomPresenterTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/startchat/impl/configureroom/ConfigureRoomPresenterTest.kt index 635a2b330e..6de15904d7 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/startchat/impl/configureroom/ConfigureRoomPresenterTest.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/startchat/impl/configureroom/ConfigureRoomPresenterTest.kt @@ -1,13 +1,15 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.startchat.impl.configureroom import android.net.Uri +import androidx.core.net.toUri import app.cash.turbine.TurbineTestContext import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.CreatedRoom @@ -49,15 +51,10 @@ import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.test -import io.mockk.every import io.mockk.mockk -import io.mockk.mockkStatic -import io.mockk.unmockkAll import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest -import org.junit.After -import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -74,17 +71,6 @@ class ConfigureRoomPresenterTest { @get:Rule val warmUpRule = WarmUpRule() - @Before - fun setup() { - mockkStatic(File::readBytes) - every { any().readBytes() } returns byteArrayOf() - } - - @After - fun tearDown() { - unmockkAll() - } - @Test fun `present - initial state`() = runTest { val presenter = createConfigureRoomPresenter() @@ -155,15 +141,15 @@ class ConfigureRoomPresenterTest { // Pick avatar pickerProvider.givenResult(null) // From gallery - val uriFromGallery = Uri.parse(AN_URI_FROM_GALLERY) - pickerProvider.givenResult(uriFromGallery) + val uriFromGallery = AN_URI_FROM_GALLERY + pickerProvider.givenResult(uriFromGallery.toUri()) newState.eventSink(ConfigureRoomEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) newState = awaitItem() expectedConfig = expectedConfig.copy(avatarUri = uriFromGallery) assertThat(newState.config).isEqualTo(expectedConfig) // From camera - val uriFromCamera = Uri.parse(AN_URI_FROM_CAMERA) - pickerProvider.givenResult(uriFromCamera) + val uriFromCamera = AN_URI_FROM_CAMERA + pickerProvider.givenResult(uriFromCamera.toUri()) assertThat(newState.cameraPermissionState.permissionGranted).isFalse() newState.eventSink(ConfigureRoomEvents.HandleAvatarAction(AvatarAction.TakePhoto)) newState = awaitItem() @@ -175,8 +161,8 @@ class ConfigureRoomPresenterTest { expectedConfig = expectedConfig.copy(avatarUri = uriFromCamera) assertThat(newState.config).isEqualTo(expectedConfig) // Do it again, no permission is requested - val uriFromCamera2 = Uri.parse(AN_URI_FROM_CAMERA_2) - pickerProvider.givenResult(uriFromCamera2) + val uriFromCamera2 = AN_URI_FROM_CAMERA_2 + pickerProvider.givenResult(uriFromCamera2.toUri()) newState.eventSink(ConfigureRoomEvents.HandleAvatarAction(AvatarAction.TakePhoto)) newState = awaitItem() expectedConfig = expectedConfig.copy(avatarUri = uriFromCamera2) @@ -259,20 +245,25 @@ class ConfigureRoomPresenterTest { val initialState = initialState() dataStore.setAvatarUri(Uri.parse(AN_URI_FROM_GALLERY)) skipItems(1) - mediaPreProcessor.givenResult(Result.success(MediaUploadInfo.Image(mockk(), mockk(), mockk()))) - matrixClient.givenUploadMediaResult(Result.failure(AN_EXCEPTION)) + val file = File.createTempFile("test", "jpg") + try { + mediaPreProcessor.givenResult(Result.success(MediaUploadInfo.Image(file, mockk(), mockk()))) + matrixClient.givenUploadMediaResult(Result.failure(AN_EXCEPTION)) - initialState.eventSink(ConfigureRoomEvents.CreateRoom) - assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java) - val stateAfterCreateRoom = awaitItem() - assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncAction.Failure::class.java) - assertThat(analyticsService.capturedEvents.filterIsInstance()).isEmpty() + initialState.eventSink(ConfigureRoomEvents.CreateRoom) + assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java) + val stateAfterCreateRoom = awaitItem() + assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncAction.Failure::class.java) + assertThat(analyticsService.capturedEvents.filterIsInstance()).isEmpty() - matrixClient.givenUploadMediaResult(Result.success(AN_AVATAR_URL)) - stateAfterCreateRoom.eventSink(ConfigureRoomEvents.CreateRoom) - assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Uninitialized::class.java) - assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java) - assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Success::class.java) + matrixClient.givenUploadMediaResult(Result.success(AN_AVATAR_URL)) + stateAfterCreateRoom.eventSink(ConfigureRoomEvents.CreateRoom) + assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Uninitialized::class.java) + assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java) + assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Success::class.java) + } finally { + file.delete() + } } } diff --git a/features/createroom/test/build.gradle.kts b/features/createroom/test/build.gradle.kts new file mode 100644 index 0000000000..98aeac8a4c --- /dev/null +++ b/features/createroom/test/build.gradle.kts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.createroom.test" +} + +dependencies { + implementation(projects.features.createroom.api) + implementation(projects.libraries.architecture) + implementation(projects.tests.testutils) +} diff --git a/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/api/FakeCreateRoomEntryPoint.kt b/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/api/FakeCreateRoomEntryPoint.kt new file mode 100644 index 0000000000..2beaecf013 --- /dev/null +++ b/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/api/FakeCreateRoomEntryPoint.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.createroom.api + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeCreateRoomEntryPoint : CreateRoomEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: CreateRoomEntryPoint.Callback, + ): Node = lambdaError() +} diff --git a/features/deactivation/api/build.gradle.kts b/features/deactivation/api/build.gradle.kts index 58a73813ea..64a9f25071 100644 --- a/features/deactivation/api/build.gradle.kts +++ b/features/deactivation/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/features/deactivation/api/src/main/kotlin/io/element/android/features/deactivation/api/AccountDeactivationEntryPoint.kt b/features/deactivation/api/src/main/kotlin/io/element/android/features/deactivation/api/AccountDeactivationEntryPoint.kt index a6bf42e03a..6694d1ecb4 100644 --- a/features/deactivation/api/src/main/kotlin/io/element/android/features/deactivation/api/AccountDeactivationEntryPoint.kt +++ b/features/deactivation/api/src/main/kotlin/io/element/android/features/deactivation/api/AccountDeactivationEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/deactivation/impl/build.gradle.kts b/features/deactivation/impl/build.gradle.kts index 842206bab7..bca144071b 100644 --- a/features/deactivation/impl/build.gradle.kts +++ b/features/deactivation/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationEvents.kt b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationEvents.kt index 755f4a7b1f..5ceb97b232 100644 --- a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationEvents.kt +++ b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationNode.kt b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationNode.kt index 3e554672a8..44be7b5d53 100644 --- a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationNode.kt +++ b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationPresenter.kt b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationPresenter.kt index eb751366a8..eaeacd022c 100644 --- a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationPresenter.kt +++ b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -33,7 +34,7 @@ class AccountDeactivationPresenter( val formState = remember { mutableStateOf(DeactivateFormState.Default) } - fun handleEvents(event: AccountDeactivationEvents) { + fun handleEvent(event: AccountDeactivationEvents) { when (event) { is AccountDeactivationEvents.SetEraseData -> { updateFormState(formState) { @@ -63,7 +64,7 @@ class AccountDeactivationPresenter( return AccountDeactivationState( deactivateFormState = formState.value, accountDeactivationAction = action.value, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationState.kt b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationState.kt index 2ef2db318b..ca9751efcc 100644 --- a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationState.kt +++ b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationStateProvider.kt b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationStateProvider.kt index 12b9a7d554..5c832a4d75 100644 --- a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationStateProvider.kt +++ b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationView.kt b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationView.kt index af1bcdad8d..c0d625a45e 100644 --- a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationView.kt +++ b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultAccountDeactivationEntryPoint.kt b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultAccountDeactivationEntryPoint.kt index 0f34e18b9f..c93d519e67 100644 --- a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultAccountDeactivationEntryPoint.kt +++ b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/DefaultAccountDeactivationEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,12 +12,10 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.deactivation.api.AccountDeactivationEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultAccountDeactivationEntryPoint : AccountDeactivationEntryPoint { override fun createNode(parentNode: Node, buildContext: BuildContext): Node { return parentNode.createNode(buildContext) diff --git a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/AccountDeactivationActionDialog.kt b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/AccountDeactivationActionDialog.kt index 4e927cd07d..84037d7eb1 100644 --- a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/AccountDeactivationActionDialog.kt +++ b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/AccountDeactivationActionDialog.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/AccountDeactivationConfirmationDialog.kt b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/AccountDeactivationConfirmationDialog.kt index b3dfc931d4..905112a78d 100644 --- a/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/AccountDeactivationConfirmationDialog.kt +++ b/features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/ui/AccountDeactivationConfirmationDialog.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/deactivation/impl/src/main/res/values-hr/translations.xml b/features/deactivation/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..04148fdc48 --- /dev/null +++ b/features/deactivation/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,14 @@ + + + "Potvrdite da želite deaktivirati svoj račun. Ova se radnja ne može poništiti." + "Izbriši sve moje poruke" + "Upozorenje: budući korisnici mogu vidjeti nepotpune razgovore." + "Deaktiviranje vašeg računa je %1$s, to će:" + "nepovratno" + "%1$s vaš račun (ne možete se ponovno prijaviti i vaš ID se ne može ponovno upotrijebiti)." + "Trajno onemogući" + "Ukloniti vas iz svih soba za razgovore." + "Izbrisati podatke o vašem računu s našeg poslužitelja identiteta." + "Vaše će poruke i dalje biti vidljive registriranim korisnicima, ali neće biti dostupne novim ili neregistriranim korisnicima ako ih odlučite izbrisati." + "Deaktiviraj račun" + diff --git a/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationPresenterTest.kt b/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationPresenterTest.kt index d0b9f57dd9..ee7f8e470d 100644 --- a/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationPresenterTest.kt +++ b/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationViewTest.kt b/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationViewTest.kt index 1da7568aae..eff479d21c 100644 --- a/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationViewTest.kt +++ b/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/DefaultAccountDeactivationEntryPointTest.kt b/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/DefaultAccountDeactivationEntryPointTest.kt index 05ad52efe9..a9fdf3ff36 100644 --- a/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/DefaultAccountDeactivationEntryPointTest.kt +++ b/features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/DefaultAccountDeactivationEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/deactivation/test/build.gradle.kts b/features/deactivation/test/build.gradle.kts new file mode 100644 index 0000000000..e1050d7aaa --- /dev/null +++ b/features/deactivation/test/build.gradle.kts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.deactivation.test" +} + +dependencies { + implementation(projects.features.deactivation.api) + implementation(projects.libraries.architecture) + implementation(projects.tests.testutils) +} diff --git a/features/deactivation/test/src/main/kotlin/io/element/android/features/deactivation/test/FakeAccountDeactivationEntryPoint.kt b/features/deactivation/test/src/main/kotlin/io/element/android/features/deactivation/test/FakeAccountDeactivationEntryPoint.kt new file mode 100644 index 0000000000..ada24833f6 --- /dev/null +++ b/features/deactivation/test/src/main/kotlin/io/element/android/features/deactivation/test/FakeAccountDeactivationEntryPoint.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.deactivation.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.deactivation.api.AccountDeactivationEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeAccountDeactivationEntryPoint : AccountDeactivationEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + ): Node { + lambdaError() + } +} diff --git a/features/enterprise/api/build.gradle.kts b/features/enterprise/api/build.gradle.kts index b32f42e31f..1c541c5080 100644 --- a/features/enterprise/api/build.gradle.kts +++ b/features/enterprise/api/build.gradle.kts @@ -1,11 +1,12 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { - id("io.element.android-library") + id("io.element.android-compose-library") } android { diff --git a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/BugReportUrl.kt b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/BugReportUrl.kt index 8c1b659ab6..86ba110da7 100644 --- a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/BugReportUrl.kt +++ b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/BugReportUrl.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt index 03ecda80c0..ecf1ba6b64 100644 --- a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt +++ b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/EnterpriseService.kt @@ -1,13 +1,15 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.enterprise.api -import io.element.android.compound.tokens.generated.SemanticColors +import androidx.compose.ui.graphics.Color +import io.element.android.compound.colors.SemanticColorsLightDark import io.element.android.libraries.matrix.api.core.SessionId import kotlinx.coroutines.flow.Flow @@ -17,13 +19,21 @@ interface EnterpriseService { fun defaultHomeserverList(): List suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String): Boolean - fun semanticColorsLight(): SemanticColors - fun semanticColorsDark(): SemanticColors + /** + * Override the brand color. + * @param sessionId the session to override the brand color for, or null to set the brand color to use when there is no session. + * @param brandColor the color in hex format (#RRGGBBAA or #RRGGBB), or null to reset to default. + */ + suspend fun overrideBrandColor(sessionId: SessionId?, brandColor: String?) + + fun brandColorsFlow(sessionId: SessionId?): Flow + + fun semanticColorsFlow(sessionId: SessionId?): Flow fun firebasePushGateway(): String? fun unifiedPushDefaultPushGateway(): String? - val bugReportUrlFlow: Flow + fun bugReportUrlFlow(sessionId: SessionId?): Flow companion object { const val ANY_ACCOUNT_PROVIDER = "*" diff --git a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt index aff3d93e8e..6bd6c78de5 100644 --- a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt +++ b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/enterprise/impl-foss/build.gradle.kts b/features/enterprise/impl-foss/build.gradle.kts index c5c194807f..f0c63f5655 100644 --- a/features/enterprise/impl-foss/build.gradle.kts +++ b/features/enterprise/impl-foss/build.gradle.kts @@ -2,13 +2,14 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { - id("io.element.android-library") + id("io.element.android-compose-library") } android { diff --git a/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt b/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt index 4d52e83a8f..b154d78afa 100644 --- a/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt +++ b/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseService.kt @@ -1,25 +1,24 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.enterprise.impl +import androidx.compose.ui.graphics.Color import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject -import io.element.android.compound.tokens.generated.SemanticColors -import io.element.android.compound.tokens.generated.compoundColorsDark -import io.element.android.compound.tokens.generated.compoundColorsLight +import io.element.android.compound.colors.SemanticColorsLightDark import io.element.android.features.enterprise.api.BugReportUrl import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.libraries.matrix.api.core.SessionId +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf @ContributesBinding(AppScope::class) -@Inject class DefaultEnterpriseService : EnterpriseService { override val isEnterpriseBuild = false @@ -28,12 +27,20 @@ class DefaultEnterpriseService : EnterpriseService { override fun defaultHomeserverList(): List = emptyList() override suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String) = true - override fun semanticColorsLight(): SemanticColors = compoundColorsLight + override suspend fun overrideBrandColor(sessionId: SessionId?, brandColor: String?) = Unit - override fun semanticColorsDark(): SemanticColors = compoundColorsDark + override fun brandColorsFlow(sessionId: SessionId?): Flow { + return flowOf(null) + } + + override fun semanticColorsFlow(sessionId: SessionId?): Flow { + return flowOf(SemanticColorsLightDark.default) + } override fun firebasePushGateway(): String? = null override fun unifiedPushDefaultPushGateway(): String? = null - override val bugReportUrlFlow = flowOf(BugReportUrl.UseDefault) + override fun bugReportUrlFlow(sessionId: SessionId?): Flow { + return flowOf(BugReportUrl.UseDefault) + } } diff --git a/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseService.kt b/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseService.kt index f25c38531a..3441063a8a 100644 --- a/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseService.kt +++ b/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseService.kt @@ -1,19 +1,18 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.enterprise.impl import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.enterprise.api.SessionEnterpriseService import io.element.android.libraries.di.SessionScope @ContributesBinding(SessionScope::class) -@Inject class DefaultSessionEnterpriseService : SessionEnterpriseService { override suspend fun init() = Unit override suspend fun isElementCallAvailable(): Boolean = true diff --git a/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseServiceTest.kt b/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseServiceTest.kt index 9f3ebbb845..ff95fd8721 100644 --- a/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseServiceTest.kt +++ b/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultEnterpriseServiceTest.kt @@ -1,13 +1,17 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.enterprise.impl +import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import io.element.android.compound.colors.SemanticColorsLightDark +import io.element.android.features.enterprise.api.BugReportUrl import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.A_SESSION_ID import kotlinx.coroutines.test.runTest @@ -37,4 +41,61 @@ class DefaultEnterpriseServiceTest { val defaultEnterpriseService = DefaultEnterpriseService() assertThat(defaultEnterpriseService.isEnterpriseUser(A_SESSION_ID)).isFalse() } + + @Test + fun `semanticColorsFlow always emits the same value`() = runTest { + val defaultEnterpriseService = DefaultEnterpriseService() + defaultEnterpriseService.semanticColorsFlow(null).test { + val initialState = awaitItem() + assertThat(initialState).isEqualTo(SemanticColorsLightDark.default) + awaitComplete() + } + } + + @Test + fun `brandColorsFlow always emits null`() = runTest { + val defaultEnterpriseService = DefaultEnterpriseService() + defaultEnterpriseService.brandColorsFlow(null).test { + val initialState = awaitItem() + assertThat(initialState).isNull() + awaitComplete() + } + } + + @Test + fun `semanticColorsFlow always emits the same value for a session`() = runTest { + val defaultEnterpriseService = DefaultEnterpriseService() + defaultEnterpriseService.semanticColorsFlow(A_SESSION_ID).test { + val initialState = awaitItem() + assertThat(initialState).isEqualTo(SemanticColorsLightDark.default) + awaitComplete() + } + } + + @Test + fun `overrideBrandColor has no effect`() = runTest { + val defaultEnterpriseService = DefaultEnterpriseService() + defaultEnterpriseService.overrideBrandColor(A_SESSION_ID, "aColor") + } + + @Test + fun `firebasePushGateway returns null`() = runTest { + val defaultEnterpriseService = DefaultEnterpriseService() + assertThat(defaultEnterpriseService.firebasePushGateway()).isNull() + } + + @Test + fun `unifiedPushDefaultPushGateway returns null`() = runTest { + val defaultEnterpriseService = DefaultEnterpriseService() + assertThat(defaultEnterpriseService.unifiedPushDefaultPushGateway()).isNull() + } + + @Test + fun `bugReportUrlFlow only emits UseDefault`() = runTest { + val defaultEnterpriseService = DefaultEnterpriseService() + defaultEnterpriseService.bugReportUrlFlow(A_SESSION_ID).test { + assertThat(awaitItem()).isEqualTo(BugReportUrl.UseDefault) + awaitComplete() + } + } } diff --git a/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseServiceTest.kt b/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseServiceTest.kt index b1e70ef045..391878a0a4 100644 --- a/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseServiceTest.kt +++ b/features/enterprise/impl-foss/src/test/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseServiceTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/enterprise/test/build.gradle.kts b/features/enterprise/test/build.gradle.kts index 38cc7aaaa9..542e73717a 100644 --- a/features/enterprise/test/build.gradle.kts +++ b/features/enterprise/test/build.gradle.kts @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt index 30ccc4c48d..b2c386267a 100644 --- a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt +++ b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeEnterpriseService.kt @@ -1,13 +1,15 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.enterprise.test -import io.element.android.compound.tokens.generated.SemanticColors +import androidx.compose.ui.graphics.Color +import io.element.android.compound.colors.SemanticColorsLightDark import io.element.android.features.enterprise.api.BugReportUrl import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.libraries.matrix.api.core.SessionId @@ -22,11 +24,15 @@ class FakeEnterpriseService( private val isEnterpriseUserResult: (SessionId) -> Boolean = { lambdaError() }, private val defaultHomeserverListResult: () -> List = { emptyList() }, private val isAllowedToConnectToHomeserverResult: (String) -> Boolean = { lambdaError() }, - private val semanticColorsLightResult: () -> SemanticColors = { lambdaError() }, - private val semanticColorsDarkResult: () -> SemanticColors = { lambdaError() }, + initialSemanticColors: SemanticColorsLightDark = SemanticColorsLightDark.default, + initialBrandColor: Color? = null, + private val overrideBrandColorResult: (SessionId?, String?) -> Unit = { _, _ -> lambdaError() }, private val firebasePushGatewayResult: () -> String? = { lambdaError() }, private val unifiedPushDefaultPushGatewayResult: () -> String? = { lambdaError() }, ) : EnterpriseService { + private val brandColorState = MutableStateFlow(initialBrandColor) + private val semanticColorsState = MutableStateFlow(initialSemanticColors) + override suspend fun isEnterpriseUser(sessionId: SessionId): Boolean = simulateLongTask { isEnterpriseUserResult(sessionId) } @@ -39,12 +45,16 @@ class FakeEnterpriseService( isAllowedToConnectToHomeserverResult(homeserverUrl) } - override fun semanticColorsLight(): SemanticColors { - return semanticColorsLightResult() + override suspend fun overrideBrandColor(sessionId: SessionId?, brandColor: String?) = simulateLongTask { + overrideBrandColorResult(sessionId, brandColor) } - override fun semanticColorsDark(): SemanticColors { - return semanticColorsDarkResult() + override fun brandColorsFlow(sessionId: SessionId?): Flow { + return brandColorState.asStateFlow() + } + + override fun semanticColorsFlow(sessionId: SessionId?): Flow { + return semanticColorsState.asStateFlow() } override fun firebasePushGateway(): String? { @@ -56,5 +66,7 @@ class FakeEnterpriseService( } val bugReportUrlMutableFlow = MutableStateFlow(BugReportUrl.UseDefault) - override val bugReportUrlFlow: Flow = bugReportUrlMutableFlow.asStateFlow() + override fun bugReportUrlFlow(sessionId: SessionId?): Flow { + return bugReportUrlMutableFlow.asStateFlow() + } } diff --git a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeSessionEnterpriseService.kt b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeSessionEnterpriseService.kt index e3125a19e6..3914c60155 100644 --- a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeSessionEnterpriseService.kt +++ b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeSessionEnterpriseService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/forward/api/build.gradle.kts b/features/forward/api/build.gradle.kts new file mode 100644 index 0000000000..1c5f47a048 --- /dev/null +++ b/features/forward/api/build.gradle.kts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.forward.api" +} + +dependencies { + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) +} diff --git a/features/forward/api/src/main/kotlin/io/element/android/features/forward/api/ForwardEntryPoint.kt b/features/forward/api/src/main/kotlin/io/element/android/features/forward/api/ForwardEntryPoint.kt new file mode 100644 index 0000000000..95b8c43808 --- /dev/null +++ b/features/forward/api/src/main/kotlin/io/element/android/features/forward/api/ForwardEntryPoint.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.forward.api + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import io.element.android.libraries.architecture.FeatureEntryPoint +import io.element.android.libraries.architecture.NodeInputs +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.timeline.TimelineProvider + +interface ForwardEntryPoint : FeatureEntryPoint { + interface Callback : Plugin { + fun onDone(roomIds: List) + } + + data class Params( + val eventId: EventId, + val timelineProvider: TimelineProvider, + ) : NodeInputs + + fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: Params, + callback: Callback, + ): Node +} diff --git a/features/forward/impl/build.gradle.kts b/features/forward/impl/build.gradle.kts new file mode 100644 index 0000000000..5d692c861e --- /dev/null +++ b/features/forward/impl/build.gradle.kts @@ -0,0 +1,41 @@ +import extension.setupDependencyInjection +import extension.testCommonDependencies + +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-compose-library") + id("kotlin-parcelize") +} + +android { + namespace = "io.element.android.features.forward.impl" + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } +} + +setupDependencyInjection() + +dependencies { + api(projects.features.forward.api) + implementation(projects.libraries.architecture) + implementation(projects.libraries.designsystem) + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.roomselect.api) + implementation(projects.libraries.uiStrings) + + testCommonDependencies(libs, true) + testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.roomselect.test) + testImplementation(projects.libraries.testtags) +} diff --git a/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/DefaultForwardEntryPoint.kt b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/DefaultForwardEntryPoint.kt new file mode 100644 index 0000000000..97256c6576 --- /dev/null +++ b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/DefaultForwardEntryPoint.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.forward.impl + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import dev.zacsweers.metro.ContributesBinding +import io.element.android.features.forward.api.ForwardEntryPoint +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.SessionScope + +@ContributesBinding(SessionScope::class) +class DefaultForwardEntryPoint : ForwardEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: ForwardEntryPoint.Params, + callback: ForwardEntryPoint.Callback, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf( + ForwardMessagesNode.Inputs( + eventId = params.eventId, + timelineProvider = params.timelineProvider, + ), + callback, + ) + ) + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesEvents.kt b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesEvents.kt similarity index 62% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesEvents.kt rename to features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesEvents.kt index 05a77b61a7..0161a5d657 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesEvents.kt +++ b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesEvents.kt @@ -1,11 +1,12 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.messages.impl.forward +package io.element.android.features.forward.impl sealed interface ForwardMessagesEvents { data object ClearError : ForwardMessagesEvents diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesNode.kt b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesNode.kt similarity index 75% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesNode.kt rename to features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesNode.kt index 2cf0c6e1e7..eb085d19c5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesNode.kt +++ b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesNode.kt @@ -1,11 +1,12 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.messages.impl.forward +package io.element.android.features.forward.impl import android.os.Parcelable import androidx.compose.foundation.layout.Box @@ -20,9 +21,11 @@ import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode +import io.element.android.features.forward.api.ForwardEntryPoint import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs -import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.di.SessionScope 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.timeline.TimelineProvider @@ -30,7 +33,7 @@ import io.element.android.libraries.roomselect.api.RoomSelectEntryPoint import io.element.android.libraries.roomselect.api.RoomSelectMode import kotlinx.parcelize.Parcelize -@ContributesNode(RoomScope::class) +@ContributesNode(SessionScope::class) @AssistedInject class ForwardMessagesNode( @Assisted buildContext: BuildContext, @@ -48,18 +51,14 @@ class ForwardMessagesNode( @Parcelize object NavTarget : Parcelable - interface Callback : Plugin { - fun onForwardedToSingleRoom(roomId: RoomId) - } - data class Inputs( val eventId: EventId, val timelineProvider: TimelineProvider, ) : NodeInputs private val inputs = inputs() + private val callback: ForwardEntryPoint.Callback = callback() private val presenter = presenterFactory.create(inputs.eventId.value, inputs.timelineProvider) - private val callbacks = plugins.filterIsInstance() override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { val callback = object : RoomSelectEntryPoint.Callback { @@ -68,14 +67,16 @@ class ForwardMessagesNode( } override fun onCancel() { - navigateUp() + callback.onDone(emptyList()) } } - return roomSelectEntryPoint.nodeBuilder(this, buildContext) - .callback(callback) - .params(RoomSelectEntryPoint.Params(mode = RoomSelectMode.Forward)) - .build() + return roomSelectEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = RoomSelectEntryPoint.Params(mode = RoomSelectMode.Forward), + callback = callback, + ) } @Composable @@ -89,16 +90,8 @@ class ForwardMessagesNode( val state = presenter.present() ForwardMessagesView( state = state, - onForwardSuccess = ::onForwardSuccess, + onForwardSuccess = callback::onDone, ) } } - - private fun onForwardSuccess(roomIds: List) { - navigateUp() - if (roomIds.size == 1) { - val targetRoomId = roomIds.first() - callbacks.forEach { it.onForwardedToSingleRoom(targetRoomId) } - } - } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenter.kt b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesPresenter.kt similarity index 77% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenter.kt rename to features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesPresenter.kt index 3e3860db3e..1145f294a3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenter.kt +++ b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesPresenter.kt @@ -1,11 +1,12 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.messages.impl.forward +package io.element.android.features.forward.impl import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState @@ -21,10 +22,9 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.timeline.TimelineProvider import io.element.android.libraries.matrix.api.timeline.getActiveTimeline -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import timber.log.Timber @AssistedInject class ForwardMessagesPresenter( @@ -36,19 +36,19 @@ class ForwardMessagesPresenter( private val eventId: EventId = EventId(eventId) @AssistedFactory - interface Factory { + fun interface Factory { fun create(eventId: String, timelineProvider: TimelineProvider): ForwardMessagesPresenter } private val forwardingActionState: MutableState>> = mutableStateOf(AsyncAction.Uninitialized) fun onRoomSelected(roomIds: List) { - sessionCoroutineScope.forwardEvent(eventId, roomIds.toImmutableList(), forwardingActionState) + sessionCoroutineScope.forwardEvent(eventId, roomIds) } @Composable override fun present(): ForwardMessagesState { - fun handleEvents(event: ForwardMessagesEvents) { + fun handleEvent(event: ForwardMessagesEvents) { when (event) { ForwardMessagesEvents.ClearError -> forwardingActionState.value = AsyncAction.Uninitialized } @@ -56,18 +56,21 @@ class ForwardMessagesPresenter( return ForwardMessagesState( forwardAction = forwardingActionState.value, - eventSink = { handleEvents(it) } + eventSink = ::handleEvent, ) } private fun CoroutineScope.forwardEvent( eventId: EventId, - roomIds: ImmutableList, - isForwardMessagesState: MutableState>>, + roomIds: List, ) = launch { suspend { - timelineProvider.getActiveTimeline().forwardEvent(eventId, roomIds).getOrThrow() + timelineProvider.getActiveTimeline().forwardEvent(eventId, roomIds) + .onFailure { + Timber.e(it, "Error while forwarding event") + } + .getOrThrow() roomIds - }.runCatchingUpdatingState(isForwardMessagesState) + }.runCatchingUpdatingState(forwardingActionState) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesState.kt b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesState.kt similarity index 73% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesState.kt rename to features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesState.kt index a1de911c72..b1e46adb03 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesState.kt +++ b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesState.kt @@ -1,11 +1,12 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.messages.impl.forward +package io.element.android.features.forward.impl import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.RoomId diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesStateProvider.kt b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesStateProvider.kt similarity index 89% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesStateProvider.kt rename to features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesStateProvider.kt index b1728f4657..bbb5b380ae 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesStateProvider.kt +++ b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesStateProvider.kt @@ -1,11 +1,12 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.messages.impl.forward +package io.element.android.features.forward.impl import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncAction diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesView.kt b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesView.kt similarity index 76% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesView.kt rename to features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesView.kt index 9ea76d754f..8065054b2d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesView.kt +++ b/features/forward/impl/src/main/kotlin/io/element/android/features/forward/impl/ForwardMessagesView.kt @@ -1,18 +1,21 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.messages.impl.forward +package io.element.android.features.forward.impl import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.ui.strings.CommonStrings @Composable fun ForwardMessagesView( @@ -24,6 +27,9 @@ fun ForwardMessagesView( onSuccess = { onForwardSuccess(it) }, + errorMessage = { + stringResource(id = CommonStrings.error_unknown) + }, onErrorDismiss = { state.eventSink(ForwardMessagesEvents.ClearError) }, diff --git a/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/DefaultForwardEntryPointTest.kt b/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/DefaultForwardEntryPointTest.kt new file mode 100644 index 0000000000..9ee932a246 --- /dev/null +++ b/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/DefaultForwardEntryPointTest.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.forward.impl + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.testing.junit4.util.MainDispatcherRule +import com.google.common.truth.Truth.assertThat +import io.element.android.features.forward.api.ForwardEntryPoint +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.timeline.FakeTimelineProvider +import io.element.android.libraries.roomselect.test.FakeRoomSelectEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError +import io.element.android.tests.testutils.node.TestParentNode +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class DefaultForwardEntryPointTest { + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + + @get:Rule + val mainDispatcherRule = MainDispatcherRule() + + @Test + fun `test node builder`() = runTest { + val entryPoint = DefaultForwardEntryPoint() + val parentNode = TestParentNode.create { buildContext, plugins -> + ForwardMessagesNode( + buildContext = buildContext, + plugins = plugins, + presenterFactory = { _, _ -> createForwardMessagesPresenter() }, + roomSelectEntryPoint = FakeRoomSelectEntryPoint(), + ) + } + val callback = object : ForwardEntryPoint.Callback { + override fun onDone(roomIds: List) = lambdaError() + } + val params = ForwardEntryPoint.Params( + eventId = AN_EVENT_ID, + timelineProvider = FakeTimelineProvider(), + ) + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + params = params, + callback = callback, + ) + assertThat(result).isInstanceOf(ForwardMessagesNode::class.java) + assertThat(result.plugins).contains( + ForwardMessagesNode.Inputs( + eventId = params.eventId, + timelineProvider = params.timelineProvider, + ) + ) + assertThat(result.plugins).contains(callback) + } +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenterTest.kt b/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesPresenterTest.kt similarity index 84% rename from features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenterTest.kt rename to features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesPresenterTest.kt index 757e682592..5e2f74ccad 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesPresenterTest.kt +++ b/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesPresenterTest.kt @@ -1,11 +1,12 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.messages.impl.forward +package io.element.android.features.forward.impl import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow @@ -32,7 +33,7 @@ class ForwardMessagesPresenterTest { @Test fun `present - initial state`() = runTest { - val presenter = aForwardMessagesPresenter() + val presenter = createForwardMessagesPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -50,7 +51,7 @@ class ForwardMessagesPresenterTest { this.forwardEventLambda = forwardEventLambda } val room = FakeJoinedRoom(liveTimeline = timeline) - val presenter = aForwardMessagesPresenter(fakeRoom = room) + val presenter = createForwardMessagesPresenter(fakeRoom = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -74,7 +75,7 @@ class ForwardMessagesPresenterTest { this.forwardEventLambda = forwardEventLambda } val room = FakeJoinedRoom(liveTimeline = timeline) - val presenter = aForwardMessagesPresenter(fakeRoom = room) + val presenter = createForwardMessagesPresenter(fakeRoom = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -90,13 +91,13 @@ class ForwardMessagesPresenterTest { forwardEventLambda.assertions().isCalledOnce() } } - - private fun TestScope.aForwardMessagesPresenter( - eventId: EventId = AN_EVENT_ID, - fakeRoom: FakeJoinedRoom = FakeJoinedRoom(), - ) = ForwardMessagesPresenter( - eventId = eventId.value, - timelineProvider = LiveTimelineProvider(fakeRoom), - sessionCoroutineScope = this, - ) } + +fun TestScope.createForwardMessagesPresenter( + eventId: EventId = AN_EVENT_ID, + fakeRoom: FakeJoinedRoom = FakeJoinedRoom(), +) = ForwardMessagesPresenter( + eventId = eventId.value, + timelineProvider = LiveTimelineProvider(fakeRoom), + sessionCoroutineScope = this, +) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesViewTest.kt b/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesViewTest.kt similarity index 94% rename from features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesViewTest.kt rename to features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesViewTest.kt index 0912fcc75c..f1e9bd8fc6 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesViewTest.kt +++ b/features/forward/impl/src/test/kotlin/io/element/android/features/forward/impl/ForwardMessagesViewTest.kt @@ -1,11 +1,12 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.messages.impl.forward +package io.element.android.features.forward.impl import androidx.activity.ComponentActivity import androidx.compose.ui.test.junit4.AndroidComposeTestRule diff --git a/features/forward/test/build.gradle.kts b/features/forward/test/build.gradle.kts new file mode 100644 index 0000000000..bf9db05e34 --- /dev/null +++ b/features/forward/test/build.gradle.kts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.forward.test" +} + +dependencies { + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) + implementation(projects.features.forward.api) + implementation(projects.tests.testutils) +} diff --git a/features/forward/test/src/main/kotlin/io/element/android/features/forward/test/FakeForwardEntryPoint.kt b/features/forward/test/src/main/kotlin/io/element/android/features/forward/test/FakeForwardEntryPoint.kt new file mode 100644 index 0000000000..9306ebcb29 --- /dev/null +++ b/features/forward/test/src/main/kotlin/io/element/android/features/forward/test/FakeForwardEntryPoint.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.forward.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.forward.api.ForwardEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeForwardEntryPoint : ForwardEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: ForwardEntryPoint.Params, + callback: ForwardEntryPoint.Callback, + ): Node = lambdaError() +} diff --git a/features/ftue/api/build.gradle.kts b/features/ftue/api/build.gradle.kts index 933dbbd62a..4f1d8b14bc 100644 --- a/features/ftue/api/build.gradle.kts +++ b/features/ftue/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/ftue/api/src/main/kotlin/io/element/android/features/ftue/api/FtueEntryPoint.kt b/features/ftue/api/src/main/kotlin/io/element/android/features/ftue/api/FtueEntryPoint.kt index 3d5aae3860..fe5ffc3b44 100644 --- a/features/ftue/api/src/main/kotlin/io/element/android/features/ftue/api/FtueEntryPoint.kt +++ b/features/ftue/api/src/main/kotlin/io/element/android/features/ftue/api/FtueEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/ftue/api/src/main/kotlin/io/element/android/features/ftue/api/state/FtueService.kt b/features/ftue/api/src/main/kotlin/io/element/android/features/ftue/api/state/FtueService.kt index b596f328d5..1bc585f015 100644 --- a/features/ftue/api/src/main/kotlin/io/element/android/features/ftue/api/state/FtueService.kt +++ b/features/ftue/api/src/main/kotlin/io/element/android/features/ftue/api/state/FtueService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/ftue/impl/build.gradle.kts b/features/ftue/impl/build.gradle.kts index d7d61f6d8d..49b3baa71b 100644 --- a/features/ftue/impl/build.gradle.kts +++ b/features/ftue/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/DefaultFtueEntryPoint.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/DefaultFtueEntryPoint.kt index 4fa086f4cd..812910e182 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/DefaultFtueEntryPoint.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/DefaultFtueEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,12 +12,10 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.ftue.api.FtueEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultFtueEntryPoint : FtueEntryPoint { override fun createNode(parentNode: Node, buildContext: BuildContext): Node { return parentNode.createNode(buildContext) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt index 6552a3b360..8d66258e12 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -110,9 +111,12 @@ class FtueFlowNode( defaultFtueService.updateFtueStep() } } - lockScreenEntryPoint.nodeBuilder(this, buildContext, LockScreenEntryPoint.Target.Setup) - .callback(callback) - .build() + lockScreenEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + navTarget = LockScreenEntryPoint.Target.Setup, + callback = callback, + ) } } } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/di/FtueModule.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/di/FtueModule.kt index 656ffac512..52f7a43bde 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/di/FtueModule.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/di/FtueModule.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInEvents.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInEvents.kt index dd133f8fd8..e0bd5acf43 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInEvents.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInNode.kt index 6e13e23a31..52ad70b242 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenter.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenter.kt index b6f5d76351..deb7fc59b9 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenter.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,7 +19,7 @@ import dev.zacsweers.metro.AssistedInject import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.di.annotations.AppCoroutineScope import io.element.android.libraries.permissions.api.PermissionStateProvider -import io.element.android.libraries.permissions.api.PermissionsEvents +import io.element.android.libraries.permissions.api.PermissionsEvent import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider @@ -51,13 +52,13 @@ class NotificationsOptInPresenter( override fun present(): NotificationsOptInState { val notificationsPermissionsState = postNotificationPermissionsPresenter.present() - fun handleEvents(event: NotificationsOptInEvents) { + fun handleEvent(event: NotificationsOptInEvents) { when (event) { NotificationsOptInEvents.ContinueClicked -> { if (notificationsPermissionsState.permissionGranted) { callback.onNotificationsOptInFinished() } else { - notificationsPermissionsState.eventSink(PermissionsEvents.RequestPermissions) + notificationsPermissionsState.eventSink(PermissionsEvent.RequestPermissions) } } NotificationsOptInEvents.NotNowClicked -> { @@ -78,7 +79,7 @@ class NotificationsOptInPresenter( return NotificationsOptInState( notificationsPermissionState = notificationsPermissionsState, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInState.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInState.kt index 92074d75ee..d12cec0778 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInState.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInStateProvider.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInStateProvider.kt index 265c4e8a99..1b2e5d2d58 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInStateProvider.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInView.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInView.kt index f23b9bafcd..6955d8e501 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInView.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt index 02a27d381d..085240d83e 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/FtueSessionVerificationFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,7 +16,6 @@ import androidx.lifecycle.lifecycleScope import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.newRoot import com.bumble.appyx.navmodel.backstack.operation.pop @@ -29,6 +29,7 @@ import io.element.android.features.securebackup.api.SecureBackupEntryPoint import io.element.android.features.verifysession.api.OutgoingVerificationEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode import io.element.android.libraries.designsystem.utils.OpenUrlInTabView import io.element.android.libraries.di.SessionScope @@ -69,6 +70,8 @@ class FtueSessionVerificationFlowNode( fun onDone() } + private val callback: Callback = callback() + private val secureBackupEntryPointCallback = object : SecureBackupEntryPoint.Callback { override fun onDone() { lifecycleScope.launch { @@ -82,62 +85,67 @@ class FtueSessionVerificationFlowNode( return when (navTarget) { is NavTarget.Root -> { val callback = object : ChooseSelfVerificationModeNode.Callback { - override fun onUseAnotherDevice() { + override fun navigateToUseAnotherDevice() { backstack.push(NavTarget.UseAnotherDevice) } - override fun onUseRecoveryKey() { + override fun navigateToUseRecoveryKey() { backstack.push(NavTarget.EnterRecoveryKey) } - override fun onResetKey() { + override fun navigateToResetKey() { backstack.push(NavTarget.ResetIdentity) } - override fun onLearnMoreAboutEncryption() { + override fun navigateToLearnMoreAboutEncryption() { learnMoreUrl.value = LearnMoreConfig.DEVICE_VERIFICATION_URL } } - createNode(buildContext, plugins = listOf(callback)) } is NavTarget.UseAnotherDevice -> { - outgoingVerificationEntryPoint.nodeBuilder(this, buildContext) - .params(OutgoingVerificationEntryPoint.Params( + outgoingVerificationEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = OutgoingVerificationEntryPoint.Params( showDeviceVerifiedScreen = true, verificationRequest = VerificationRequest.Outgoing.CurrentSession, - )) - .callback(object : OutgoingVerificationEntryPoint.Callback { + ), + callback = object : OutgoingVerificationEntryPoint.Callback { override fun onDone() { - plugins().forEach { it.onDone() } + callback.onDone() } override fun onBack() { backstack.pop() } - override fun onLearnMoreAboutEncryption() { + override fun navigateToLearnMoreAboutEncryption() { // Note that this callback is never called. The "Learn more" link is not displayed // for the self session interactive verification. } - }) - .build() + } + ) } is NavTarget.EnterRecoveryKey -> { - secureBackupEntryPoint.nodeBuilder(this, buildContext) - .params(SecureBackupEntryPoint.Params(SecureBackupEntryPoint.InitialTarget.EnterRecoveryKey)) - .callback(secureBackupEntryPointCallback) - .build() + secureBackupEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = SecureBackupEntryPoint.Params(SecureBackupEntryPoint.InitialTarget.EnterRecoveryKey), + callback = secureBackupEntryPointCallback + ) } is NavTarget.ResetIdentity -> { - secureBackupEntryPoint.nodeBuilder(this, buildContext) - .params(SecureBackupEntryPoint.Params(SecureBackupEntryPoint.InitialTarget.ResetIdentity)) - .callback(object : SecureBackupEntryPoint.Callback { + secureBackupEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = SecureBackupEntryPoint.Params(SecureBackupEntryPoint.InitialTarget.ResetIdentity), + callback = object : SecureBackupEntryPoint.Callback { override fun onDone() { - plugins().forEach { it.onDone() } + callback.onDone() } - }) - .build() + }, + ) } } } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeEvent.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeEvent.kt index 99e5950693..de7e1767f5 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeEvent.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeEvent.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt index 99409ac2d2..e78eef7edb 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeNode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,12 +13,12 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.logout.api.direct.DirectLogoutView import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.SessionScope @ContributesNode(SessionScope::class) @@ -29,13 +30,13 @@ class ChooseSelfVerificationModeNode( private val directLogoutView: DirectLogoutView, ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun onUseAnotherDevice() - fun onUseRecoveryKey() - fun onResetKey() - fun onLearnMoreAboutEncryption() + fun navigateToUseAnotherDevice() + fun navigateToUseRecoveryKey() + fun navigateToResetKey() + fun navigateToLearnMoreAboutEncryption() } - private val callback = plugins().first() + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { @@ -43,10 +44,10 @@ class ChooseSelfVerificationModeNode( ChooseSelfVerificationModeView( state = state, - onUseAnotherDevice = callback::onUseAnotherDevice, - onUseRecoveryKey = callback::onUseRecoveryKey, - onResetKey = callback::onResetKey, - onLearnMore = callback::onLearnMoreAboutEncryption, + onUseAnotherDevice = callback::navigateToUseAnotherDevice, + onUseRecoveryKey = callback::navigateToUseRecoveryKey, + onResetKey = callback::navigateToResetKey, + onLearnMore = callback::navigateToLearnMoreAboutEncryption, modifier = modifier, ) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModePresenter.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModePresenter.kt index eb3c1330b3..ace890be9c 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModePresenter.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModePresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,7 +16,9 @@ import androidx.compose.runtime.remember import dev.zacsweers.metro.Inject import io.element.android.features.logout.api.direct.DirectLogoutEvents import io.element.android.features.logout.api.direct.DirectLogoutState +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.core.coroutine.mapState import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.RecoveryState @@ -27,22 +30,46 @@ class ChooseSelfVerificationModePresenter( @Composable override fun present(): ChooseSelfVerificationModeState { val hasDevicesToVerifyAgainst by encryptionService.hasDevicesToVerifyAgainst.collectAsState() - val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState() - val canEnterRecoveryKey by remember { derivedStateOf { recoveryState == RecoveryState.INCOMPLETE } } + val canEnterRecoveryKey by encryptionService.recoveryStateStateFlow + .mapState { recoveryState -> + when (recoveryState) { + RecoveryState.WAITING_FOR_SYNC, + RecoveryState.UNKNOWN -> AsyncData.Loading() + RecoveryState.INCOMPLETE -> AsyncData.Success(true) + RecoveryState.ENABLED, + RecoveryState.DISABLED -> AsyncData.Success(false) + } + } + .collectAsState() + val buttonsState by remember { + derivedStateOf { + val canUseAnotherDevice = hasDevicesToVerifyAgainst.dataOrNull() + val canEnterRecoveryKey = canEnterRecoveryKey.dataOrNull() + if (canUseAnotherDevice == null || canEnterRecoveryKey == null) { + AsyncData.Loading() + } else { + AsyncData.Success( + ChooseSelfVerificationModeState.ButtonsState( + canUseAnotherDevice = canUseAnotherDevice, + canEnterRecoveryKey = canEnterRecoveryKey, + ) + ) + } + } + } val directLogoutState = directLogoutPresenter.present() - fun eventHandler(event: ChooseSelfVerificationModeEvent) { + fun handleEvent(event: ChooseSelfVerificationModeEvent) { when (event) { ChooseSelfVerificationModeEvent.SignOut -> directLogoutState.eventSink(DirectLogoutEvents.Logout(ignoreSdkError = false)) } } return ChooseSelfVerificationModeState( - canUseAnotherDevice = hasDevicesToVerifyAgainst, - canEnterRecoveryKey = canEnterRecoveryKey, + buttonsState = buttonsState, directLogoutState = directLogoutState, - eventSink = ::eventHandler, + eventSink = ::handleEvent, ) } } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeState.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeState.kt index 117768a6d2..4e27043f40 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeState.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeState.kt @@ -1,17 +1,23 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.ftue.impl.sessionverification.choosemode import io.element.android.features.logout.api.direct.DirectLogoutState +import io.element.android.libraries.architecture.AsyncData data class ChooseSelfVerificationModeState( - val canUseAnotherDevice: Boolean, - val canEnterRecoveryKey: Boolean, + val buttonsState: AsyncData, val directLogoutState: DirectLogoutState, val eventSink: (ChooseSelfVerificationModeEvent) -> Unit, -) +) { + data class ButtonsState( + val canUseAnotherDevice: Boolean, + val canEnterRecoveryKey: Boolean, + ) +} diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeStateProvider.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeStateProvider.kt index e053728e2c..676c77211f 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeStateProvider.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,23 +10,49 @@ package io.element.android.features.ftue.impl.sessionverification.choosemode import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.logout.api.direct.aDirectLogoutState +import io.element.android.libraries.architecture.AsyncData class ChooseSelfVerificationModeStateProvider : PreviewParameterProvider { override val values = sequenceOf( - aChooseSelfVerificationModeState(canUseAnotherDevice = false, canEnterRecoveryKey = true), - aChooseSelfVerificationModeState(canUseAnotherDevice = false, canEnterRecoveryKey = false), - aChooseSelfVerificationModeState(canUseAnotherDevice = true, canEnterRecoveryKey = true), - aChooseSelfVerificationModeState(canUseAnotherDevice = true, canEnterRecoveryKey = false), + aChooseSelfVerificationModeState( + buttonsState = AsyncData.Success( + aButtonsState(canUseAnotherDevice = false, canEnterRecoveryKey = true), + ), + ), + aChooseSelfVerificationModeState( + buttonsState = AsyncData.Success( + aButtonsState(canUseAnotherDevice = false, canEnterRecoveryKey = false), + ), + ), + aChooseSelfVerificationModeState( + buttonsState = AsyncData.Success( + aButtonsState(canUseAnotherDevice = true, canEnterRecoveryKey = true), + ), + ), + aChooseSelfVerificationModeState( + buttonsState = AsyncData.Success( + aButtonsState(canUseAnotherDevice = true, canEnterRecoveryKey = false), + ), + ), + aChooseSelfVerificationModeState( + buttonsState = AsyncData.Loading(), + ), ) } fun aChooseSelfVerificationModeState( - canUseAnotherDevice: Boolean = true, - canEnterRecoveryKey: Boolean = true, + buttonsState: AsyncData = AsyncData.Success(aButtonsState()), ) = ChooseSelfVerificationModeState( - canUseAnotherDevice = canUseAnotherDevice, - canEnterRecoveryKey = canEnterRecoveryKey, + buttonsState = buttonsState, directLogoutState = aDirectLogoutState(), eventSink = {}, ) + +fun aButtonsState( + canUseAnotherDevice: Boolean = true, + canEnterRecoveryKey: Boolean = true, +) = ChooseSelfVerificationModeState.ButtonsState( + canUseAnotherDevice = canUseAnotherDevice, + canEnterRecoveryKey = canEnterRecoveryKey, +) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt index b07c04ac9c..8f72f038de 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSelfVerificationModeView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -23,6 +24,8 @@ import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.ftue.impl.R +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.designsystem.atomic.atoms.LoadingButtonAtom import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage @@ -50,7 +53,6 @@ fun ChooseSelfVerificationModeView( BackHandler { activity?.finish() } - HeaderFooterPage( modifier = modifier, topBar = { @@ -73,29 +75,12 @@ fun ChooseSelfVerificationModeView( ) }, footer = { - ButtonColumnMolecule( - modifier = Modifier.padding(bottom = 16.dp) - ) { - if (state.canUseAnotherDevice) { - Button( - modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.screen_identity_use_another_device), - onClick = onUseAnotherDevice, - ) - } - if (state.canEnterRecoveryKey) { - Button( - modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.screen_session_verification_enter_recovery_key), - onClick = onUseRecoveryKey, - ) - } - OutlinedButton( - modifier = Modifier.fillMaxWidth(), - text = stringResource(R.string.screen_identity_confirmation_cannot_confirm), - onClick = onResetKey, - ) - } + ChooseSelfVerificationModeButtons( + state = state, + onUseAnotherDevice = onUseAnotherDevice, + onUseRecoveryKey = onUseRecoveryKey, + onResetKey = onResetKey, + ) } ) { Row( @@ -113,6 +98,47 @@ fun ChooseSelfVerificationModeView( } } +@Composable +private fun ChooseSelfVerificationModeButtons( + state: ChooseSelfVerificationModeState, + onUseAnotherDevice: () -> Unit, + onUseRecoveryKey: () -> Unit, + onResetKey: () -> Unit, +) { + ButtonColumnMolecule( + modifier = Modifier.padding(bottom = 16.dp) + ) { + when (state.buttonsState) { + AsyncData.Uninitialized, + is AsyncData.Failure, + is AsyncData.Loading -> { + LoadingButtonAtom() + } + is AsyncData.Success -> { + if (state.buttonsState.data.canUseAnotherDevice) { + Button( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.screen_identity_use_another_device), + onClick = onUseAnotherDevice, + ) + } + if (state.buttonsState.data.canEnterRecoveryKey) { + Button( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.screen_session_verification_enter_recovery_key), + onClick = onUseRecoveryKey, + ) + } + OutlinedButton( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.screen_identity_confirmation_cannot_confirm), + onClick = onResetKey, + ) + } + } + } +} + @PreviewsDayNight @Composable internal fun ChooseSelfVerificationModeViewPreview( diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt index 40f19b1e7a..78bb5f5c38 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.features.ftue.impl.state import android.Manifest import android.os.Build import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.features.ftue.api.state.FtueService import io.element.android.features.ftue.api.state.FtueState @@ -35,7 +35,6 @@ import kotlinx.coroutines.launch @ContributesBinding(SessionScope::class) @SingleIn(SessionScope::class) -@Inject class DefaultFtueService( private val sdkVersionProvider: BuildVersionSdkIntProvider, @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/InternalFtueState.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/InternalFtueState.kt index c620a2ca5b..b352a43fe5 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/InternalFtueState.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/InternalFtueState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/ftue/impl/src/main/res/values-eo/translations.xml b/features/ftue/impl/src/main/res/values-eo/translations.xml deleted file mode 100644 index fae7da561c..0000000000 --- a/features/ftue/impl/src/main/res/values-eo/translations.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - "Create a new backup password" - "Confirm this device to set up secure messaging." - "Confirm it\'s you" - "Use backup password" - "Device confirmed" - "Enter backup password" - diff --git a/features/ftue/impl/src/main/res/values-hr/translations.xml b/features/ftue/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..cb0e330872 --- /dev/null +++ b/features/ftue/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,16 @@ + + + "Ne možete potvrditi?" + "Izradi novi ključ za oporavak" + "Potvrdite ovaj uređaj kako biste postavili sigurnu razmjenu poruka." + "Potvrdite svoj identitet" + "Upotrijebite drugi uređaj" + "Upotrijebi ključ za oporavak" + "Sada možete sigurno čitati ili slati poruke, a svatko s kim razgovarate također može vjerovati ovom uređaju." + "Uređaj je potvrđen" + "Upotrijebite drugi uređaj" + "Čekanje na drugi uređaj…" + "Postavke možete promijeniti poslije." + "Omogućite obavijesti i nikada ne propustite poruku" + "Unesi ključ za oporavak" + diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueEntryPointTest.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueEntryPointTest.kt index 3a8ed11ea2..bbab0fc647 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueEntryPointTest.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueEntryPointTest.kt @@ -1,19 +1,18 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.ftue.impl -import android.content.Context -import android.content.Intent import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.testing.junit4.util.MainDispatcherRule import com.google.common.truth.Truth.assertThat -import io.element.android.features.lockscreen.api.LockScreenEntryPoint +import io.element.android.features.lockscreen.test.FakeLockScreenEntryPoint import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.node.TestParentNode import kotlinx.coroutines.test.runTest @@ -36,19 +35,7 @@ class DefaultFtueEntryPointTest { plugins = plugins, analyticsEntryPoint = { _, _ -> lambdaError() }, defaultFtueService = createDefaultFtueService(), - lockScreenEntryPoint = object : LockScreenEntryPoint { - override fun nodeBuilder( - parentNode: com.bumble.appyx.core.node.Node, - buildContext: BuildContext, - navTarget: LockScreenEntryPoint.Target - ): LockScreenEntryPoint.NodeBuilder { - lambdaError() - } - - override fun pinUnlockIntent(context: Context): Intent { - lambdaError() - } - }, + lockScreenEntryPoint = FakeLockScreenEntryPoint(), ) } val result = entryPoint.createNode(parentNode, BuildContext.root(null)) diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueServiceTest.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueServiceTest.kt index e46f43f3c3..dee8c7c091 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueServiceTest.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/DefaultFtueServiceTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenterTest.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenterTest.kt index fbdc9d1dd1..32d68dd1e1 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenterTest.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/notifications/NotificationsOptInPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModePresenterTest.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModePresenterTest.kt index 3dbf5a6932..c95c455bfd 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModePresenterTest.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModePresenterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,6 +12,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.logout.api.direct.DirectLogoutEvents import io.element.android.features.logout.api.direct.DirectLogoutState import io.element.android.features.logout.api.direct.aDirectLogoutState +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService @@ -22,23 +24,92 @@ import org.junit.Test class ChooseSessionVerificationModePresenterTest { @Test - fun `initial state - is relayed from EncryptionService`() = runTest { - val encryptionService = FakeEncryptionService().apply { - // Has device to verify against - emitHasDevicesToVerifyAgainst(false) - // Can enter recovery key - emitRecoveryState(RecoveryState.INCOMPLETE) - } - val presenter = createPresenter(encryptionService = encryptionService) + fun `present - initial state`() = runTest { + val presenter = createPresenter() presenter.test { awaitItem().run { - assertThat(canUseAnotherDevice).isFalse() - assertThat(canEnterRecoveryKey).isTrue() + assertThat(buttonsState.isLoading()).isTrue() assertThat(directLogoutState.logoutAction.isUninitialized()).isTrue() } } } + @Test + fun `present - state is relayed from EncryptionService, order 1`() = runTest { + val encryptionService = FakeEncryptionService() + val presenter = createPresenter(encryptionService = encryptionService) + presenter.test { + assertThat(awaitItem().buttonsState.isLoading()).isTrue() + // Has device to verify against + encryptionService.emitHasDevicesToVerifyAgainst(AsyncData.Success(false)) + // Can enter recovery key + encryptionService.emitRecoveryState(RecoveryState.DISABLED) + assertThat(awaitItem().buttonsState.dataOrNull()).isEqualTo( + ChooseSelfVerificationModeState.ButtonsState( + canUseAnotherDevice = false, + canEnterRecoveryKey = false, + ) + ) + } + } + + @Test + fun `present - state is relayed from EncryptionService, order 2`() = runTest { + val encryptionService = FakeEncryptionService() + val presenter = createPresenter(encryptionService = encryptionService) + presenter.test { + assertThat(awaitItem().buttonsState.isLoading()).isTrue() + // Can enter recovery key + encryptionService.emitRecoveryState(RecoveryState.DISABLED) + // Has device to verify against + encryptionService.emitHasDevicesToVerifyAgainst(AsyncData.Success(false)) + assertThat(awaitItem().buttonsState.dataOrNull()).isEqualTo( + ChooseSelfVerificationModeState.ButtonsState( + canUseAnotherDevice = false, + canEnterRecoveryKey = false, + ) + ) + } + } + + @Test + fun `present - can use another device`() = runTest { + val encryptionService = FakeEncryptionService() + val presenter = createPresenter(encryptionService = encryptionService) + presenter.test { + assertThat(awaitItem().buttonsState.isLoading()).isTrue() + // Can enter recovery key + encryptionService.emitRecoveryState(RecoveryState.DISABLED) + // Has device to verify against + encryptionService.emitHasDevicesToVerifyAgainst(AsyncData.Success(true)) + assertThat(awaitItem().buttonsState.dataOrNull()).isEqualTo( + ChooseSelfVerificationModeState.ButtonsState( + canUseAnotherDevice = true, + canEnterRecoveryKey = false, + ) + ) + } + } + + @Test + fun `present - can enter recovery key`() = runTest { + val encryptionService = FakeEncryptionService() + val presenter = createPresenter(encryptionService = encryptionService) + presenter.test { + assertThat(awaitItem().buttonsState.isLoading()).isTrue() + // Can enter recovery key + encryptionService.emitRecoveryState(RecoveryState.INCOMPLETE) + // Has device to verify against + encryptionService.emitHasDevicesToVerifyAgainst(AsyncData.Success(false)) + assertThat(awaitItem().buttonsState.dataOrNull()).isEqualTo( + ChooseSelfVerificationModeState.ButtonsState( + canUseAnotherDevice = false, + canEnterRecoveryKey = true, + ) + ) + } + } + @Test fun `sing out action triggers a direct logout`() = runTest { val logoutEventRecorder = lambdaRecorder {} @@ -49,8 +120,8 @@ class ChooseSessionVerificationModePresenterTest { presenter.test { val initial = awaitItem() initial.eventSink(ChooseSelfVerificationModeEvent.SignOut) - - logoutEventRecorder.assertions().isCalledOnce().with(value(DirectLogoutEvents.Logout(ignoreSdkError = false))) + logoutEventRecorder.assertions().isCalledOnce() + .with(value(DirectLogoutEvents.Logout(ignoreSdkError = false))) } } diff --git a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt index ed7d99dd19..f201cb6c41 100644 --- a/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt +++ b/features/ftue/impl/src/test/kotlin/io/element/android/features/ftue/impl/sessionverification/choosemode/ChooseSessionVerificationModeViewTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,6 +13,7 @@ import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 import io.element.android.features.ftue.impl.R +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.clickOn @@ -43,7 +45,7 @@ class ChooseSessionVerificationModeViewTest { fun `clicking on use another device calls the callback`() { ensureCalledOnce { callback -> rule.setChooseSelfVerificationModeView( - aChooseSelfVerificationModeState(canUseAnotherDevice = true), + aChooseSelfVerificationModeState(AsyncData.Success(aButtonsState(canUseAnotherDevice = true))), onUseAnotherDevice = callback, ) rule.clickOn(R.string.screen_identity_use_another_device) @@ -55,7 +57,7 @@ class ChooseSessionVerificationModeViewTest { fun `clicking on enter recovery key calls the callback`() { ensureCalledOnce { callback -> rule.setChooseSelfVerificationModeView( - aChooseSelfVerificationModeState(canEnterRecoveryKey = true), + aChooseSelfVerificationModeState(AsyncData.Success(aButtonsState(canEnterRecoveryKey = true))), onEnterRecoveryKey = callback, ) rule.clickOn(R.string.screen_session_verification_enter_recovery_key) diff --git a/features/ftue/test/build.gradle.kts b/features/ftue/test/build.gradle.kts index 9c0d4ffadd..4c7dc57535 100644 --- a/features/ftue/test/build.gradle.kts +++ b/features/ftue/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/ftue/test/src/main/kotlin/io/element/android/features/ftue/test/FakeFtueService.kt b/features/ftue/test/src/main/kotlin/io/element/android/features/ftue/test/FakeFtueService.kt index 1dbc2c281b..963f67c8e6 100644 --- a/features/ftue/test/src/main/kotlin/io/element/android/features/ftue/test/FakeFtueService.kt +++ b/features/ftue/test/src/main/kotlin/io/element/android/features/ftue/test/FakeFtueService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/api/build.gradle.kts b/features/home/api/build.gradle.kts index c1aee95e67..2e725659c1 100644 --- a/features/home/api/build.gradle.kts +++ b/features/home/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/features/home/api/src/main/kotlin/io/element/android/features/home/api/HomeEntryPoint.kt b/features/home/api/src/main/kotlin/io/element/android/features/home/api/HomeEntryPoint.kt index 9beb147568..71ee093985 100644 --- a/features/home/api/src/main/kotlin/io/element/android/features/home/api/HomeEntryPoint.kt +++ b/features/home/api/src/main/kotlin/io/element/android/features/home/api/HomeEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,21 +13,22 @@ import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.JoinedRoom interface HomeEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - interface NodeBuilder { - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: Callback, + ): Node interface Callback : Plugin { - fun onRoomClick(roomId: RoomId) - fun onStartChatClick() - fun onSettingsClick() - fun onSetUpRecoveryClick() - fun onSessionConfirmRecoveryKeyClick() - fun onRoomSettingsClick(roomId: RoomId) - fun onReportBugClick() + fun navigateToRoom(roomId: RoomId, joinedRoom: JoinedRoom?) + fun navigateToCreateRoom() + fun navigateToSettings() + fun navigateToSetUpRecovery() + fun navigateToEnterRecoveryKey() + fun navigateToRoomSettings(roomId: RoomId) + fun navigateToBugReport() } } diff --git a/features/home/impl/build.gradle.kts b/features/home/impl/build.gradle.kts index 9a29532ef0..b36ee6aed2 100644 --- a/features/home/impl/build.gradle.kts +++ b/features/home/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -56,7 +57,7 @@ dependencies { implementation(libs.haze) implementation(libs.haze.materials) implementation(projects.features.reportroom.api) - implementation(projects.features.changeroommemberroles.api) + implementation(projects.features.rolesandpermissions.api) implementation(projects.libraries.previewutils) api(projects.features.home.api) diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/CurrentUserWithNeighborsBuilder.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/CurrentUserWithNeighborsBuilder.kt index 9c29766067..b29dc788a7 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/CurrentUserWithNeighborsBuilder.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/CurrentUserWithNeighborsBuilder.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/DefaultHomeEntryPoint.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/DefaultHomeEntryPoint.kt index 272a9bc9b1..8da31b601d 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/DefaultHomeEntryPoint.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/DefaultHomeEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,28 +10,18 @@ package io.element.android.features.home.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.home.api.HomeEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultHomeEntryPoint : HomeEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): HomeEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : HomeEntryPoint.NodeBuilder { - override fun callback(callback: HomeEntryPoint.Callback): HomeEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: HomeEntryPoint.Callback, + ): Node { + return parentNode.createNode(buildContext, listOf(callback)) } } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeEvents.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeEvents.kt index bc0f821845..db9dafba63 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeEvents.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeFlowNode.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeFlowNode.kt index 94f243b634..d9f87e2edd 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeFlowNode.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,6 +14,8 @@ import androidx.activity.compose.LocalActivity import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.lifecycle.Lifecycle import androidx.lifecycle.coroutineScope @@ -21,7 +24,6 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push @@ -29,8 +31,6 @@ import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.annotations.ContributesNode -import io.element.android.features.changeroommemberroes.api.ChangeRoomMemberRolesEntryPoint -import io.element.android.features.changeroommemberroes.api.ChangeRoomMemberRolesListType import io.element.android.features.home.api.HomeEntryPoint import io.element.android.features.home.impl.components.RoomListMenuAction import io.element.android.features.home.impl.model.RoomListRoomSummary @@ -41,19 +41,35 @@ import io.element.android.features.invite.api.declineandblock.DeclineInviteAndBl import io.element.android.features.leaveroom.api.LeaveRoomRenderer import io.element.android.features.logout.api.direct.DirectLogoutView import io.element.android.features.reportroom.api.ReportRoomEntryPoint +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.appyx.launchMolecule +import io.element.android.libraries.architecture.callback +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.deeplink.api.usecase.InviteFriendsUseCase +import io.element.android.libraries.designsystem.components.ProgressDialog +import io.element.android.libraries.designsystem.utils.DelayedVisibility import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.services.analytics.api.AnalyticsService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.job import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize +import timber.log.Timber +import kotlin.coroutines.cancellation.CancellationException +import kotlin.time.Duration.Companion.milliseconds @ContributesNode(SessionScope::class) @AssistedInject @@ -70,6 +86,7 @@ class HomeFlowNode( private val declineInviteAndBlockUserEntryPoint: DeclineInviteAndBlockEntryPoint, private val changeRoomMemberRolesEntryPoint: ChangeRoomMemberRolesEntryPoint, private val leaveRoomRenderer: LeaveRoomRenderer, + @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.Root, @@ -78,6 +95,7 @@ class HomeFlowNode( buildContext = buildContext, plugins = plugins ) { + private val callback: HomeEntryPoint.Callback = callback() private val stateFlow = launchMolecule { presenter.present() } override fun onBuilt() { @@ -87,13 +105,17 @@ class HomeFlowNode( analyticsService.screen(MobileScreen(screenName = MobileScreen.ScreenName.Home)) } ) - whenChildAttached { commonLifecycle: Lifecycle, - changeRoomMemberRolesNode: ChangeRoomMemberRolesEntryPoint.NodeProxy -> + whenChildAttached { + commonLifecycle: Lifecycle, + changeRoomMemberRolesNode: ChangeRoomMemberRolesEntryPoint.NodeProxy, + -> commonLifecycle.coroutineScope.launch { - changeRoomMemberRolesNode.waitForRoleChanged() + val isNewOwnerSelected = changeRoomMemberRolesNode.waitForCompletion() withContext(NonCancellable) { backstack.pop() - onNewOwnersSelected(changeRoomMemberRolesNode.roomId) + if (isNewOwnerSelected) { + onNewOwnersSelected(changeRoomMemberRolesNode.roomId) + } } } } @@ -113,35 +135,11 @@ class HomeFlowNode( data class SelectNewOwnersWhenLeavingRoom(val roomId: RoomId) : NavTarget } - private fun onRoomClick(roomId: RoomId) { - plugins().forEach { it.onRoomClick(roomId) } - } - - private fun onOpenSettings() { - plugins().forEach { it.onSettingsClick() } - } - - private fun onStartChatClick() { - plugins().forEach { it.onStartChatClick() } - } - - private fun onSetUpRecoveryClick() { - plugins().forEach { it.onSetUpRecoveryClick() } - } - - private fun onSessionConfirmRecoveryKeyClick() { - plugins().forEach { it.onSessionConfirmRecoveryKeyClick() } - } - - private fun onRoomSettingsClick(roomId: RoomId) { - plugins().forEach { it.onRoomSettingsClick(roomId) } - } - - private fun onReportRoomClick(roomId: RoomId) { + private fun navigateToReportRoom(roomId: RoomId) { backstack.push(NavTarget.ReportRoom(roomId)) } - private fun onDeclineInviteAndBlockUserClick(roomSummary: RoomListRoomSummary) { + private fun navigateToDeclineInviteAndBlockUser(roomSummary: RoomListRoomSummary) { backstack.push(NavTarget.DeclineInviteAndBlockUser(roomSummary.toInviteData())) } @@ -151,12 +149,12 @@ class HomeFlowNode( inviteFriendsUseCase.execute(activity) } RoomListMenuAction.ReportBug -> { - plugins().forEach { it.onReportBugClick() } + callback.navigateToBugReport() } } } - private fun onSelectNewOwnersWhenLeavingRoom(roomId: RoomId) { + private fun navigateToSelectNewOwnersWhenLeavingRoom(roomId: RoomId) { backstack.push(NavTarget.SelectNewOwnersWhenLeavingRoom(roomId)) } @@ -168,22 +166,71 @@ class HomeFlowNode( return node(buildContext) { modifier -> val state by stateFlow.collectAsState() val activity = requireNotNull(LocalActivity.current) + + val loadingJoinedRoomJob = remember { mutableStateOf>(AsyncData.Uninitialized) } + if (loadingJoinedRoomJob.value.isLoading()) { + DelayedVisibility(duration = 400.milliseconds) { + ProgressDialog( + onDismissRequest = { + loadingJoinedRoomJob.value.dataOrNull()?.cancel() + loadingJoinedRoomJob.value = AsyncData.Uninitialized + } + ) + } + } + + fun navigateToRoom( + roomId: RoomId, + ) { + if (!loadingJoinedRoomJob.value.isUninitialized()) { + Timber.w("Already loading a room, ignoring navigateToRoom for $roomId") + return + } + + val job = sessionCoroutineScope.launch { + runCatchingExceptions { + matrixClient.getJoinedRoom(roomId) + }.fold( + onSuccess = { joinedRoom -> + if (isActive) { + callback.navigateToRoom(roomId, joinedRoom) + loadingJoinedRoomJob.value = AsyncData.Success(coroutineContext.job) + // Wait a bit before resetting the state to avoid allowing to open several rooms + delay(200.milliseconds) + loadingJoinedRoomJob.value = AsyncData.Uninitialized + } + }, + onFailure = { + // If the operation wasn't cancelled, navigate without the room, using the room id + if (it !is CancellationException) { + callback.navigateToRoom(roomId, null) + } + loadingJoinedRoomJob.value = AsyncData.Failure(error = it, prevData = coroutineContext.job) + // Wait a bit before resetting the state to avoid allowing to open several rooms + delay(200.milliseconds) + loadingJoinedRoomJob.value = AsyncData.Uninitialized + } + ) + } + loadingJoinedRoomJob.value = AsyncData.Loading(job) + } + HomeView( homeState = state, - onRoomClick = this::onRoomClick, - onSettingsClick = this::onOpenSettings, - onStartChatClick = this::onStartChatClick, - onSetUpRecoveryClick = this::onSetUpRecoveryClick, - onConfirmRecoveryKeyClick = this::onSessionConfirmRecoveryKeyClick, - onRoomSettingsClick = this::onRoomSettingsClick, + onRoomClick = ::navigateToRoom, + onSettingsClick = callback::navigateToSettings, + onStartChatClick = callback::navigateToCreateRoom, + onSetUpRecoveryClick = callback::navigateToSetUpRecovery, + onConfirmRecoveryKeyClick = callback::navigateToEnterRecoveryKey, + onRoomSettingsClick = callback::navigateToRoomSettings, onMenuActionClick = { onMenuActionClick(activity, it) }, - onReportRoomClick = this::onReportRoomClick, - onDeclineInviteAndBlockUser = this::onDeclineInviteAndBlockUserClick, + onReportRoomClick = ::navigateToReportRoom, + onDeclineInviteAndBlockUser = ::navigateToDeclineInviteAndBlockUser, modifier = modifier, acceptDeclineInviteView = { acceptDeclineInviteView.Render( state = state.roomListState.acceptDeclineInviteState, - onAcceptInviteSuccess = this::onRoomClick, + onAcceptInviteSuccess = ::navigateToRoom, onDeclineInviteSuccess = { }, modifier = Modifier ) @@ -191,7 +238,7 @@ class HomeFlowNode( leaveRoomView = { leaveRoomRenderer.Render( state = state.roomListState.leaveRoomState, - onSelectNewOwners = this::onSelectNewOwnersWhenLeavingRoom, + onSelectNewOwners = ::navigateToSelectNewOwnersWhenLeavingRoom, modifier = Modifier ) } @@ -207,14 +254,28 @@ class HomeFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { - is NavTarget.ReportRoom -> reportRoomEntryPoint.createNode(this, buildContext, navTarget.roomId) - is NavTarget.DeclineInviteAndBlockUser -> declineInviteAndBlockUserEntryPoint.createNode(this, buildContext, navTarget.inviteData) + is NavTarget.ReportRoom -> { + reportRoomEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + roomId = navTarget.roomId, + ) + } + is NavTarget.DeclineInviteAndBlockUser -> { + declineInviteAndBlockUserEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + inviteData = navTarget.inviteData, + ) + } is NavTarget.SelectNewOwnersWhenLeavingRoom -> { val room = runBlocking { matrixClient.getJoinedRoom(navTarget.roomId) } ?: error("Room ${navTarget.roomId} not found") - changeRoomMemberRolesEntryPoint.builder(this, buildContext) - .room(room) - .listType(ChangeRoomMemberRolesListType.SelectNewOwnersWhenLeaving) - .build() + changeRoomMemberRolesEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + room = room, + listType = ChangeRoomMemberRolesListType.SelectNewOwnersWhenLeaving, + ) } NavTarget.Root -> rootNode(buildContext) } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeNavigationBarItem.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeNavigationBarItem.kt index 328661c834..035e953b23 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeNavigationBarItem.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeNavigationBarItem.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -27,7 +28,7 @@ enum class HomeNavigationBarItem( isSelected: Boolean, ) = when (this) { Chats -> if (isSelected) CompoundIcons.ChatSolid() else CompoundIcons.Chat() - Spaces -> if (isSelected) CompoundIcons.WorkspaceSolid() else CompoundIcons.Workspace() + Spaces -> if (isSelected) CompoundIcons.SpaceSolid() else CompoundIcons.Space() } companion object { diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomePresenter.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomePresenter.kt index e3ca9612d1..e53d20857e 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomePresenter.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomePresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -85,7 +86,7 @@ class HomePresenter( val showAvatarIndicator by indicatorService.showRoomListTopBarIndicator() val directLogoutState = logoutPresenter.present() - fun handleEvents(event: HomeEvents) { + fun handleEvent(event: HomeEvents) { when (event) { is HomeEvents.SelectHomeNavigationBarItem -> coroutineState.launch { if (event.item == HomeNavigationBarItem.Spaces) { @@ -117,7 +118,7 @@ class HomePresenter( canReportBug = canReportBug, directLogoutState = directLogoutState, isSpaceFeatureEnabled = isSpaceFeatureEnabled, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeState.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeState.kt index d35412734f..90667a8734 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeState.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeState.kt @@ -1,13 +1,13 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.home.impl -import androidx.compose.runtime.Immutable import io.element.android.features.home.impl.roomlist.RoomListState import io.element.android.features.home.impl.spaces.HomeSpacesState import io.element.android.features.logout.api.direct.DirectLogoutState @@ -15,7 +15,6 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.collections.immutable.ImmutableList -@Immutable data class HomeState( /** * The current user of this session, in case of multiple accounts, will contains 3 items, with the @@ -34,5 +33,6 @@ data class HomeState( val eventSink: (HomeEvents) -> Unit, ) { val displayActions = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats + val displayRoomListFilters = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats && roomListState.displayFilters val showNavigationBar = isSpaceFeatureEnabled && homeSpacesState.spaceRooms.isNotEmpty() } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeStateProvider.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeStateProvider.kt index c5bb339661..43010b1720 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeStateProvider.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt index aa4742f074..42e4f72073 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomeView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,7 +19,7 @@ import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState @@ -40,9 +41,9 @@ import dev.chrisbanes.haze.materials.HazeMaterials import dev.chrisbanes.haze.rememberHazeState import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.home.impl.components.HomeTopBar import io.element.android.features.home.impl.components.RoomListContentView import io.element.android.features.home.impl.components.RoomListMenuAction -import io.element.android.features.home.impl.components.RoomListTopBar import io.element.android.features.home.impl.model.RoomListRoomSummary import io.element.android.features.home.impl.roomlist.RoomListContextMenu import io.element.android.features.home.impl.roomlist.RoomListDeclineInviteMenu @@ -50,7 +51,6 @@ import io.element.android.features.home.impl.roomlist.RoomListEvents import io.element.android.features.home.impl.roomlist.RoomListState import io.element.android.features.home.impl.search.RoomListSearchView import io.element.android.features.home.impl.spaces.HomeSpacesView -import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorContainer import io.element.android.libraries.androidutils.throttler.FirstThrottler import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -64,6 +64,7 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState import io.element.android.libraries.matrix.api.core.RoomId +import kotlinx.coroutines.launch @Composable fun HomeView( @@ -84,56 +85,47 @@ fun HomeView( val state: RoomListState = homeState.roomListState val coroutineScope = rememberCoroutineScope() val firstThrottler = remember { FirstThrottler(300, coroutineScope) } - - ConnectivityIndicatorContainer( - modifier = modifier, - isOnline = homeState.hasNetworkConnection, - ) { topPadding -> - Box { - if (state.contextMenu is RoomListState.ContextMenu.Shown) { - RoomListContextMenu( - contextMenu = state.contextMenu, - canReportRoom = state.canReportRoom, - eventSink = state.eventSink, - onRoomSettingsClick = onRoomSettingsClick, - onReportRoomClick = onReportRoomClick, - ) - } - if (state.declineInviteMenu is RoomListState.DeclineInviteMenu.Shown) { - RoomListDeclineInviteMenu( - menu = state.declineInviteMenu, - canReportRoom = state.canReportRoom, - eventSink = state.eventSink, - onDeclineAndBlockClick = onDeclineInviteAndBlockUser, - ) - } - - leaveRoomView() - - HomeScaffold( - state = homeState, - onSetUpRecoveryClick = onSetUpRecoveryClick, - onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick, - onRoomClick = { if (firstThrottler.canHandle()) onRoomClick(it) }, - onOpenSettings = { if (firstThrottler.canHandle()) onSettingsClick() }, - onStartChatClick = { if (firstThrottler.canHandle()) onStartChatClick() }, - onMenuActionClick = onMenuActionClick, - modifier = Modifier.padding(top = topPadding), - ) - // This overlaid view will only be visible when state.displaySearchResults is true - RoomListSearchView( - state = state.searchState, + Box(modifier) { + if (state.contextMenu is RoomListState.ContextMenu.Shown) { + RoomListContextMenu( + contextMenu = state.contextMenu, + canReportRoom = state.canReportRoom, eventSink = state.eventSink, - hideInvitesAvatars = state.hideInvitesAvatars, - onRoomClick = { if (firstThrottler.canHandle()) onRoomClick(it) }, - modifier = Modifier - .statusBarsPadding() - .padding(top = topPadding) - .fillMaxSize() - .background(ElementTheme.colors.bgCanvasDefault) + onRoomSettingsClick = onRoomSettingsClick, + onReportRoomClick = onReportRoomClick, ) - acceptDeclineInviteView() } + if (state.declineInviteMenu is RoomListState.DeclineInviteMenu.Shown) { + RoomListDeclineInviteMenu( + menu = state.declineInviteMenu, + canReportRoom = state.canReportRoom, + eventSink = state.eventSink, + onDeclineAndBlockClick = onDeclineInviteAndBlockUser, + ) + } + + leaveRoomView() + + HomeScaffold( + state = homeState, + onSetUpRecoveryClick = onSetUpRecoveryClick, + onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick, + onRoomClick = { if (firstThrottler.canHandle()) onRoomClick(it) }, + onOpenSettings = { if (firstThrottler.canHandle()) onSettingsClick() }, + onStartChatClick = { if (firstThrottler.canHandle()) onStartChatClick() }, + onMenuActionClick = onMenuActionClick, + ) + // This overlaid view will only be visible when state.displaySearchResults is true + RoomListSearchView( + state = state.searchState, + eventSink = state.eventSink, + hideInvitesAvatars = state.hideInvitesAvatars, + onRoomClick = { if (firstThrottler.canHandle()) onRoomClick(it) }, + modifier = Modifier + .fillMaxSize() + .background(ElementTheme.colors.bgCanvasDefault) + ) + acceptDeclineInviteView() } } @@ -154,7 +146,7 @@ private fun HomeScaffold( } val appBarState = rememberTopAppBarState() - val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(appBarState) + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(appBarState) val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage) val roomListState: RoomListState = state.roomListState @@ -165,11 +157,13 @@ private fun HomeScaffold( } val hazeState = rememberHazeState() + val roomsLazyListState = rememberLazyListState() + val spacesLazyListState = rememberLazyListState() Scaffold( modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - RoomListTopBar( + HomeTopBar( title = stringResource(state.currentHomeNavigationBarItem.labelRes), currentUserAndNeighbors = state.currentUserAndNeighbors, showAvatarIndicator = state.showAvatarIndicator, @@ -182,7 +176,7 @@ private fun HomeScaffold( }, scrollBehavior = scrollBehavior, displayMenuItems = state.displayActions, - displayFilters = roomListState.displayFilters && state.currentHomeNavigationBarItem == HomeNavigationBarItem.Chats, + displayFilters = state.displayRoomListFilters, filtersState = roomListState.filtersState, canReportBug = state.canReportBug, modifier = if (state.isSpaceFeatureEnabled) { @@ -191,41 +185,39 @@ private fun HomeScaffold( style = HazeMaterials.thick(), ) } else { - Modifier - .background(ElementTheme.colors.bgCanvasDefault) + Modifier.background(ElementTheme.colors.bgCanvasDefault) } ) }, bottomBar = { if (state.showNavigationBar) { - NavigationBar( - containerColor = Color.Transparent, - modifier = Modifier - .hazeEffect( - state = hazeState, - style = HazeMaterials.thick(), - ) - ) { - HomeNavigationBarItem.entries.forEach { item -> - val isSelected = state.currentHomeNavigationBarItem == item - NavigationBarItem( - selected = isSelected, - onClick = { - state.eventSink(HomeEvents.SelectHomeNavigationBarItem(item)) - }, - icon = { - NavigationBarIcon( - imageVector = item.icon(isSelected), - ) - }, - label = { - NavigationBarText( - text = stringResource(item.labelRes), - ) + val coroutineScope = rememberCoroutineScope() + HomeBottomBar( + currentHomeNavigationBarItem = state.currentHomeNavigationBarItem, + onItemClick = { item -> + // scroll to top if selecting the same item + if (item == state.currentHomeNavigationBarItem) { + val lazyListStateTarget = when (item) { + HomeNavigationBarItem.Chats -> roomsLazyListState + HomeNavigationBarItem.Spaces -> spacesLazyListState } - ) - } - } + coroutineScope.launch { + if (lazyListStateTarget.firstVisibleItemIndex > 10) { + lazyListStateTarget.scrollToItem(10) + } + // Also reset the scrollBehavior height offset as it's not triggered by programmatic scrolls + scrollBehavior.state.heightOffset = 0f + lazyListStateTarget.animateScrollToItem(0) + } + } else { + state.eventSink(HomeEvents.SelectHomeNavigationBarItem(item)) + } + }, + modifier = Modifier.hazeEffect( + state = hazeState, + style = HazeMaterials.thick(), + ) + ) } }, content = { padding -> @@ -234,6 +226,7 @@ private fun HomeScaffold( RoomListContentView( contentState = roomListState.contentState, filtersState = roomListState.filtersState, + lazyListState = roomsLazyListState, hideInvitesAvatars = roomListState.hideInvitesAvatars, eventSink = roomListState.eventSink, onSetUpRecoveryClick = onSetUpRecoveryClick, @@ -271,6 +264,7 @@ private fun HomeScaffold( .consumeWindowInsets(padding) .hazeSource(state = hazeState), state = state.homeSpacesState, + lazyListState = spacesLazyListState, onSpaceClick = { spaceId -> onRoomClick(spaceId) } @@ -294,6 +288,38 @@ private fun HomeScaffold( ) } +@Composable +private fun HomeBottomBar( + currentHomeNavigationBarItem: HomeNavigationBarItem, + onItemClick: (HomeNavigationBarItem) -> Unit, + modifier: Modifier = Modifier, +) { + NavigationBar( + containerColor = Color.Transparent, + modifier = modifier + ) { + HomeNavigationBarItem.entries.forEach { item -> + val isSelected = currentHomeNavigationBarItem == item + NavigationBarItem( + selected = isSelected, + onClick = { + onItemClick(item) + }, + icon = { + NavigationBarIcon( + imageVector = item.icon(isSelected), + ) + }, + label = { + NavigationBarText( + text = stringResource(item.labelRes), + ) + } + ) + } + } +} + internal fun RoomListRoomSummary.contentType() = displayType.ordinal @PreviewsDayNight diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/BannerPadding.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/BannerPadding.kt index 389c2aecfb..cc3227f965 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/BannerPadding.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/BannerPadding.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/BatteryOptimizationBanner.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/BatteryOptimizationBanner.kt index 017ca9592d..c6a9480bc9 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/BatteryOptimizationBanner.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/BatteryOptimizationBanner.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/ConfirmRecoveryKeyBanner.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/ConfirmRecoveryKeyBanner.kt index c833e2914a..c0f83538c9 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/ConfirmRecoveryKeyBanner.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/ConfirmRecoveryKeyBanner.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/FullScreenIntentPermissionBanner.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/FullScreenIntentPermissionBanner.kt index e89392da62..92a5d8dbaa 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/FullScreenIntentPermissionBanner.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/FullScreenIntentPermissionBanner.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomListTopBar.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/HomeTopBar.kt similarity index 51% rename from features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomListTopBar.kt rename to features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/HomeTopBar.kt index abd6e7892d..093b91fb66 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomListTopBar.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/HomeTopBar.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,14 +11,12 @@ package io.element.android.features.home.impl.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.pager.VerticalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.rememberTopAppBarState @@ -32,7 +31,6 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.heading @@ -46,22 +44,20 @@ import io.element.android.features.home.impl.filters.RoomListFiltersState import io.element.android.features.home.impl.filters.RoomListFiltersView import io.element.android.features.home.impl.filters.aRoomListFiltersState import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom +import io.element.android.libraries.designsystem.components.TopAppBarScrollBehaviorLayout import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarType import io.element.android.libraries.designsystem.modifiers.backgroundVerticalGradient import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.designsystem.text.applyScaleDown -import io.element.android.libraries.designsystem.text.toSp import io.element.android.libraries.designsystem.theme.aliasScreenTitle import io.element.android.libraries.designsystem.theme.components.DropdownMenu import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem -import io.element.android.libraries.designsystem.theme.components.HorizontalDivider import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton -import io.element.android.libraries.designsystem.theme.components.MediumTopAppBar import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.user.MatrixUser @@ -76,7 +72,7 @@ import kotlinx.collections.immutable.toImmutableList @OptIn(ExperimentalMaterial3Api::class) @Composable -fun RoomListTopBar( +fun HomeTopBar( title: String, currentUserAndNeighbors: ImmutableList, showAvatarIndicator: Boolean, @@ -87,169 +83,113 @@ fun RoomListTopBar( onAccountSwitch: (SessionId) -> Unit, scrollBehavior: TopAppBarScrollBehavior, displayMenuItems: Boolean, + canReportBug: Boolean, displayFilters: Boolean, filtersState: RoomListFiltersState, - canReportBug: Boolean, modifier: Modifier = Modifier, ) { - DefaultRoomListTopBar( - title = title, - currentUserAndNeighbors = currentUserAndNeighbors, - showAvatarIndicator = showAvatarIndicator, - areSearchResultsDisplayed = areSearchResultsDisplayed, - onOpenSettings = onOpenSettings, - onAccountSwitch = onAccountSwitch, - onSearchClick = onToggleSearch, - onMenuActionClick = onMenuActionClick, - scrollBehavior = scrollBehavior, - displayMenuItems = displayMenuItems, - displayFilters = displayFilters, - filtersState = filtersState, - canReportBug = canReportBug, - modifier = modifier, - ) -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun DefaultRoomListTopBar( - title: String, - currentUserAndNeighbors: ImmutableList, - showAvatarIndicator: Boolean, - areSearchResultsDisplayed: Boolean, - scrollBehavior: TopAppBarScrollBehavior, - onOpenSettings: () -> Unit, - onAccountSwitch: (SessionId) -> Unit, - onSearchClick: () -> Unit, - onMenuActionClick: (RoomListMenuAction) -> Unit, - displayMenuItems: Boolean, - displayFilters: Boolean, - filtersState: RoomListFiltersState, - canReportBug: Boolean, - modifier: Modifier = Modifier, -) { - val collapsedFraction = scrollBehavior.state.collapsedFraction - Box(modifier = modifier) { - val collapsedTitleTextStyle = ElementTheme.typography.aliasScreenTitle - val expandedTitleTextStyle = ElementTheme.typography.fontHeadingLgBold.copy( - // Due to a limitation of MediumTopAppBar, and to avoid the text to be truncated, - // ensure that the font size will never be bigger than 28.dp. - fontSize = 28.dp.applyScaleDown().toSp() - ) - MaterialTheme( - colorScheme = ElementTheme.materialColors, - shapes = MaterialTheme.shapes, - typography = ElementTheme.materialTypography.copy( - headlineSmall = expandedTitleTextStyle, - titleLarge = collapsedTitleTextStyle + Column(modifier) { + TopAppBar( + modifier = Modifier + .backgroundVerticalGradient( + isVisible = !areSearchResultsDisplayed, + ) + .statusBarsPadding(), + colors = TopAppBarDefaults.topAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent, ), - ) { - Column { - MediumTopAppBar( - modifier = Modifier - .backgroundVerticalGradient( - isVisible = !areSearchResultsDisplayed, - ) - .statusBarsPadding(), - colors = TopAppBarDefaults.mediumTopAppBarColors( - containerColor = Color.Transparent, - scrolledContainerColor = Color.Transparent, - ), - title = { - Text( - modifier = Modifier.semantics { - heading() - }, - text = title, - ) + title = { + Text( + modifier = Modifier.semantics { + heading() }, - navigationIcon = { - NavigationIcon( - currentUserAndNeighbors = currentUserAndNeighbors, - showAvatarIndicator = showAvatarIndicator, - onAccountSwitch = onAccountSwitch, - onClick = onOpenSettings, + style = ElementTheme.typography.aliasScreenTitle, + text = title, + ) + }, + navigationIcon = { + NavigationIcon( + currentUserAndNeighbors = currentUserAndNeighbors, + showAvatarIndicator = showAvatarIndicator, + onAccountSwitch = onAccountSwitch, + onClick = onOpenSettings, + ) + }, + actions = { + if (displayMenuItems) { + IconButton( + onClick = onToggleSearch, + ) { + Icon( + imageVector = CompoundIcons.Search(), + contentDescription = stringResource(CommonStrings.action_search), ) - }, - actions = { - if (displayMenuItems) { - IconButton( - onClick = onSearchClick, - ) { - Icon( - imageVector = CompoundIcons.Search(), - contentDescription = stringResource(CommonStrings.action_search), + } + if (RoomListConfig.HAS_DROP_DOWN_MENU) { + var showMenu by remember { mutableStateOf(false) } + IconButton( + onClick = { showMenu = !showMenu } + ) { + Icon( + imageVector = CompoundIcons.OverflowVertical(), + contentDescription = null, + ) + } + DropdownMenu( + expanded = showMenu, + onDismissRequest = { showMenu = false } + ) { + if (RoomListConfig.SHOW_INVITE_MENU_ITEM) { + DropdownMenuItem( + onClick = { + showMenu = false + onMenuActionClick(RoomListMenuAction.InviteFriends) + }, + text = { Text(stringResource(id = CommonStrings.action_invite)) }, + leadingIcon = { + Icon( + imageVector = CompoundIcons.ShareAndroid(), + tint = ElementTheme.colors.iconSecondary, + contentDescription = null, + ) + } ) } - if (RoomListConfig.HAS_DROP_DOWN_MENU) { - var showMenu by remember { mutableStateOf(false) } - IconButton( - onClick = { showMenu = !showMenu } - ) { - Icon( - imageVector = CompoundIcons.OverflowVertical(), - contentDescription = null, - ) - } - DropdownMenu( - expanded = showMenu, - onDismissRequest = { showMenu = false } - ) { - if (RoomListConfig.SHOW_INVITE_MENU_ITEM) { - DropdownMenuItem( - onClick = { - showMenu = false - onMenuActionClick(RoomListMenuAction.InviteFriends) - }, - text = { Text(stringResource(id = CommonStrings.action_invite)) }, - leadingIcon = { - Icon( - imageVector = CompoundIcons.ShareAndroid(), - tint = ElementTheme.colors.iconSecondary, - contentDescription = null, - ) - } + if (RoomListConfig.SHOW_REPORT_PROBLEM_MENU_ITEM && canReportBug) { + DropdownMenuItem( + onClick = { + showMenu = false + onMenuActionClick(RoomListMenuAction.ReportBug) + }, + text = { Text(stringResource(id = CommonStrings.common_report_a_problem)) }, + leadingIcon = { + Icon( + imageVector = CompoundIcons.ChatProblem(), + tint = ElementTheme.colors.iconSecondary, + contentDescription = null, ) } - if (RoomListConfig.SHOW_REPORT_PROBLEM_MENU_ITEM && canReportBug) { - DropdownMenuItem( - onClick = { - showMenu = false - onMenuActionClick(RoomListMenuAction.ReportBug) - }, - text = { Text(stringResource(id = CommonStrings.common_report_a_problem)) }, - leadingIcon = { - Icon( - imageVector = CompoundIcons.ChatProblem(), - tint = ElementTheme.colors.iconSecondary, - contentDescription = null, - ) - } - ) - } - } + ) } } - }, - scrollBehavior = scrollBehavior, - windowInsets = WindowInsets(0.dp), - ) - if (displayFilters) { - RoomListFiltersView( - state = filtersState, - modifier = Modifier.padding(bottom = 16.dp) - ) + } } + }, + // We want a 16dp left padding for the navigationIcon : + // 4dp from default TopAppBarHorizontalPadding + // 8dp from AccountIcon default padding (because of IconButton) + // 4dp extra padding using left insets + windowInsets = WindowInsets(left = 4.dp), + ) + if (displayFilters) { + TopAppBarScrollBehaviorLayout(scrollBehavior = scrollBehavior) { + RoomListFiltersView( + state = filtersState, + modifier = Modifier.padding(bottom = 16.dp) + ) } } - - HorizontalDivider( - modifier = Modifier - .fillMaxWidth() - .alpha(collapsedFraction) - .align(Alignment.BottomCenter), - color = ElementTheme.materialColors.outlineVariant, - ) } } @@ -301,9 +241,11 @@ private fun AccountIcon( isCurrentAccount: Boolean, showAvatarIndicator: Boolean, onClick: () -> Unit, + modifier: Modifier = Modifier, ) { + val testTag = if (isCurrentAccount) Modifier.testTag(TestTags.homeScreenSettings) else Modifier IconButton( - modifier = if (isCurrentAccount) Modifier.testTag(TestTags.homeScreenSettings) else Modifier, + modifier = modifier.then(testTag), onClick = onClick, ) { Box { @@ -329,20 +271,20 @@ private fun AccountIcon( @OptIn(ExperimentalMaterial3Api::class) @PreviewsDayNight @Composable -internal fun DefaultRoomListTopBarPreview() = ElementPreview { - DefaultRoomListTopBar( +internal fun HomeTopBarPreview() = ElementPreview { + HomeTopBar( title = stringResource(R.string.screen_roomlist_main_space_title), currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")), showAvatarIndicator = false, areSearchResultsDisplayed = false, - scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()), + scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()), onOpenSettings = {}, onAccountSwitch = {}, - onSearchClick = {}, + onToggleSearch = {}, displayMenuItems = true, + canReportBug = true, displayFilters = true, filtersState = aRoomListFiltersState(), - canReportBug = true, onMenuActionClick = {}, ) } @@ -350,20 +292,20 @@ internal fun DefaultRoomListTopBarPreview() = ElementPreview { @OptIn(ExperimentalMaterial3Api::class) @PreviewsDayNight @Composable -internal fun DefaultRoomListTopBarWithIndicatorPreview() = ElementPreview { - DefaultRoomListTopBar( +internal fun HomeTopBarWithIndicatorPreview() = ElementPreview { + HomeTopBar( title = stringResource(R.string.screen_roomlist_main_space_title), currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")), showAvatarIndicator = true, areSearchResultsDisplayed = false, - scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()), + scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()), onOpenSettings = {}, onAccountSwitch = {}, - onSearchClick = {}, + onToggleSearch = {}, displayMenuItems = true, + canReportBug = true, displayFilters = true, filtersState = aRoomListFiltersState(), - canReportBug = true, onMenuActionClick = {}, ) } @@ -371,20 +313,20 @@ internal fun DefaultRoomListTopBarWithIndicatorPreview() = ElementPreview { @OptIn(ExperimentalMaterial3Api::class) @PreviewsDayNight @Composable -internal fun DefaultRoomListTopBarMultiAccountPreview() = ElementPreview { - DefaultRoomListTopBar( +internal fun HomeTopBarMultiAccountPreview() = ElementPreview { + HomeTopBar( title = stringResource(R.string.screen_roomlist_main_space_title), currentUserAndNeighbors = aMatrixUserList().take(3).toImmutableList(), showAvatarIndicator = false, areSearchResultsDisplayed = false, - scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()), + scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()), onOpenSettings = {}, onAccountSwitch = {}, - onSearchClick = {}, + onToggleSearch = {}, displayMenuItems = true, + canReportBug = true, displayFilters = true, filtersState = aRoomListFiltersState(), - canReportBug = true, onMenuActionClick = {}, ) } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/NewNotificationSoundBanner.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/NewNotificationSoundBanner.kt index f7516e41e8..c1c5ac0aef 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/NewNotificationSoundBanner.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/NewNotificationSoundBanner.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomListContentView.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomListContentView.kt index 34d036b204..364fe596f8 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomListContentView.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomListContentView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,6 +19,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable @@ -60,6 +62,7 @@ import kotlinx.collections.immutable.ImmutableList fun RoomListContentView( contentState: RoomListContentState, filtersState: RoomListFiltersState, + lazyListState: LazyListState, hideInvitesAvatars: Boolean, eventSink: (RoomListEvents) -> Unit, onSetUpRecoveryClick: () -> Unit, @@ -97,6 +100,7 @@ fun RoomListContentView( onSetUpRecoveryClick = onSetUpRecoveryClick, onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick, onRoomClick = onRoomClick, + lazyListState = lazyListState, contentPadding = contentPadding, ) } @@ -176,6 +180,7 @@ private fun RoomsView( onConfirmRecoveryKeyClick: () -> Unit, onRoomClick: (RoomListRoomSummary) -> Unit, contentPadding: PaddingValues, + lazyListState: LazyListState, modifier: Modifier = Modifier, ) { if (state.summaries.isEmpty() && filtersState.hasAnyFilterSelected) { @@ -192,6 +197,7 @@ private fun RoomsView( onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick, onRoomClick = onRoomClick, contentPadding = contentPadding, + lazyListState = lazyListState, modifier = modifier.fillMaxSize(), ) } @@ -206,9 +212,9 @@ private fun RoomsViewList( onConfirmRecoveryKeyClick: () -> Unit, onRoomClick: (RoomListRoomSummary) -> Unit, contentPadding: PaddingValues, + lazyListState: LazyListState, modifier: Modifier = Modifier, ) { - val lazyListState = rememberLazyListState() val visibleRange by remember { derivedStateOf { val layoutInfo = lazyListState.layoutInfo @@ -343,6 +349,7 @@ internal fun RoomListContentViewPreview(@PreviewParameter(RoomListContentStatePr onConfirmRecoveryKeyClick = {}, onRoomClick = {}, onCreateRoomClick = {}, + lazyListState = rememberLazyListState(), contentPadding = PaddingValues(0.dp), ) } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomListMenuAction.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomListMenuAction.kt index 9f17fc835e..2cfc589131 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomListMenuAction.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomListMenuAction.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomSummaryPlaceholderRow.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomSummaryPlaceholderRow.kt index f07fe880b3..466abb76d3 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomSummaryPlaceholderRow.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomSummaryPlaceholderRow.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomSummaryRow.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomSummaryRow.kt index c5e1798baa..50b9559e94 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomSummaryRow.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomSummaryRow.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -39,6 +40,7 @@ import androidx.compose.ui.zIndex import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.home.impl.R +import io.element.android.features.home.impl.model.LatestEvent import io.element.android.features.home.impl.model.RoomListRoomSummary import io.element.android.features.home.impl.model.RoomListRoomSummaryProvider import io.element.android.features.home.impl.model.RoomSummaryDisplayType @@ -218,16 +220,20 @@ private fun NameAndTimestampRow( modifier = modifier.fillMaxWidth(), horizontalArrangement = spacedBy(16.dp) ) { - // Name - Text( + Row( modifier = Modifier.weight(1f), - style = ElementTheme.typography.fontBodyLgMedium, - text = name ?: stringResource(id = CommonStrings.common_no_room_name), - fontStyle = FontStyle.Italic.takeIf { name == null }, - color = ElementTheme.colors.roomListRoomName, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) + verticalAlignment = Alignment.CenterVertically, + ) { + // Name + Text( + style = ElementTheme.typography.fontBodyLgMedium, + text = name ?: stringResource(id = CommonStrings.common_no_room_name), + fontStyle = FontStyle.Italic.takeIf { name == null }, + color = ElementTheme.colors.roomListRoomName, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } // Timestamp Text( text = timestamp ?: "", @@ -271,24 +277,64 @@ private fun MessagePreviewAndIndicatorRow( ) { Row( modifier = modifier.fillMaxWidth(), - horizontalArrangement = spacedBy(28.dp) ) { - val messagePreview = if (room.isTombstoned) { - stringResource(R.string.screen_roomlist_tombstoned_room_description) + if (room.isTombstoned) { + Text( + modifier = Modifier.weight(1f), + text = stringResource(R.string.screen_roomlist_tombstoned_room_description), + color = ElementTheme.colors.roomListRoomMessage, + style = ElementTheme.typography.fontBodyMdRegular, + minLines = 2, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) } else { - room.lastMessage.orEmpty() + if (room.latestEvent is LatestEvent.Error) { + Icon( + modifier = Modifier + .padding(top = 2.dp) + .size(16.dp), + imageVector = CompoundIcons.ErrorSolid(), + // The last message contains the error. + contentDescription = null, + tint = ElementTheme.colors.iconCriticalPrimary, + ) + Spacer(modifier = Modifier.width(6.dp)) + Text( + modifier = Modifier.weight(1f), + text = stringResource(CommonStrings.common_message_failed_to_send), + color = ElementTheme.colors.textCriticalPrimary, + style = ElementTheme.typography.fontBodyMdRegular, + minLines = 2, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + } else { + if (room.latestEvent is LatestEvent.Sending) { + Icon( + modifier = Modifier + .padding(top = 2.dp) + .size(16.dp), + imageVector = CompoundIcons.Time(), + contentDescription = stringResource(CommonStrings.common_sending), + tint = ElementTheme.colors.iconTertiary, + ) + Spacer(modifier = Modifier.width(6.dp)) + } + val messagePreview = room.latestEvent.content() + val annotatedMessagePreview = messagePreview as? AnnotatedString ?: AnnotatedString(text = messagePreview.orEmpty().toString()) + Text( + modifier = Modifier.weight(1f), + text = annotatedMessagePreview, + color = ElementTheme.colors.roomListRoomMessage, + style = ElementTheme.typography.fontBodyMdRegular, + minLines = 2, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + } } - val annotatedMessagePreview = messagePreview as? AnnotatedString ?: AnnotatedString(text = messagePreview.toString()) - Text( - modifier = Modifier.weight(1f), - text = annotatedMessagePreview, - color = ElementTheme.colors.roomListRoomMessage, - style = ElementTheme.typography.fontBodyMdRegular, - minLines = 2, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - + Spacer(modifier = Modifier.width(16.dp)) // Call and unread Row( modifier = Modifier diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/SetUpRecoveryKeyBanner.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/SetUpRecoveryKeyBanner.kt index 37aed6d130..0d3bf87b07 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/SetUpRecoveryKeyBanner.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/SetUpRecoveryKeyBanner.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/datasource/RoomListDataSource.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/datasource/RoomListDataSource.kt index b9e8d27bf7..ef5d1ffcc6 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/datasource/RoomListDataSource.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/datasource/RoomListDataSource.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/datasource/RoomListRoomSummaryFactory.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/datasource/RoomListRoomSummaryFactory.kt index ffd6f640ac..0200f495ef 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/datasource/RoomListRoomSummaryFactory.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/datasource/RoomListRoomSummaryFactory.kt @@ -1,22 +1,25 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.home.impl.datasource import dev.zacsweers.metro.Inject +import io.element.android.features.home.impl.model.LatestEvent import io.element.android.features.home.impl.model.RoomListRoomSummary import io.element.android.features.home.impl.model.RoomSummaryDisplayType import io.element.android.libraries.core.extensions.orEmpty import io.element.android.libraries.dateformatter.api.DateFormatter import io.element.android.libraries.dateformatter.api.DateFormatterMode import io.element.android.libraries.designsystem.components.avatar.AvatarSize -import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter +import io.element.android.libraries.eventformatter.api.RoomLatestEventFormatter import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.isDm +import io.element.android.libraries.matrix.api.roomlist.LatestEventValue import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.matrix.ui.model.toInviteSender @@ -25,7 +28,7 @@ import kotlinx.collections.immutable.toImmutableList @Inject class RoomListRoomSummaryFactory( private val dateFormatter: DateFormatter, - private val roomLastMessageFormatter: RoomLastMessageFormatter, + private val roomLatestEventFormatter: RoomLatestEventFormatter, ) { fun create(roomSummary: RoomSummary): RoomListRoomSummary { val roomInfo = roomSummary.info @@ -39,13 +42,11 @@ class RoomListRoomSummaryFactory( numberOfUnreadNotifications = roomInfo.numUnreadNotifications, isMarkedUnread = roomInfo.isMarkedUnread, timestamp = dateFormatter.format( - timestamp = roomSummary.lastMessageTimestamp, + timestamp = roomSummary.latestEventTimestamp, mode = DateFormatterMode.TimeOrDate, useRelative = true, ), - lastMessage = roomSummary.lastMessage?.let { message -> - roomLastMessageFormatter.format(message.event, roomInfo.isDm) - }.orEmpty(), + latestEvent = computeLatestEvent(roomSummary.latestEvent, roomInfo.isDm), avatarData = avatarData, userDefinedNotificationMode = roomInfo.userDefinedNotificationMode, hasRoomCall = roomInfo.hasRoomCall, @@ -72,4 +73,28 @@ class RoomListRoomSummaryFactory( isSpace = roomInfo.isSpace, ) } + + private fun computeLatestEvent(latestEvent: LatestEventValue, dm: Boolean): LatestEvent { + return when (latestEvent) { + is LatestEventValue.None -> { + LatestEvent.None + } + is LatestEventValue.Local -> { + if (latestEvent.isSending) { + val content = roomLatestEventFormatter.format(latestEvent, dm).orEmpty() + LatestEvent.Sending( + content = content, + ) + } else { + LatestEvent.Error + } + } + is LatestEventValue.Remote -> { + val content = roomLatestEventFormatter.format(latestEvent, dm).orEmpty() + LatestEvent.Synced( + content = content, + ) + } + } + } } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/di/HomeSpacesModule.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/di/HomeSpacesModule.kt index e8631431ca..5e2212375d 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/di/HomeSpacesModule.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/di/HomeSpacesModule.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/di/RoomListModule.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/di/RoomListModule.kt index 926205cbea..ea80c1b3cb 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/di/RoomListModule.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/di/RoomListModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFilter.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFilter.kt index 2c7ebc3573..1f627eca4e 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFilter.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFilter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersEmptyStateResources.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersEmptyStateResources.kt index 61436a0054..7381ac308e 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersEmptyStateResources.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersEmptyStateResources.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersEvents.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersEvents.kt index 13fcc656e8..8b1906d28f 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersEvents.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersPresenter.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersPresenter.kt index 08c34d60ff..4b20c69751 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersPresenter.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -27,7 +28,7 @@ class RoomListFiltersPresenter( @Composable override fun present(): RoomListFiltersState { - fun handleEvents(event: RoomListFiltersEvents) { + fun handleEvent(event: RoomListFiltersEvents) { when (event) { RoomListFiltersEvents.ClearSelectedFilters -> { filterSelectionStrategy.clear() @@ -63,7 +64,7 @@ class RoomListFiltersPresenter( return RoomListFiltersState( filterSelectionStates = filters, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersState.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersState.kt index 215641c5b2..104a99cfbd 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersState.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersStateProvider.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersStateProvider.kt index 8cba01163c..7d73727484 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersStateProvider.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersView.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersView.kt index 91b00b1180..588da6b9db 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersView.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -19,6 +20,7 @@ import androidx.compose.foundation.layout.PaddingValues 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.lazy.LazyRow import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape @@ -34,6 +36,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp @@ -48,6 +51,9 @@ import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.testTag +/** + * Ref: https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=2191-606 + */ @Composable fun RoomListFiltersView( state: RoomListFiltersState, @@ -143,9 +149,12 @@ private fun RoomListClearFiltersButton( .clip(CircleShape) .background(ElementTheme.colors.bgActionPrimaryRest) .clickable(onClick = onClick) + .padding(4.dp) ) { Icon( - modifier = Modifier.align(Alignment.Center), + modifier = Modifier + .align(Alignment.Center) + .size(16.dp), imageVector = CompoundIcons.Close(), tint = ElementTheme.colors.iconOnSolidPrimary, contentDescription = stringResource(id = R.string.screen_roomlist_clear_filters), @@ -170,21 +179,34 @@ private fun RoomListFilterView( animationSpec = spring(stiffness = Spring.StiffnessMediumLow), label = "chip text colour", ) + val borderColour = animateColorAsState( + targetValue = if (selected) Color.Transparent else ElementTheme.colors.borderInteractiveSecondary, + animationSpec = spring(stiffness = Spring.StiffnessMediumLow), + label = "chip border colour", + ) FilterChip( selected = selected, onClick = { onClick(roomListFilter) }, - modifier = modifier.height(36.dp), + modifier = modifier.height(32.dp), shape = CircleShape, colors = FilterChipDefaults.filterChipColors( containerColor = background.value, selectedContainerColor = background.value, labelColor = textColour.value, - selectedLabelColor = textColour.value + selectedLabelColor = textColour.value, ), label = { - Text(text = stringResource(id = roomListFilter.stringResource)) - } + Text( + text = stringResource(id = roomListFilter.stringResource), + style = ElementTheme.typography.fontBodyMdRegular, + ) + }, + border = FilterChipDefaults.filterChipBorder( + enabled = true, + selected = selected, + borderColor = borderColour.value, + ), ) } @@ -192,6 +214,7 @@ private fun RoomListFilterView( @Composable internal fun RoomListFiltersViewPreview(@PreviewParameter(RoomListFiltersStateProvider::class) state: RoomListFiltersState) = ElementPreview { RoomListFiltersView( + modifier = Modifier.padding(vertical = 4.dp), state = state, ) } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/selection/DefaultFilterSelectionStrategy.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/selection/DefaultFilterSelectionStrategy.kt index c1da8b18b2..877e934727 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/selection/DefaultFilterSelectionStrategy.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/selection/DefaultFilterSelectionStrategy.kt @@ -1,20 +1,19 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.home.impl.filters.selection import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.home.impl.filters.RoomListFilter import io.element.android.libraries.di.SessionScope import kotlinx.coroutines.flow.MutableStateFlow @ContributesBinding(SessionScope::class) -@Inject class DefaultFilterSelectionStrategy : FilterSelectionStrategy { private val selectedFilters = LinkedHashSet() diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/selection/FilterSelectionState.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/selection/FilterSelectionState.kt index d604e51f84..3a13688582 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/selection/FilterSelectionState.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/selection/FilterSelectionState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/selection/FilterSelectionStrategy.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/selection/FilterSelectionStrategy.kt index 8396a4e2a2..f0877b5e0d 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/selection/FilterSelectionStrategy.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/filters/selection/FilterSelectionStrategy.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/model/LatestEvent.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/model/LatestEvent.kt new file mode 100644 index 0000000000..d6ddcf258c --- /dev/null +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/model/LatestEvent.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.home.impl.model + +import androidx.compose.runtime.Immutable + +@Immutable +sealed interface LatestEvent { + data object None : LatestEvent + + data class Synced( + val content: CharSequence?, + ) : LatestEvent + + data class Sending( + val content: CharSequence?, + ) : LatestEvent + + data object Error : LatestEvent + + fun content(): CharSequence? { + return when (this) { + is None -> null + is Synced -> content + is Sending -> content + is Error -> null + } + } +} diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/model/RoomListRoomSummary.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/model/RoomListRoomSummary.kt index 8af359d3e5..a59e444455 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/model/RoomListRoomSummary.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/model/RoomListRoomSummary.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -28,7 +29,7 @@ data class RoomListRoomSummary( val numberOfUnreadNotifications: Long, val isMarkedUnread: Boolean, val timestamp: String?, - val lastMessage: CharSequence?, + val latestEvent: LatestEvent, val avatarData: AvatarData, val userDefinedNotificationMode: RoomNotificationMode?, val hasRoomCall: Boolean, diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/model/RoomListRoomSummaryProvider.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/model/RoomListRoomSummaryProvider.kt index 1e763843af..400decff6f 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/model/RoomListRoomSummaryProvider.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/model/RoomListRoomSummaryProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -24,12 +25,14 @@ open class RoomListRoomSummaryProvider : PreviewParameterProvider { private val encryptionService = client.encryptionService @@ -113,7 +116,7 @@ class RoomListPresenter( val contextMenu = remember { mutableStateOf(RoomListState.ContextMenu.Hidden) } val declineInviteMenu = remember { mutableStateOf(RoomListState.DeclineInviteMenu.Hidden) } - fun handleEvents(event: RoomListEvents) { + fun handleEvent(event: RoomListEvents) { when (event) { is RoomListEvents.UpdateVisibleRange -> coroutineScope.launch { updateVisibleRange(event.range) @@ -169,7 +172,7 @@ class RoomListPresenter( acceptDeclineInviteState = acceptDeclineInviteState, hideInvitesAvatars = hideInvitesAvatar, canReportRoom = canReportRoom, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } @@ -235,6 +238,8 @@ class RoomListPresenter( ) showSkeleton -> RoomListContentState.Skeleton(count = 16) else -> { + coldStartWatcher.onRoomListVisible() + RoomListContentState.Rooms( securityBannerState = securityBannerState, showNewNotificationSoundBanner = showNewNotificationSoundBanner, diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListState.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListState.kt index 80cd6394e8..2b5ca0391e 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListState.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -19,7 +20,6 @@ import io.element.android.libraries.push.api.battery.BatteryOptimizationState import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableSet -@Immutable data class RoomListState( val contextMenu: ContextMenu, val declineInviteMenu: DeclineInviteMenu, diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListStateContextMenuShownProvider.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListStateContextMenuShownProvider.kt index 856b7662af..3f2499657e 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListStateContextMenuShownProvider.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListStateContextMenuShownProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListStateProvider.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListStateProvider.kt index faa811b920..188f4468de 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListStateProvider.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/roomlist/RoomListStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,6 +11,7 @@ package io.element.android.features.home.impl.roomlist import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.home.impl.filters.RoomListFiltersState import io.element.android.features.home.impl.filters.aRoomListFiltersState +import io.element.android.features.home.impl.model.LatestEvent import io.element.android.features.home.impl.model.RoomListRoomSummary import io.element.android.features.home.impl.model.RoomSummaryDisplayType import io.element.android.features.home.impl.model.aRoomListRoomSummary @@ -87,7 +89,7 @@ internal fun aRoomListRoomSummaryList(): ImmutableList { name = "Room", numberOfUnreadMessages = 1, timestamp = "14:18", - lastMessage = "A very very very very long message which suites on two lines", + latestEvent = LatestEvent.Synced("A very very very very long message which suites on two lines"), avatarData = AvatarData("!id", "R", size = AvatarSize.RoomListItem), id = "!roomId:domain", ), @@ -95,7 +97,7 @@ internal fun aRoomListRoomSummaryList(): ImmutableList { name = "Room#2", numberOfUnreadMessages = 0, timestamp = "14:16", - lastMessage = "A short message", + latestEvent = LatestEvent.Synced("A short message"), avatarData = AvatarData("!id", "Z", size = AvatarSize.RoomListItem), id = "!roomId2:domain", ), @@ -118,7 +120,7 @@ internal fun generateRoomListRoomSummaryList( name = "Room#$index", numberOfUnreadMessages = 0, timestamp = "14:16", - lastMessage = "A message", + latestEvent = LatestEvent.Synced("A message"), avatarData = AvatarData("!id$index", "${(65 + index % 26).toChar()}", size = AvatarSize.RoomListItem), id = "!roomId$index:domain", ) diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchDataSource.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchDataSource.kt index ef853bd1cf..44f53a2663 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchDataSource.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchDataSource.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchEvents.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchEvents.kt index 26f713da81..20222c144d 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchEvents.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchPresenter.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchPresenter.kt index ba77b0cdce..ad06b12f5e 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchPresenter.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -40,7 +41,7 @@ class RoomListSearchPresenter( dataSource.setSearchQuery(searchQuery) } - fun handleEvents(event: RoomListSearchEvents) { + fun handleEvent(event: RoomListSearchEvents) { when (event) { RoomListSearchEvents.ClearQuery -> { searchQuery = "" @@ -61,7 +62,7 @@ class RoomListSearchPresenter( isSearchActive = isSearchActive, query = searchQuery, results = searchResults, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchState.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchState.kt index 9d50f52a33..92e8c1ff03 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchState.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchStateProvider.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchStateProvider.kt index 503e334418..a5015a5003 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchStateProvider.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchView.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchView.kt index 2302b3325f..58d6ba7e00 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchView.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/search/RoomListSearchView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -20,11 +21,12 @@ import androidx.compose.foundation.lazy.items import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TextFieldDefaults -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind @@ -33,6 +35,7 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.tokens.generated.CompoundIcons @@ -41,7 +44,6 @@ import io.element.android.features.home.impl.contentType import io.element.android.features.home.impl.model.RoomListRoomSummary import io.element.android.features.home.impl.roomlist.RoomListEvents import io.element.android.libraries.designsystem.components.button.BackButton -import io.element.android.libraries.designsystem.components.form.textFieldState import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.FilledTextField @@ -49,7 +51,6 @@ import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.TopAppBar -import io.element.android.libraries.designsystem.utils.copy import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.ui.strings.CommonStrings @@ -111,18 +112,22 @@ private fun RoomListSearchContent( }, navigationIcon = { BackButton(onClick = ::onBackButtonClick) }, title = { - var filter by textFieldState(state.query) + // TODO replace `state.query` with TextFieldState when it's available for M3 TextField + // The stateSaver will keep the selection state when returning to this UI + var value by rememberSaveable(stateSaver = TextFieldValue.Saver) { + mutableStateOf(TextFieldValue(state.query)) + } val focusRequester = remember { FocusRequester() } FilledTextField( modifier = Modifier .fillMaxWidth() .focusRequester(focusRequester), - value = filter, + value = value, singleLine = true, onValueChange = { - filter = it - state.eventSink(RoomListSearchEvents.QueryChanged(it)) + value = it + state.eventSink(RoomListSearchEvents.QueryChanged(it.text)) }, colors = TextFieldDefaults.colors( focusedContainerColor = Color.Transparent, @@ -134,9 +139,11 @@ private fun RoomListSearchContent( errorIndicatorColor = Color.Transparent, ), trailingIcon = { - if (filter.isNotEmpty()) { + if (value.text.isNotEmpty()) { IconButton(onClick = { state.eventSink(RoomListSearchEvents.ClearQuery) + // Clear local state too + value = value.copy(text = "") }) { Icon( imageVector = CompoundIcons.Close(), @@ -148,10 +155,12 @@ private fun RoomListSearchContent( ) LaunchedEffect(Unit) { - focusRequester.requestFocus() + if (!focusRequester.restoreFocusedChild()) { + focusRequester.requestFocus() + } + focusRequester.saveFocusedChild() } }, - windowInsets = TopAppBarDefaults.windowInsets.copy(top = 0) ) } ) { padding -> diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesEvents.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesEvents.kt index 5d07a5e358..88290417dd 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesEvents.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesPresenter.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesPresenter.kt index 8c3e2962ac..a890a61ac3 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesPresenter.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesPresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,7 +17,9 @@ import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentSetOf +import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableSet import kotlinx.coroutines.flow.map @@ -28,12 +31,15 @@ class HomeSpacesPresenter( @Composable override fun present(): HomeSpacesState { val hideInvitesAvatar by client.rememberHideInvitesAvatar() - val spaceRooms by client.spaceService.spaceRoomsFlow.collectAsState(emptyList()) + val spaceRooms by remember { + client.spaceService.spaceRoomsFlow.map { it.toImmutableList() } + }.collectAsState(persistentListOf()) + val seenSpaceInvites by remember { seenInvitesStore.seenRoomIds().map { it.toImmutableSet() } }.collectAsState(persistentSetOf()) - fun handleEvents(event: HomeSpacesEvents) { + fun handleEvent(event: HomeSpacesEvents) { // when (event) { } } @@ -42,7 +48,7 @@ class HomeSpacesPresenter( spaceRooms = spaceRooms, seenSpaceInvites = seenSpaceInvites, hideInvitesAvatar = hideInvitesAvatar, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } } diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesState.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesState.kt index 96733991f9..7dcb370219 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesState.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,11 +10,12 @@ package io.element.android.features.home.impl.spaces import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.spaces.SpaceRoom +import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableSet data class HomeSpacesState( val space: CurrentSpace, - val spaceRooms: List, + val spaceRooms: ImmutableList, val seenSpaceInvites: ImmutableSet, val hideInvitesAvatar: Boolean, val eventSink: (HomeSpacesEvents) -> Unit, diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesStateProvider.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesStateProvider.kt index 921c340886..8c03cff7ee 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesStateProvider.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,6 +12,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.previewutils.room.aSpaceRoom +import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableSet open class HomeSpacesStateProvider : PreviewParameterProvider { @@ -39,7 +41,7 @@ internal fun aHomeSpacesState( eventSink: (HomeSpacesEvents) -> Unit = {}, ) = HomeSpacesState( space = space, - spaceRooms = spaceRooms, + spaceRooms = spaceRooms.toImmutableList(), seenSpaceInvites = seenSpaceInvites.toImmutableSet(), hideInvitesAvatar = hideInvitesAvatar, eventSink = eventSink, diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt index b18e732b42..2505cf831d 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesView.kt @@ -1,19 +1,24 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.home.impl.spaces import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.HorizontalDivider import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.ui.components.SpaceHeaderRootView @@ -25,10 +30,14 @@ import kotlinx.collections.immutable.toImmutableList @Composable fun HomeSpacesView( state: HomeSpacesState, + lazyListState: LazyListState, onSpaceClick: (RoomId) -> Unit, modifier: Modifier = Modifier, ) { - LazyColumn(modifier) { + LazyColumn( + modifier = modifier, + state = lazyListState + ) { val space = state.space when (space) { CurrentSpace.Root -> { @@ -47,20 +56,27 @@ fun HomeSpacesView( ) } } - state.spaceRooms.forEach { spaceRoom -> - item(spaceRoom.roomId) { - val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED - SpaceRoomItemView( - spaceRoom = spaceRoom, - showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites, - hideAvatars = isInvitation && state.hideInvitesAvatar, - onClick = { - onSpaceClick(spaceRoom.roomId) - }, - onLongClick = { - // TODO - }, - ) + item { + HorizontalDivider() + } + itemsIndexed( + items = state.spaceRooms, + key = { _, spaceRoom -> spaceRoom.roomId } + ) { index, spaceRoom -> + val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED + SpaceRoomItemView( + spaceRoom = spaceRoom, + showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites, + hideAvatars = isInvitation && state.hideInvitesAvatar, + onClick = { + onSpaceClick(spaceRoom.roomId) + }, + onLongClick = { + // TODO + }, + ) + if (index != state.spaceRooms.lastIndex) { + HorizontalDivider() } } } @@ -73,6 +89,7 @@ internal fun HomeSpacesViewPreview( ) = ElementPreview { HomeSpacesView( state = state, + lazyListState = rememberLazyListState(), onSpaceClick = {}, modifier = Modifier, ) diff --git a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/SpaceRoomProvider.kt b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/SpaceRoomProvider.kt index b1b3ac1950..4a77e45372 100644 --- a/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/SpaceRoomProvider.kt +++ b/features/home/impl/src/main/kotlin/io/element/android/features/home/impl/spaces/SpaceRoomProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/main/res/values-bg/translations.xml b/features/home/impl/src/main/res/values-bg/translations.xml index f988aee0fd..44a0bd575e 100644 --- a/features/home/impl/src/main/res/values-bg/translations.xml +++ b/features/home/impl/src/main/res/values-bg/translations.xml @@ -4,6 +4,7 @@ "Въведете ключа си за възстановяване" "Хранилището ви за ключове не е синхронизирано" "Всички чатове" + "Пространства" "Сигурни ли сте, че искате да отхвърлите поканата за присъединяване в %1$s?" "Отказване на покана" "Сигурни ли сте, че искате да откажете този личен чат с %1$s?" diff --git a/features/home/impl/src/main/res/values-cs/translations.xml b/features/home/impl/src/main/res/values-cs/translations.xml index 3699b7975e..1eab814e06 100644 --- a/features/home/impl/src/main/res/values-cs/translations.xml +++ b/features/home/impl/src/main/res/values-cs/translations.xml @@ -3,6 +3,8 @@ "Zakažte optimalizaci baterie pro tuto aplikaci, abyste měli jistotu, že budou přijata všechna oznámení." "Zakázat optimalizaci" "Nepřicházejí vám oznámení?" + "Váš zvuk oznámení byl aktualizován – je jasnější, rychlejší a méně rušivý." + "Aktualizovali jsme vaše zvuky" "Vygenerujte nový klíč pro obnovení, který lze použít k obnovení historie šifrovaných zpráv v případě, že ztratíte přístup ke svým zařízením." "Nastavení obnovy" "Nastavení obnovy" diff --git a/features/home/impl/src/main/res/values-da/translations.xml b/features/home/impl/src/main/res/values-da/translations.xml index b686fb6e03..b0763591aa 100644 --- a/features/home/impl/src/main/res/values-da/translations.xml +++ b/features/home/impl/src/main/res/values-da/translations.xml @@ -15,7 +15,7 @@ "For at sikre, at du aldrig går glip af et vigtigt opkald, skal du ændre dine indstillinger til at tillade underretninger i fuld skærm, når din telefon er låst." "Gør din opkaldsoplevelse bedre" "Samtaler" - "Klynger" + "Grupper" "Er du sikker på, at du vil afvise invitationen til at deltage i %1$s?" "Afvis invitation" "Er du sikker på, at du vil afvise denne private samtale med %1$s?" diff --git a/features/home/impl/src/main/res/values-eo/translations.xml b/features/home/impl/src/main/res/values-eo/translations.xml deleted file mode 100644 index 46986026aa..0000000000 --- a/features/home/impl/src/main/res/values-eo/translations.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - "Restore your account security and message history with a backup password if you have lost all your existing devices." - "Set up backup" - "Set up backup to protect your account" - "Confirm your backup password to maintain access to your message backup and message history." - "Enter your backup password" - "Forgot your backup password?" - "Your message backup is out of sync" - "Looks like you\'re using a new device. Confirm it with another linked device to access your encrypted messages." - diff --git a/features/home/impl/src/main/res/values-et/translations.xml b/features/home/impl/src/main/res/values-et/translations.xml index 43dd57cdfa..d5b6fa06d4 100644 --- a/features/home/impl/src/main/res/values-et/translations.xml +++ b/features/home/impl/src/main/res/values-et/translations.xml @@ -3,6 +3,8 @@ "Kui tahad olla kindel, et näed õigel ajal kõiki teavitusi, siis palun lülita akukasutuse optimeerimine välja." "Lülita akukasutuse optimeerimine välja" "Sa ei näe kõiki teavitusi?" + "Sinu nutiseadme teavituste heli on uuenenud - see on nüüd selgem, kiirem ja vähem häiriv." + "Oleme sinu helisid värskendanud" "Loo uus taastevõti, mida saad kasutada oma krüptitud sõnumite ajaloo taastamisel olukorras, kus kaotad ligipääsu oma seadmetele." "Seadista andmete taastamine" "Seadista taastamine" diff --git a/features/home/impl/src/main/res/values-fa/translations.xml b/features/home/impl/src/main/res/values-fa/translations.xml index be83431f7d..aec50309d8 100644 --- a/features/home/impl/src/main/res/values-fa/translations.xml +++ b/features/home/impl/src/main/res/values-fa/translations.xml @@ -6,10 +6,12 @@ "بازگردانی تاریخچهٔ پیام‌ها و هویت رمزنگاشته‌تان با کلید بازیابی در صورت از دست دادن همهٔ افزاره‌های موجودتان." "برپایی بازیابی" "برپایی بازیابی" + "کلید بازیابی خود را تأیید کنید تا دسترسی به حافظه کلیدها و تاریخچه پیام‌هایتان حفظ شود ." "ورود کلید بازیابیتان" "ذخیره‌ساز کلیدتان از هم‌گام بودن در آمده" "بهبود تجریهٔ تماستان" "گپ‌ها" + "فضاها" "مطمئنید که می‌خواهید دعوت پیوستن به %1$s را رد کنید؟" "رد دعوت" "مطمئنید که می‌خواهید این گپ خصوصی با %1$s را رد کنید؟" @@ -19,6 +21,7 @@ "فرایندی یک باره است. ممنون از شکیباییتان." "برپایی حسابتان." "ایجاد اتاق یا گفت‌وگویی جدید" + "پاک کردن پالایه‌ها" "آغاز با پیام دادن به کسی." "هنوز گپی وجود ندارد." "علاقه‌مندی‌ها" @@ -39,6 +42,7 @@ "گپ‌ها" "علامت‌گذاری به عنوان خوانده شده" "نشان به ناخوانده" + "این اتاق ارتقا یافته" "گویا از افزاره‌ای جدید استفاده می‌کنید. تأیید با افزاره‌ای دیگر برای دسترسی به پیام‌های رمزنگاری شده‌تان." "تأیید کنید که خودتانید" diff --git a/features/home/impl/src/main/res/values-hr/translations.xml b/features/home/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..dd1769b998 --- /dev/null +++ b/features/home/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,55 @@ + + + "Onemogućite optimizaciju baterije za ovu aplikaciju kako biste bili sigurni da ćete primati sve obavijesti." + "Onemogući optimizaciju" + "Obavijesti ne stižu?" + "Vaš je signal obavijesti ažuriran – jasniji je, brži i manje ometajući." + "Ažurirali smo vaše zvukove" + "Ako ste izgubili sve postojeće uređaje, oporavite svoj kriptografski identitet i povijest poruka pomoću ključa za oporavak." + "Postavljanje oporavka" + "Postavite oporavak kako biste zaštitili svoj račun" + "Potvrdite svoj ključ za oporavak kako biste zadržali pristup pohrani ključeva i povijesti poruka." + "Unesite svoj ključ za oporavak" + "Zaboravili ste ključ za oporavak?" + "Vaša pohrana ključeva nije sinkronizirana" + "Kako biste bili sigurni da nikada nećete propustiti važan poziv, promijenite postavke kako biste omogućili obavijesti preko cijelog zaslona kada je telefon zaključan." + "Poboljšajte svoje iskustvo poziva" + "Razgovori" + "Prostori" + "Jeste li sigurni da želite odbiti poziv za pridruživanje %1$s?" + "Odbij poziv" + "Jeste li sigurni da želite odbiti ovaj privatni razgovor s korisnikom %1$s?" + "Odbij razgovor" + "Nema pozivnica" + "Pozvao vas je korisnik %1$s (%2$s)" + "Ovaj se postupak izvodi samo jednom, hvala na čekanju." + "Postavljanje vašeg računa." + "Stvori novi razgovor ili sobu" + "Ukloni filtre" + "Započnite tako da nekome pošaljete poruku." + "Još nema razgovora." + "Favoriti" + "Razgovor možete dodati u favorite u postavkama razgovora. +Zasad možete poništiti odabir filtera kako biste vidjeli ostale razgovore." + "Još nemate omiljenih razgovora" + "Pozivnice" + "Nemate pozivnica na čekanju." + "Nizak prioritet" + "Još nemate razgovora niskog prioriteta" + "Možete poništiti odabir filtera kako biste vidjeli ostale razgovore" + "Nemate razgovora za ovaj odabir" + "Osobe" + "Nemate još nijednu izravnu poruku" + "Sobe" + "Niste još ni u jednoj sobi" + "Nepročitano" + "Čestitamo! +Nemate nepročitanih poruka!" + "Zahtjev za pridruživanje je poslan" + "Razgovori" + "Označi kao pročitano" + "Označi kao nepročitano" + "Ova je soba nadograđena" + "Izgleda da koristite novi uređaj. Izvršite provjeru drugim uređajem da biste pristupili svojim šifriranim porukama." + "Potvrdi identitet" + diff --git a/features/home/impl/src/main/res/values-hu/translations.xml b/features/home/impl/src/main/res/values-hu/translations.xml index 7250794095..cca927910b 100644 --- a/features/home/impl/src/main/res/values-hu/translations.xml +++ b/features/home/impl/src/main/res/values-hu/translations.xml @@ -3,6 +3,8 @@ "Kapcsolja ki az alkalmazás akkumulátoroptimalizálását, hogy biztosan megkapja az összes értesítést." "Optimalizálás letiltása" "Nem érkeznek meg az értesítések?" + "Értesítési hangja frissült – tisztább, gyorsabb és kevésbé zavaró lett." + "Frissítettük a hangokat" "Hozzon létre egy új helyreállítási kulcsot, amellyel visszaállíthatja a titkosított üzenetek előzményeit, ha elveszíti az eszközökhöz való hozzáférést." "Helyreállítás beállítása" "Helyreállítás beállítása a fiókja védelméhez" diff --git a/features/home/impl/src/main/res/values-it/translations.xml b/features/home/impl/src/main/res/values-it/translations.xml index 74f8d4abac..932545c7b0 100644 --- a/features/home/impl/src/main/res/values-it/translations.xml +++ b/features/home/impl/src/main/res/values-it/translations.xml @@ -3,6 +3,8 @@ "Disabilita l\'ottimizzazione della batteria per questa app, per assicurarti che tutte le notifiche vengano ricevute." "Disabilita l\'ottimizzazione" "Le notifiche non arrivano?" + "Il ping delle notifiche è stato aggiornato: ora è più chiaro, più rapido e meno fastidioso." + "Abbiamo rinnovato i tuoi suoni" "Recupera la tua identità crittografica e la cronologia dei messaggi con una chiave di recupero se hai perso tutti i tuoi dispositivi." "Configura il recupero" "Configura il ripristino" diff --git a/features/home/impl/src/main/res/values-pl/translations.xml b/features/home/impl/src/main/res/values-pl/translations.xml index 51dbc0512b..73e0b5e52e 100644 --- a/features/home/impl/src/main/res/values-pl/translations.xml +++ b/features/home/impl/src/main/res/values-pl/translations.xml @@ -3,6 +3,8 @@ "Wyłącz optymalizację baterii dla tej aplikacji, aby upewnić się, że wszystkie powiadomienia są odbierane." "Wyłącz optymalizację" "Powiadomienia nie dochodzą?" + "Sygnał powiadomień został zaktualizowany — jest wyraźniejszy, szybszy i mniej uciążliwy." + "Odświeżyliśmy Twoje dźwięki" "Wygeneruj nowy klucz przywracania, którego można użyć do przywrócenia historii wiadomości szyfrowanych w przypadku utraty dostępu do swoich urządzeń." "Skonfiguruj przywracanie" "Skonfiguruj przywracanie" @@ -13,6 +15,7 @@ "Upewnij się, że nie pominiesz żadnego połączenia. Zmień swoje ustawienia i zezwól na powiadomienia na blokadzie ekranu." "Popraw jakość swoich rozmów" "Wszystkie czaty" + "Przestrzenie" "Czy na pewno chcesz odrzucić zaproszenie dołączenia do %1$s?" "Odrzuć zaproszenie" "Czy na pewno chcesz odrzucić rozmowę prywatną z %1$s?" @@ -32,6 +35,7 @@ Na razie możesz wyczyścić filtry, aby zobaczyć pozostałe czaty" "Zaproszenia" "Nie masz żadnych oczekujących zaproszeń." "Niski priorytet" + "Nie masz jeszcze żadnych czatów o niskim priorytecie" "Wyczyść filtry, aby zobaczyć pozostałe czaty" "Brak czatów dla podanych kryteriów" "Osoby" diff --git a/features/home/impl/src/main/res/values-pt-rBR/translations.xml b/features/home/impl/src/main/res/values-pt-rBR/translations.xml index 7aab508a38..10f5cf9585 100644 --- a/features/home/impl/src/main/res/values-pt-rBR/translations.xml +++ b/features/home/impl/src/main/res/values-pt-rBR/translations.xml @@ -3,6 +3,8 @@ "Desative a otimização de bateria para este app, para que tenha certeza que todas as notificações sejam recebidas." "Desativar otimização" "As notificações não chegam?" + "O seu ping de notificação foi atualizado—mais suave, mais rápido, e menos disruptivo." + "Recarregamos seus sons" "Recupere sua identidade criptográfica e o histórico de mensagens com uma chave de recuperação caso você perda todos os dispositivos existentes." "Configurar a recuperação" "Configure a recuperação para proteger sua conta" @@ -33,6 +35,7 @@ Por enquanto, você pode desmarcar os filtros para ver suas outras conversas""Convites" "Você não tem nenhum convite pendente." "Baixa prioridade" + "Você ainda não tem nenhuma conversa de baixa prioridade" "Você pode desmarcar filtros para ver suas outras conversas" "Você não tem conversas para esta seleção" "Pessoas" diff --git a/features/home/impl/src/main/res/values-ru/translations.xml b/features/home/impl/src/main/res/values-ru/translations.xml index 0f77d56db4..a5007784ee 100644 --- a/features/home/impl/src/main/res/values-ru/translations.xml +++ b/features/home/impl/src/main/res/values-ru/translations.xml @@ -44,7 +44,7 @@ "Вас пока нет ни в одной комнате" "Непрочитанные" "Поздравляем! -У вас нет непрочитанных сообщений!" +Все сообщения прочитаны!" "Запрос на присоединение отправлен" "Все чаты" "Пометить как прочитанное" diff --git a/features/home/impl/src/main/res/values-sk/translations.xml b/features/home/impl/src/main/res/values-sk/translations.xml index f44e294432..275a4824b3 100644 --- a/features/home/impl/src/main/res/values-sk/translations.xml +++ b/features/home/impl/src/main/res/values-sk/translations.xml @@ -3,6 +3,8 @@ "Vypnite optimalizáciu batérie pre túto aplikáciu, aby ste sa uistili, že sú prijaté všetky upozornenia." "Zakázať optimalizáciu" "Oznámenia neprichádzajú?" + "Vaše oznámenia boli aktualizované – sú prehľadnejšie, rýchlejšie a menej rušivé." + "Obnovili sme vaše zvuky" "Vytvorte nový kľúč na obnovenie, ktorý môžete použiť na obnovenie vašej histórie šifrovaných správ v prípade straty prístupu k vašim zariadeniam." "Nastaviť obnovenie" "Nastaviť obnovenie" diff --git a/features/home/impl/src/main/res/values-uz/translations.xml b/features/home/impl/src/main/res/values-uz/translations.xml index fbfeca156a..2de9b1b266 100644 --- a/features/home/impl/src/main/res/values-uz/translations.xml +++ b/features/home/impl/src/main/res/values-uz/translations.xml @@ -13,6 +13,7 @@ "Muhim qoʻngʻiroqlarni oʻtkazib yubormasligingiz uchun telefoningiz qulflangan holatida toʻliq ekranli bildirishnomalarni ko‘rsatishga ruxsat beradigan qilib sozlamalaringizni oʻzgartiring." "Qoʻngʻiroq tajribangizni yaxshilang" "Suhbatlar" + "Bo‘shliqlar" "Haqiqatan ham qo\'shilish taklifini rad qilmoqchimisiz%1$s ?" "Taklifni rad etish" "Haqiqatan ham bu shaxsiy chatni rad qilmoqchimisiz%1$s ?" @@ -22,6 +23,7 @@ "Bu bir martalik jarayon, kutganingiz uchun rahmat." "Hisobingiz sozlanmoqda." "Yangi suhbat yoki xona yarating" + "Filtrlarni tozalash" "Kimgadir xabar yuborishdan boshlang." "Hozircha chatlar yo‘q." "Sevimlilar" @@ -31,6 +33,7 @@ Hozircha, boshqa suhbatlaringizni ko‘rish uchun filtrlarni bekor qilishingiz m "Takliflar" "Sizda hech qanday kutilayotgan takliflar yoʻq." "Past darajali" + "Sizda hali past ustuvor chatlar yoʻq" "Boshqa suhbatlaringizni koʻrish uchun filtrlarni bekor qilishingiz mumkin" "Sizda bu tanlov uchun chatlar yo‘q" "Odamlar" @@ -44,6 +47,7 @@ Sizda oʻqilmagan xabarlar yoʻq!" "Suhbatlar" "Oʻqilgan deb belgilash" "Oʻqilmagan deb belgilash" + "Bu xona yangilandi" "Siz yangi qurilmadan foydalanayotganga o‘xshaysiz. Shifrlangan xabarlaringizga kirish uchun boshqa qurilma bilan tasdiqlang." "Siz ekanligingizni tasdiqlang" diff --git a/features/home/impl/src/main/res/values-zh-rTW/translations.xml b/features/home/impl/src/main/res/values-zh-rTW/translations.xml index 625ce3ceb0..20ee59da98 100644 --- a/features/home/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/home/impl/src/main/res/values-zh-rTW/translations.xml @@ -3,6 +3,8 @@ "停用此應用程式的電池最佳化,才能確保收到所有通知。" "停用最佳化" "沒收到通知?" + "您的通知提示音已更新,更清晰、更快、更不易分心。" + "我們已更新您的音效設定" "若您遺失了所有現有裝置,則請使用復原金鑰以救援您的密碼學身份與訊息歷史紀錄。" "設定復原" "設定備援以保護您的帳號" diff --git a/features/home/impl/src/main/res/values-zh/translations.xml b/features/home/impl/src/main/res/values-zh/translations.xml index 805b56de5c..748159d80f 100644 --- a/features/home/impl/src/main/res/values-zh/translations.xml +++ b/features/home/impl/src/main/res/values-zh/translations.xml @@ -3,6 +3,8 @@ "请关闭本应用的电池优化设置,确保不错过任何消息通知。" "禁用优化" "通知未送达?" + "您的通知提示音已升级 - 更清晰、更快速、干扰更少。" + "我们已更新您的声音" "生成新的恢复密钥,该密钥可用于在您无法访问设备时恢复加密的消息历史记录。" "设置恢复" "设置恢复" diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/CurrentUserWithNeighborsBuilderTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/CurrentUserWithNeighborsBuilderTest.kt index a03c0d0065..13c9585386 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/CurrentUserWithNeighborsBuilderTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/CurrentUserWithNeighborsBuilderTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/DefaultHomeEntryPointTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/DefaultHomeEntryPointTest.kt index 7d0c95befd..9778556dd2 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/DefaultHomeEntryPointTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/DefaultHomeEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,17 +13,19 @@ import com.bumble.appyx.core.modality.BuildContext import com.google.common.truth.Truth.assertThat import io.element.android.features.home.api.HomeEntryPoint import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.node.TestParentNode +import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class DefaultHomeEntryPointTest { @Test - fun `test node builder`() { + fun `test node builder`() = runTest { val entryPoint = DefaultHomeEntryPoint() val parentNode = TestParentNode.create { buildContext, plugins -> HomeFlowNode( @@ -36,22 +39,25 @@ class DefaultHomeEntryPointTest { directLogoutView = { _ -> lambdaError() }, reportRoomEntryPoint = { _, _, _ -> lambdaError() }, declineInviteAndBlockUserEntryPoint = { _, _, _ -> lambdaError() }, - changeRoomMemberRolesEntryPoint = { _, _ -> lambdaError() }, + changeRoomMemberRolesEntryPoint = { _, _, _, _ -> lambdaError() }, leaveRoomRenderer = { _, _, _ -> lambdaError() }, + sessionCoroutineScope = backgroundScope, ) } val callback = object : HomeEntryPoint.Callback { - override fun onRoomClick(roomId: RoomId) = lambdaError() - override fun onStartChatClick() = lambdaError() - override fun onSettingsClick() = lambdaError() - override fun onSetUpRecoveryClick() = lambdaError() - override fun onSessionConfirmRecoveryKeyClick() = lambdaError() - override fun onRoomSettingsClick(roomId: RoomId) = lambdaError() - override fun onReportBugClick() = lambdaError() + override fun navigateToRoom(roomId: RoomId, joinedRoom: JoinedRoom?) = lambdaError() + override fun navigateToCreateRoom() = lambdaError() + override fun navigateToSettings() = lambdaError() + override fun navigateToSetUpRecovery() = lambdaError() + override fun navigateToEnterRecoveryKey() = lambdaError() + override fun navigateToRoomSettings(roomId: RoomId) = lambdaError() + override fun navigateToBugReport() = lambdaError() } - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + callback = callback, + ) assertThat(result).isInstanceOf(HomeFlowNode::class.java) assertThat(result.plugins).contains(callback) } diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/FakeDateTimeObserver.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/FakeDateTimeObserver.kt index 6cbebb3135..3e88ff7641 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/FakeDateTimeObserver.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/FakeDateTimeObserver.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/HomePresenterTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/HomePresenterTest.kt index 938e0720d7..0ae3ea1ff3 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/HomePresenterTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/HomePresenterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/datasource/RoomListDataSourceTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/datasource/RoomListDataSourceTest.kt index 03fe2efd94..35c30af86e 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/datasource/RoomListDataSourceTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/datasource/RoomListDataSourceTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/datasource/RoomListRoomSummaryFactoryTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/datasource/RoomListRoomSummaryFactoryTest.kt index 95444b6c4f..5f307918be 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/datasource/RoomListRoomSummaryFactoryTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/datasource/RoomListRoomSummaryFactoryTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,12 +10,13 @@ package io.element.android.features.home.impl.datasource import io.element.android.libraries.dateformatter.api.DateFormatter import io.element.android.libraries.dateformatter.test.FakeDateFormatter -import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter +import io.element.android.libraries.eventformatter.api.RoomLatestEventFormatter +import io.element.android.libraries.eventformatter.test.FakeRoomLatestEventFormatter fun aRoomListRoomSummaryFactory( dateFormatter: DateFormatter = FakeDateFormatter { _, _, _ -> "Today" }, - roomLastMessageFormatter: RoomLastMessageFormatter = RoomLastMessageFormatter { _, _ -> "Hey" } + roomLatestEventFormatter: RoomLatestEventFormatter = FakeRoomLatestEventFormatter(), ) = RoomListRoomSummaryFactory( dateFormatter = dateFormatter, - roomLastMessageFormatter = roomLastMessageFormatter + roomLatestEventFormatter = roomLatestEventFormatter, ) diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersEmptyStateResourcesTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersEmptyStateResourcesTest.kt index 3f4f45afae..250f43ee8f 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersEmptyStateResourcesTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersEmptyStateResourcesTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersPresenterTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersPresenterTest.kt index a71afca607..df1bc18107 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersPresenterTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersViewTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersViewTest.kt index 1fd37d9252..9e99220885 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersViewTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/filters/RoomListFiltersViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/model/RoomListBaseRoomSummaryTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/model/RoomListBaseRoomSummaryTest.kt index b62c19fc7b..28e7051a55 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/model/RoomListBaseRoomSummaryTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/model/RoomListBaseRoomSummaryTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -95,7 +96,7 @@ internal fun createRoomListRoomSummary( numberOfUnreadNotifications = numberOfUnreadNotifications, isMarkedUnread = isMarkedUnread, timestamp = timestamp, - lastMessage = "", + latestEvent = LatestEvent.Synced(""), avatarData = AvatarData(id = A_ROOM_ID.value, name = A_ROOM_NAME, size = AvatarSize.RoomListItem), displayType = displayType, userDefinedNotificationMode = userDefinedNotificationMode, diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListContextMenuTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListContextMenuTest.kt index 66753da035..b692d49d1e 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListContextMenuTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListContextMenuTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListDeclineInviteMenuTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListDeclineInviteMenuTest.kt index 48a51b87bb..0803993a0b 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListDeclineInviteMenuTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListDeclineInviteMenuTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenterTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenterTest.kt index bef312cdea..acc68db018 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenterTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -34,8 +35,8 @@ import io.element.android.features.rageshake.test.logs.FakeAnnouncementService import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.dateformatter.api.DateFormatter import io.element.android.libraries.dateformatter.test.FakeDateFormatter -import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter -import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter +import io.element.android.libraries.eventformatter.api.RoomLatestEventFormatter +import io.element.android.libraries.eventformatter.test.FakeRoomLatestEventFormatter import io.element.android.libraries.fullscreenintent.api.aFullScreenIntentPermissionsState import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId @@ -69,6 +70,7 @@ import io.element.android.libraries.push.api.notifications.NotificationCleaner import io.element.android.libraries.push.test.notifications.FakeNotificationCleaner import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.services.analytics.test.watchers.FakeAnalyticsColdStartWatcher import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.consumeItemsUntilPredicate @@ -637,7 +639,7 @@ class RoomListPresenterTest { client: MatrixClient = FakeMatrixClient(), leaveRoomState: LeaveRoomState = aLeaveRoomState(), dateFormatter: DateFormatter = FakeDateFormatter(), - roomLastMessageFormatter: RoomLastMessageFormatter = FakeRoomLastMessageFormatter(), + roomLatestEventFormatter: RoomLatestEventFormatter = FakeRoomLatestEventFormatter(), sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(), analyticsService: AnalyticsService = FakeAnalyticsService(), filtersPresenter: Presenter = Presenter { aRoomListFiltersState() }, @@ -654,7 +656,7 @@ class RoomListPresenterTest { roomListService = client.roomListService, roomListRoomSummaryFactory = aRoomListRoomSummaryFactory( dateFormatter = dateFormatter, - roomLastMessageFormatter = roomLastMessageFormatter, + roomLatestEventFormatter = roomLatestEventFormatter, ), coroutineDispatchers = testCoroutineDispatchers(), notificationSettingsService = client.notificationSettingsService, @@ -672,5 +674,6 @@ class RoomListPresenterTest { appPreferencesStore = appPreferencesStore, seenInvitesStore = seenInvitesStore, announcementService = announcementService, + coldStartWatcher = FakeAnalyticsColdStartWatcher(), ) } diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListState.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListState.kt index 3837fe6f55..2f158ec4b9 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListState.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListViewTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListViewTest.kt index 4fcbcd170b..bb82d51a79 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListViewTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/roomlist/RoomListViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -169,7 +170,7 @@ class RoomListViewTest { // Remove automatic initial events eventsRecorder.clear() - rule.onNodeWithText(room0.lastMessage!!.toString()).performClick() + rule.onNodeWithText(room0.latestEvent.content().toString()).performClick() } eventsRecorder.assertEmpty() @@ -191,7 +192,7 @@ class RoomListViewTest { ) // Remove automatic initial events eventsRecorder.clear() - rule.onNodeWithText(room0.lastMessage!!.toString()) + rule.onNodeWithText(room0.latestEvent.content().toString()) .performClick() .performClick() } @@ -213,7 +214,7 @@ class RoomListViewTest { // Remove automatic initial events eventsRecorder.clear() - rule.onNodeWithText(room0.lastMessage!!.toString()).performTouchInput { longClick() } + rule.onNodeWithText(room0.latestEvent.content().toString()).performTouchInput { longClick() } eventsRecorder.assertSingle(RoomListEvents.ShowContextMenu(room0)) } diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/search/RoomListSearchPresenterTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/search/RoomListSearchPresenterTest.kt index 7649b6141e..dee0601915 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/search/RoomListSearchPresenterTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/search/RoomListSearchPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,7 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.home.impl.datasource.aRoomListRoomSummaryFactory import io.element.android.libraries.dateformatter.test.FakeDateFormatter -import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter +import io.element.android.libraries.eventformatter.test.FakeRoomLatestEventFormatter import io.element.android.libraries.matrix.api.roomlist.RoomListFilter import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.test.room.aRoomSummary @@ -125,7 +126,7 @@ fun TestScope.createRoomListSearchPresenter( roomListService = roomListService, roomSummaryFactory = aRoomListRoomSummaryFactory( dateFormatter = FakeDateFormatter(), - roomLastMessageFormatter = FakeRoomLastMessageFormatter(), + roomLatestEventFormatter = FakeRoomLatestEventFormatter(), ), coroutineDispatchers = testCoroutineDispatchers(), ), diff --git a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesPresenterTest.kt b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesPresenterTest.kt index ef75dcad81..c7608833ac 100644 --- a/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesPresenterTest.kt +++ b/features/home/impl/src/test/kotlin/io/element/android/features/home/impl/spaces/HomeSpacesPresenterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/api/build.gradle.kts b/features/invite/api/build.gradle.kts index d17b392e59..084ad1088d 100644 --- a/features/invite/api/build.gradle.kts +++ b/features/invite/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/InviteData.kt b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/InviteData.kt index 9f826ac228..696e02a0d7 100644 --- a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/InviteData.kt +++ b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/InviteData.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/SeenInvitesStore.kt b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/SeenInvitesStore.kt index 682970ffe7..046303ca4f 100644 --- a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/SeenInvitesStore.kt +++ b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/SeenInvitesStore.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteEvents.kt b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteEvents.kt index 015fc16182..2caaacab2b 100644 --- a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteEvents.kt +++ b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteState.kt b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteState.kt index f4645383df..3bfd0430fe 100644 --- a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteState.kt +++ b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteStateProvider.kt b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteStateProvider.kt index bd5c5e6749..18acc11e5e 100644 --- a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteStateProvider.kt +++ b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteView.kt b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteView.kt index a098443c45..b6eec03a6f 100644 --- a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteView.kt +++ b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/ConfirmingDeclineInvite.kt b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/ConfirmingDeclineInvite.kt index f0c4366351..0ebf675966 100644 --- a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/ConfirmingDeclineInvite.kt +++ b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/ConfirmingDeclineInvite.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/declineandblock/DeclineInviteAndBlockEntryPoint.kt b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/declineandblock/DeclineInviteAndBlockEntryPoint.kt index 8517fe2786..4174d99222 100644 --- a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/declineandblock/DeclineInviteAndBlockEntryPoint.kt +++ b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/declineandblock/DeclineInviteAndBlockEntryPoint.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,5 +14,9 @@ import io.element.android.features.invite.api.InviteData import io.element.android.libraries.architecture.FeatureEntryPoint fun interface DeclineInviteAndBlockEntryPoint : FeatureEntryPoint { - fun createNode(parentNode: Node, buildContext: BuildContext, inviteData: InviteData): Node + fun createNode( + parentNode: Node, + buildContext: BuildContext, + inviteData: InviteData, + ): Node } diff --git a/features/invite/impl/build.gradle.kts b/features/invite/impl/build.gradle.kts index 4caeafb71f..80b98464f7 100644 --- a/features/invite/impl/build.gradle.kts +++ b/features/invite/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/AcceptInvite.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/AcceptInvite.kt index 0972701435..217c5e4c74 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/AcceptInvite.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/AcceptInvite.kt @@ -1,14 +1,14 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.invite.impl import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import im.vector.app.features.analytics.plan.JoinedRoom import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.libraries.core.extensions.mapFailure @@ -30,7 +30,6 @@ interface AcceptInvite { } @ContributesBinding(SessionScope::class) -@Inject class DefaultAcceptInvite( private val client: MatrixClient, private val joinRoom: JoinRoom, diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/DeclineInvite.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/DeclineInvite.kt index 6c2588de7e..ca3ad28395 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/DeclineInvite.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/DeclineInvite.kt @@ -1,14 +1,14 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.invite.impl import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient @@ -32,7 +32,6 @@ interface DeclineInvite { } @ContributesBinding(SessionScope::class) -@Inject class DefaultDeclineInvite( private val client: MatrixClient, private val notificationCleaner: NotificationCleaner, diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/DefaultSeenInvitesStore.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/DefaultSeenInvitesStore.kt index 38295daa27..9e36b68656 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/DefaultSeenInvitesStore.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/DefaultSeenInvitesStore.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -33,8 +34,7 @@ class DefaultSeenInvitesStore( ) : SeenInvitesStore { init { sessionObserver.addListener(object : SessionListener { - override suspend fun onSessionCreated(userId: String) = Unit - override suspend fun onSessionDeleted(userId: String) { + override suspend fun onSessionDeleted(userId: String, wasLastSession: Boolean) { if (sessionId.value == userId) { clear() } diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/DefaultSeenInvitesStoreFactory.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/DefaultSeenInvitesStoreFactory.kt index 10812eeb80..b1a46f8035 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/DefaultSeenInvitesStoreFactory.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/DefaultSeenInvitesStoreFactory.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.features.invite.impl import android.content.Context import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.libraries.di.annotations.ApplicationContext @@ -21,7 +21,6 @@ import java.util.concurrent.ConcurrentHashMap @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DefaultSeenInvitesStoreFactory( @ApplicationContext private val context: Context, private val sessionObserver: SessionObserver, diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/SeenInvitesStoreFactory.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/SeenInvitesStoreFactory.kt index 4d681e8462..5b5ef54077 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/SeenInvitesStoreFactory.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/SeenInvitesStoreFactory.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInvitePresenter.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInvitePresenter.kt index 7d15971fe4..9be255fe1a 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInvitePresenter.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInvitePresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -39,7 +40,7 @@ class AcceptDeclineInvitePresenter( val declinedAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } - fun handleEvents(event: AcceptDeclineInviteEvents) { + fun handleEvent(event: AcceptDeclineInviteEvents) { when (event) { is AcceptDeclineInviteEvents.AcceptInvite -> { localCoroutineScope.acceptInvite(event.invite.roomId, acceptedAction) @@ -70,7 +71,7 @@ class AcceptDeclineInvitePresenter( return AcceptDeclineInviteState( acceptAction = acceptedAction.value, declineAction = declinedAction.value, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInviteStateProvider.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInviteStateProvider.kt index 6db000d3db..3f8bf93afa 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInviteStateProvider.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInviteStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInviteView.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInviteView.kt index 46cc496508..da035f0ff1 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInviteView.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInviteView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/DefaultAcceptDeclineInviteView.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/DefaultAcceptDeclineInviteView.kt index 487865065d..f819de650c 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/DefaultAcceptDeclineInviteView.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/DefaultAcceptDeclineInviteView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,14 +11,12 @@ package io.element.android.features.invite.impl.acceptdecline import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteView import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.RoomId @ContributesBinding(SessionScope::class) -@Inject class DefaultAcceptDeclineInviteView : AcceptDeclineInviteView { @Composable override fun Render( diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/InternalAcceptDeclineInviteEvents.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/InternalAcceptDeclineInviteEvents.kt index df05b69645..765c7055d3 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/InternalAcceptDeclineInviteEvents.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/acceptdecline/InternalAcceptDeclineInviteEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockEvents.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockEvents.kt index 033d2e58de..1fc160e530 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockEvents.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockNode.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockNode.kt index 51cdf59b6a..2fe5c23088 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockNode.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockNode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockPresenter.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockPresenter.kt index 59812a5e04..af2a10cbfd 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockPresenter.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockPresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -48,7 +49,7 @@ class DeclineAndBlockPresenter( val coroutineScope = rememberCoroutineScope() - fun handleEvents(event: DeclineAndBlockEvents) { + fun handleEvent(event: DeclineAndBlockEvents) { when (event) { DeclineAndBlockEvents.ClearDeclineAction -> declineAction.value = AsyncAction.Uninitialized DeclineAndBlockEvents.Decline -> coroutineScope.decline(reportReason, blockUser, reportRoom, declineAction) @@ -63,7 +64,7 @@ class DeclineAndBlockPresenter( reportReason = reportReason, blockUser = blockUser, declineAction = declineAction.value, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockState.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockState.kt index 0ee7444b2c..7dbbbfd60e 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockState.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockStateProvider.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockStateProvider.kt index 87016d13cc..2e78214306 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockStateProvider.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockView.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockView.kt index 05110f982c..1028a4c142 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockView.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DefaultDeclineAndBlockEntryPoint.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DefaultDeclineAndBlockEntryPoint.kt index ea5456feb2..d6e93fb805 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DefaultDeclineAndBlockEntryPoint.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/declineandblock/DefaultDeclineAndBlockEntryPoint.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,15 +12,17 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.invite.api.InviteData import io.element.android.features.invite.api.declineandblock.DeclineInviteAndBlockEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultDeclineAndBlockEntryPoint : DeclineInviteAndBlockEntryPoint { - override fun createNode(parentNode: Node, buildContext: BuildContext, inviteData: InviteData): Node { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + inviteData: InviteData, + ): Node { val inputs = DeclineAndBlockNode.Inputs(inviteData) return parentNode.createNode(buildContext, plugins = listOf(inputs)) } diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/di/InviteModule.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/di/InviteModule.kt index 3f62408b20..f78c0358a6 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/di/InviteModule.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/di/InviteModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/impl/src/main/res/values-bg/translations.xml b/features/invite/impl/src/main/res/values-bg/translations.xml index e089814fe9..d4c19eca2b 100644 --- a/features/invite/impl/src/main/res/values-bg/translations.xml +++ b/features/invite/impl/src/main/res/values-bg/translations.xml @@ -1,6 +1,7 @@ "Блокиране на потребителя" + "Отхвърляне и блокиране" "Сигурни ли сте, че искате да отхвърлите поканата за присъединяване в %1$s?" "Отказване на покана" "Сигурни ли сте, че искате да откажете този личен чат с %1$s?" diff --git a/features/invite/impl/src/main/res/values-eu/translations.xml b/features/invite/impl/src/main/res/values-eu/translations.xml index 289a8fa418..dfc8310366 100644 --- a/features/invite/impl/src/main/res/values-eu/translations.xml +++ b/features/invite/impl/src/main/res/values-eu/translations.xml @@ -1,7 +1,7 @@ "Blokeatu erabiltzailea" - "Eman ezetza eta blokeatu" + "Baztertu eta blokeatu" "Ziur %1$s(e)ra batzeko gonbidapena baztertu nahi duzula?" "Baztertu gonbidapena" "Ziur %1$s(r)en txat pribatua baztertu nahi duzula?" diff --git a/features/invite/impl/src/main/res/values-hr/translations.xml b/features/invite/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..6d853dacf1 --- /dev/null +++ b/features/invite/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,18 @@ + + + "Nećete vidjeti nikakve poruke ili pozivnice za sobu od ovog korisnika" + "Blokiraj korisnika" + "Prijavite ovu sobu svom davatelju usluga računa." + "Navedite razlog prijave…" + "Odbij i blokiraj" + "Jeste li sigurni da želite odbiti poziv za pridruživanje %1$s?" + "Odbij poziv" + "Jeste li sigurni da želite odbiti ovaj privatni razgovor s korisnikom %1$s?" + "Odbij razgovor" + "Nema pozivnica" + "Pozvao vas je korisnik %1$s (%2$s)" + "Da, odbij i blokiraj" + "Jeste li sigurni da želite odbiti poziv za pridruživanje ovoj sobi? Time ćete također spriječiti da %1$s kontaktira s vama ili vas pozove u sobe." + "Odbij poziv i blokiraj" + "Odbij i blokiraj" + diff --git a/features/invite/impl/src/main/res/values-nl/translations.xml b/features/invite/impl/src/main/res/values-nl/translations.xml index 4abfdda76d..de0f69097c 100644 --- a/features/invite/impl/src/main/res/values-nl/translations.xml +++ b/features/invite/impl/src/main/res/values-nl/translations.xml @@ -1,6 +1,7 @@ "Gebruiker blokkeren" + "Weigeren en blokkeren" "Weet je zeker dat je de uitnodiging om toe te treden tot %1$s wilt weigeren?" "Uitnodiging weigeren" "Weet je zeker dat je deze privéchat met %1$s wilt weigeren?" diff --git a/features/invite/impl/src/main/res/values-pt/translations.xml b/features/invite/impl/src/main/res/values-pt/translations.xml index 1bdf28b8f6..f00ce6151b 100644 --- a/features/invite/impl/src/main/res/values-pt/translations.xml +++ b/features/invite/impl/src/main/res/values-pt/translations.xml @@ -4,7 +4,7 @@ "Bloquear utilizador" "Denunciar esta sala ao fornecedor da tua conta." "Descreve a razão para denunciar…" - "Rejeitar e bloquear" + "Recusar e bloquear" "Tens a certeza que queres rejeitar o convite para entra em %1$s?" "Rejeitar convite" "Tens a certeza que queres rejeitar esta conversa privada com %1$s?" diff --git a/features/invite/impl/src/main/res/values-ru/translations.xml b/features/invite/impl/src/main/res/values-ru/translations.xml index f22b81feb2..90d467e62c 100644 --- a/features/invite/impl/src/main/res/values-ru/translations.xml +++ b/features/invite/impl/src/main/res/values-ru/translations.xml @@ -3,7 +3,7 @@ "Вы не увидите сообщений или приглашений в комнату от этого пользователя" "Заблокировать пользователя" "Сообщите об этой комнате своему поставщику учетной записи." - "Опишите причину…" + "Опишите причину жалобы…" "Отклонить и заблокировать" "Вы уверены, что хотите отклонить приглашение в %1$s?" "Отклонить приглашение" diff --git a/features/invite/impl/src/main/res/values-uz/translations.xml b/features/invite/impl/src/main/res/values-uz/translations.xml index 1db05ad0d9..ab20b58101 100644 --- a/features/invite/impl/src/main/res/values-uz/translations.xml +++ b/features/invite/impl/src/main/res/values-uz/translations.xml @@ -1,11 +1,18 @@ + "Siz bu foydalanuvchidan hech qanday xabar yoki xonaga taklif ko‘rmaysiz" "Foydalanuvchini bloklash" + "Bu xona haqida hisobingiz provayderiga xabar bering." + "Xabar berish sababini tushuntiring…" + "Rad etish va bloklash" "Haqiqatan ham qo\'shilish taklifini rad qilmoqchimisiz%1$s ?" "Taklifni rad etish" "Haqiqatan ham bu shaxsiy chatni rad qilmoqchimisiz%1$s ?" "Chatni rad etish" "Takliflar yo\'q" "%1$s(%2$s ) sizni taklif qildi" + "Ha, rad etish va bloklash" + "Ushbu xonaga qo‘shilish taklifini rad etishga ishonchingiz komilmi? Bu %1$sning siz bilan bog‘lanishiga yoki sizni xonalarga taklif qilishiga ham to‘sqinlik qiladi." + "Taklifni rad etish va bloklash" "Rad etish va bloklash" diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/DefaultAcceptInviteTest.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/DefaultAcceptInviteTest.kt index eeefe688a1..b1a0c30a38 100644 --- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/DefaultAcceptInviteTest.kt +++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/DefaultAcceptInviteTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/DefaultDeclineInviteTest.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/DefaultDeclineInviteTest.kt index cb07bd5191..40621f4c08 100644 --- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/DefaultDeclineInviteTest.kt +++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/DefaultDeclineInviteTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -34,14 +35,14 @@ class DefaultDeclineInviteTest { private val notificationCleaner = FakeNotificationCleaner(clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda) - private val successLeaveRoomLambda = lambdaRecorder> { -> Result.success(Unit) } + private val successLeaveRoomLambda = lambdaRecorder> { Result.success(Unit) } private val successIgnoreUserLambda = lambdaRecorder> { _ -> Result.success(Unit) } private val successReportRoomLambda = lambdaRecorder> { _ -> Result.success(Unit) } private val failureLeaveRoomLambda = - lambdaRecorder> { -> Result.failure(Exception("Leave room error")) } + lambdaRecorder> { Result.failure(Exception("Leave room error")) } private val failureIgnoreUserLambda = lambdaRecorder> { _ -> Result.failure(Exception("Ignore user error")) } private val failureReportRoomLambda = diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInvitePresenterTest.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInvitePresenterTest.kt index 6a5c225acb..ba080293f8 100644 --- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInvitePresenterTest.kt +++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/acceptdecline/AcceptDeclineInvitePresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockPresenterTest.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockPresenterTest.kt index 651b7bb6bb..67c2d55490 100644 --- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockPresenterTest.kt +++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockPresenterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockViewTest.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockViewTest.kt index 60e00f8e35..299fec8565 100644 --- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockViewTest.kt +++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DeclineAndBlockViewTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DefaultDeclineAndBlockEntryPointTest.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DefaultDeclineAndBlockEntryPointTest.kt index 7cdf208b9a..13bb13dccf 100644 --- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DefaultDeclineAndBlockEntryPointTest.kt +++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/declineandblock/DefaultDeclineAndBlockEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/fake/FakeAcceptInvite.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/fake/FakeAcceptInvite.kt index 1ba95bc76e..de210de558 100644 --- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/fake/FakeAcceptInvite.kt +++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/fake/FakeAcceptInvite.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/fake/FakeDeclineInvite.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/fake/FakeDeclineInvite.kt index 18b46dbd5f..cb7af7828b 100644 --- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/fake/FakeDeclineInvite.kt +++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/fake/FakeDeclineInvite.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/test/build.gradle.kts b/features/invite/test/build.gradle.kts index e504873c3b..2df267f155 100644 --- a/features/invite/test/build.gradle.kts +++ b/features/invite/test/build.gradle.kts @@ -1,17 +1,9 @@ /* - * Copyright (c) 2025 New Vector Ltd + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. */ plugins { @@ -26,5 +18,6 @@ dependencies { implementation(libs.coroutines.core) implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrix.test) + implementation(projects.tests.testutils) api(projects.features.invite.api) } diff --git a/features/invite/test/src/main/kotlin/io/element/android/features/invite/test/InMemorySeenInvitesStore.kt b/features/invite/test/src/main/kotlin/io/element/android/features/invite/test/InMemorySeenInvitesStore.kt index 25db72532e..709a4a0f2a 100644 --- a/features/invite/test/src/main/kotlin/io/element/android/features/invite/test/InMemorySeenInvitesStore.kt +++ b/features/invite/test/src/main/kotlin/io/element/android/features/invite/test/InMemorySeenInvitesStore.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/test/src/main/kotlin/io/element/android/features/invite/test/InviteData.kt b/features/invite/test/src/main/kotlin/io/element/android/features/invite/test/InviteData.kt index 62e6d8b322..84810f2e65 100644 --- a/features/invite/test/src/main/kotlin/io/element/android/features/invite/test/InviteData.kt +++ b/features/invite/test/src/main/kotlin/io/element/android/features/invite/test/InviteData.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invite/test/src/main/kotlin/io/element/android/features/invite/test/declineandblock/FakeDeclineInviteAndBlockEntryPoint.kt b/features/invite/test/src/main/kotlin/io/element/android/features/invite/test/declineandblock/FakeDeclineInviteAndBlockEntryPoint.kt new file mode 100644 index 0000000000..52d4d17790 --- /dev/null +++ b/features/invite/test/src/main/kotlin/io/element/android/features/invite/test/declineandblock/FakeDeclineInviteAndBlockEntryPoint.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.invite.test.declineandblock + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.invite.api.InviteData +import io.element.android.features.invite.api.declineandblock.DeclineInviteAndBlockEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeDeclineInviteAndBlockEntryPoint : DeclineInviteAndBlockEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + inviteData: InviteData, + ): Node { + lambdaError() + } +} diff --git a/features/invitepeople/api/build.gradle.kts b/features/invitepeople/api/build.gradle.kts index 3cbd83724a..9fba48462e 100644 --- a/features/invitepeople/api/build.gradle.kts +++ b/features/invitepeople/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleEvents.kt b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleEvents.kt index 0ab097462b..264aafd570 100644 --- a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleEvents.kt +++ b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeoplePresenter.kt b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeoplePresenter.kt index 46903bfb18..0be0798680 100644 --- a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeoplePresenter.kt +++ b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeoplePresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleRenderer.kt b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleRenderer.kt index 30144a11d1..7bad81ab65 100644 --- a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleRenderer.kt +++ b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleRenderer.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleState.kt b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleState.kt index 75fd211295..9d342d191f 100644 --- a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleState.kt +++ b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleStateProvider.kt b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleStateProvider.kt index fdcebb17a2..ce30bcc1f6 100644 --- a/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleStateProvider.kt +++ b/features/invitepeople/api/src/main/kotlin/io/element/android/features/invitepeople/api/InvitePeopleStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invitepeople/impl/build.gradle.kts b/features/invitepeople/impl/build.gradle.kts index f0fcde6a00..e2025405ff 100644 --- a/features/invitepeople/impl/build.gradle.kts +++ b/features/invitepeople/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleEvents.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleEvents.kt index 984eefbc77..b0c8994a62 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleEvents.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt index 5e2b00a3f1..5ac3bc2824 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -103,7 +104,7 @@ class DefaultInvitePeoplePresenter( ) } - fun handleEvents(event: InvitePeopleEvents) { + fun handleEvent(event: InvitePeopleEvents) { when (event) { is DefaultInvitePeopleEvents.OnSearchActiveChanged -> { searchActive = event.active @@ -139,7 +140,7 @@ class DefaultInvitePeoplePresenter( searchResults = searchResults.value, showSearchLoader = showSearchLoader.value, sendInvitesAction = sendInvitesAction.value, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleRenderer.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleRenderer.kt index 3301b7ec2c..59f25f82f1 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleRenderer.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleRenderer.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,13 +11,11 @@ package io.element.android.features.invitepeople.impl import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.invitepeople.api.InvitePeopleRenderer import io.element.android.features.invitepeople.api.InvitePeopleState import io.element.android.libraries.di.SessionScope @ContributesBinding(SessionScope::class) -@Inject class DefaultInvitePeopleRenderer : InvitePeopleRenderer { @Composable override fun Render(state: InvitePeopleState, modifier: Modifier) { diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleState.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleState.kt index 5ae51a8698..917915e4d6 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleState.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleStateProvider.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleStateProvider.kt index ebcd932a55..2f28db8786 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleStateProvider.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeopleStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitableUser.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitableUser.kt index 7dfd6a0f36..d2fd3f5edf 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitableUser.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitableUser.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt index 5363ca43f1..a8e056c7aa 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/invitepeople/impl/src/main/res/values-hr/translations.xml b/features/invitepeople/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..66031c5fd7 --- /dev/null +++ b/features/invitepeople/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,5 @@ + + + "Već je član" + "Već je pozvan/a" + diff --git a/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt b/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt index e51c3ee352..388baf15e6 100644 --- a/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt +++ b/features/invitepeople/impl/src/test/kotlin/io/element/android/features/invitepeople/impl/DefaultInvitePeoplePresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/joinroom/api/build.gradle.kts b/features/joinroom/api/build.gradle.kts index 10c33410d4..6ba5f9c761 100644 --- a/features/joinroom/api/build.gradle.kts +++ b/features/joinroom/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/joinroom/api/src/main/kotlin/io/element/android/features/joinroom/api/JoinRoomEntryPoint.kt b/features/joinroom/api/src/main/kotlin/io/element/android/features/joinroom/api/JoinRoomEntryPoint.kt index 9af4703949..4d61f27763 100644 --- a/features/joinroom/api/src/main/kotlin/io/element/android/features/joinroom/api/JoinRoomEntryPoint.kt +++ b/features/joinroom/api/src/main/kotlin/io/element/android/features/joinroom/api/JoinRoomEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,7 +19,11 @@ import io.element.android.libraries.matrix.api.core.RoomIdOrAlias import java.util.Optional interface JoinRoomEntryPoint : FeatureEntryPoint { - fun createNode(parentNode: Node, buildContext: BuildContext, inputs: Inputs): Node + fun createNode( + parentNode: Node, + buildContext: BuildContext, + inputs: Inputs, + ): Node data class Inputs( val roomId: RoomId, diff --git a/features/joinroom/impl/build.gradle.kts b/features/joinroom/impl/build.gradle.kts index 476aacd863..71acdad128 100644 --- a/features/joinroom/impl/build.gradle.kts +++ b/features/joinroom/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/DefaultJoinRoomEntryPoint.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/DefaultJoinRoomEntryPoint.kt index 5f217b26c2..2343860aab 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/DefaultJoinRoomEntryPoint.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/DefaultJoinRoomEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,14 +12,16 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.joinroom.api.JoinRoomEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultJoinRoomEntryPoint : JoinRoomEntryPoint { - override fun createNode(parentNode: Node, buildContext: BuildContext, inputs: JoinRoomEntryPoint.Inputs): Node { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + inputs: JoinRoomEntryPoint.Inputs, + ): Node { return parentNode.createNode( buildContext = buildContext, plugins = listOf(inputs) diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvents.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvents.kt index 323b556c38..51245dd8f6 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvents.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomFlowNode.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomFlowNode.kt index f501752544..93f8f53412 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomFlowNode.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -64,7 +65,11 @@ class JoinRoomFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { - is NavTarget.DeclineInviteAndBlockUser -> declineAndBlockEntryPoint.createNode(this, buildContext, navTarget.inviteData) + is NavTarget.DeclineInviteAndBlockUser -> declineAndBlockEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + inviteData = navTarget.inviteData, + ) NavTarget.Root -> rootNode(buildContext) } } diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt index 6e123630d6..49c5ffb85f 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -39,8 +40,6 @@ import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomIdOrAlias -import io.element.android.libraries.matrix.api.exception.ClientException -import io.element.android.libraries.matrix.api.exception.ErrorKind import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.RoomInfo import io.element.android.libraries.matrix.api.room.RoomMembershipDetails @@ -141,11 +140,7 @@ class JoinRoomPresenter( preview.previewInfo.toContentState(membershipDetails) }, onFailure = { throwable -> - if (throwable is ClientException.MatrixApi && (throwable.kind == ErrorKind.NotFound || throwable.kind == ErrorKind.Forbidden)) { - ContentState.UnknownRoom - } else { - ContentState.Failure(throwable) - } + ContentState.UnknownRoom } ) } @@ -157,7 +152,7 @@ class JoinRoomPresenter( contentState.markRoomInviteAsSeen() } - fun handleEvents(event: JoinRoomEvents) { + fun handleEvent(event: JoinRoomEvents) { when (event) { JoinRoomEvents.JoinRoom -> coroutineScope.joinRoom(joinAction) is JoinRoomEvents.KnockRoom -> coroutineScope.knockRoom(knockAction, knockMessage) @@ -203,7 +198,7 @@ class JoinRoomPresenter( knockMessage = knockMessage, hideInviteAvatars = hideInviteAvatars, canReportRoom = canReportRoom, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt index f27a290f5d..e109bb1315 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -24,7 +25,6 @@ import kotlinx.collections.immutable.ImmutableList internal const val MAX_KNOCK_MESSAGE_LENGTH = 500 -@Immutable data class JoinRoomState( val roomIdOrAlias: RoomIdOrAlias, val contentState: ContentState, diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt index e5f7df67a3..7e5142321a 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt index 38d084c7d3..8992b92056 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/CancelKnockRoom.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/CancelKnockRoom.kt index 675fb98a0c..11614dc737 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/CancelKnockRoom.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/CancelKnockRoom.kt @@ -1,14 +1,14 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.joinroom.impl.di import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId @@ -18,7 +18,6 @@ interface CancelKnockRoom { } @ContributesBinding(SessionScope::class) -@Inject class DefaultCancelKnockRoom(private val client: MatrixClient) : CancelKnockRoom { override suspend fun invoke(roomId: RoomId): Result { return client diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/ForgetRoom.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/ForgetRoom.kt index 711439a44c..c93ea6807d 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/ForgetRoom.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/ForgetRoom.kt @@ -1,14 +1,14 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.joinroom.impl.di import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId @@ -18,7 +18,6 @@ interface ForgetRoom { } @ContributesBinding(SessionScope::class) -@Inject class DefaultForgetRoom(private val client: MatrixClient) : ForgetRoom { override suspend fun invoke(roomId: RoomId): Result { return client.getRoom(roomId)?.use { it.forget() } diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt index a3a4a778f0..3304169b1a 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/KnockRoom.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/KnockRoom.kt index 4d32043b0f..1e34fddacd 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/KnockRoom.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/KnockRoom.kt @@ -1,14 +1,14 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.joinroom.impl.di import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomIdOrAlias @@ -22,7 +22,6 @@ interface KnockRoom { } @ContributesBinding(SessionScope::class) -@Inject class DefaultKnockRoom(private val client: MatrixClient) : KnockRoom { override suspend fun invoke( roomIdOrAlias: RoomIdOrAlias, diff --git a/features/joinroom/impl/src/main/res/values-cs/translations.xml b/features/joinroom/impl/src/main/res/values-cs/translations.xml index 0c00c0c4bd..85403339e8 100644 --- a/features/joinroom/impl/src/main/res/values-cs/translations.xml +++ b/features/joinroom/impl/src/main/res/values-cs/translations.xml @@ -1,7 +1,7 @@ - "Z této místnosti jste byl vykázán uživatelem %1$s." - "Byl vám zakázán vstup do této místnosti" + "Byli jste vykázáni uživatelem %1$s." + "Byl vám zakázán vstup" "Důvod: %1$s." "Zrušit žádost" "Ano, zrušit" @@ -11,10 +11,10 @@ "Opravdu chcete odmítnout pozvánku do této místnosti? Tím také zabráníte tomu, aby vás %1$s kontaktoval(a) nebo pozval(a) do místností." "Odmítnout pozvání a zablokovat" "Odmítnout a zablokovat" - "Vstup do místnosti se nezdařil." - "Tato místnost je buď určena pouze pro zvané, nebo do ní může být omezen přístup na úrovni prostoru." - "Zapomenout na tuto místnost" - "Abyste se mohli připojit k této místnosti, potřebujete pozvánku." + "Vstup se nezdařil" + "Buď musíte být pozváni ke vstupu, nebo mohou existovat omezení přístupu." + "Zapomenout" + "Pro vstup potřebujete pozvánku" "Pozván(a)" "Vstoupit" "Abyste se mohli připojit, musíte být pozváni nebo být členem některého prostoru." diff --git a/features/joinroom/impl/src/main/res/values-da/translations.xml b/features/joinroom/impl/src/main/res/values-da/translations.xml index ac1cd26191..12613b5878 100644 --- a/features/joinroom/impl/src/main/res/values-da/translations.xml +++ b/features/joinroom/impl/src/main/res/values-da/translations.xml @@ -17,7 +17,7 @@ "Du har brug for en invitation for at deltage" "Inviteret af" "Deltag" - "Du skal muligvis være inviteret eller være medlem af en klynge for at deltage." + "Du skal muligvis være inviteret eller være medlem af en gruppe for at deltage." "Send anmodning om at deltage" "Tilladte tegn %1$d af %2$d" "Besked (valgfrit)" @@ -25,8 +25,8 @@ "Anmodning om at deltage sendt" "Vi kunne ikke forhåndsvise rummet. Dette kan skyldes netværks- eller serverproblemer." "Vi kunne ikke forhåndsvise rummet" - "%1$s understøtter ikke klynger endnu. Du kan få adgang til klynger på nettet." - "Klynger er ikke understøttet endnu" + "%1$s understøtter ikke grupper endnu. Du kan få adgang til grupper på nettet." + "Grupper er ikke understøttet endnu" "Klik på knappen nedenfor, og en rumadministrator vil blive underrettet. Du kan deltage i samtalen, når din anmodning er godkendt." "Du skal være medlem af dette rum for at kunne se meddelelseshistorikken." "Vil du deltage i dette rum?" diff --git a/features/joinroom/impl/src/main/res/values-et/translations.xml b/features/joinroom/impl/src/main/res/values-et/translations.xml index b3c51abbf1..572e499d99 100644 --- a/features/joinroom/impl/src/main/res/values-et/translations.xml +++ b/features/joinroom/impl/src/main/res/values-et/translations.xml @@ -11,8 +11,8 @@ "Kas sa oled kindel, et soovid keelduda kutsest sellesse jututuppa? Samaga kaob kasutajal %1$s võimalus sinuga suhelda ja saata sulle jututubade kutseid." "Keeldu kutsest ja blokeeri" "Keeldu ja blokeeri" - "Jututoaga liitumine ei õnnestunud." - "Ligipääs siia jututuppa on võimalik vaid kutse alusel või kehtivad siin kogukonnakohased piirangud." + "Jututoaga liitumine ei õnnestunud" + "Ligipääs siia on võimalik vaid kutse alusel või siin kehtivad ligipääsupiirangud." "Unusta see jututuba" "Selle jututoaga liitumiseks vajad sa kutset" "Kutsuja" diff --git a/features/joinroom/impl/src/main/res/values-fa/translations.xml b/features/joinroom/impl/src/main/res/values-fa/translations.xml index 77468f41ef..3f9fc99877 100644 --- a/features/joinroom/impl/src/main/res/values-fa/translations.xml +++ b/features/joinroom/impl/src/main/res/values-fa/translations.xml @@ -9,10 +9,12 @@ "بله. رد و انسداد" "رد دعوت و انسداد" "رد و انسداد" - "پیوستن به اتاق شکست خورد." + "پیوستن شکست خورد" "فراموشی این اتاق" "برای پیوستن به این اتاق نیاز به دعوت دارید" + "دعوت شده از سوی" "پیوستن" + "برای پیوستن به فضا باید دعوت شده باشید." "در زدن برای پیوستن" "پیام (اختیاری)" "درخواست پیوستن فرستاده شد" diff --git a/features/joinroom/impl/src/main/res/values-fi/translations.xml b/features/joinroom/impl/src/main/res/values-fi/translations.xml index 9a8a7e2a16..38ba043955 100644 --- a/features/joinroom/impl/src/main/res/values-fi/translations.xml +++ b/features/joinroom/impl/src/main/res/values-fi/translations.xml @@ -15,6 +15,7 @@ "Sinun on joko saatava kutsu liittyäksesi tai pääsyyn voi olla rajoituksia." "Unohda" "Tarvitset kutsun liittyäksesi" + "Kutsuja" "Liity" "Saatat tarvita kutsun tai olla tilan jäsen, jotta voit liittyä." "Lähetä liittymispyyntö" diff --git a/features/joinroom/impl/src/main/res/values-hr/translations.xml b/features/joinroom/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..1a8489255a --- /dev/null +++ b/features/joinroom/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,34 @@ + + + "Korisnik %1$s vam je zabranio pristup." + "Zabranjen vam je pristup" + "Razlog: %1$s." + "Otkaži zahtjev" + "Da, otkaži" + "Jeste li sigurni da želite otkazati svoj zahtjev za pridruživanje ovoj sobi?" + "Otkaži zahtjev za pridruživanje" + "Da, odbij i blokiraj" + "Jeste li sigurni da želite odbiti poziv za pridruživanje ovoj sobi? Time ćete također spriječiti da %1$s kontaktira s vama ili vas pozove u sobe." + "Odbij poziv i blokiraj" + "Odbij i blokiraj" + "Pridruživanje nije uspjelo" + "Morate biti pozvani da se pridružite ili možda postoje ograničenja pristupa." + "Zaboravi" + "Trebate imati pozivnicu kako biste se pridružili" + "Pozvao/la" + "Pridruži se" + "Možda ćete morati biti pozvani ili biti član prostora kako biste se pridružili." + "Pošalji zahtjev za pridruživanje" + "Dopuštenih znakova %1$d od %2$d" + "Poruka (nije obavezna)" + "Primit ćete pozivnicu za pridruživanje sobi ako vaš zahtjev bude prihvaćen." + "Zahtjev za pridruživanje je poslan" + "Nismo mogli prikazati pregled sobe. To bi moglo biti zbog problema s mrežom ili poslužiteljem." + "Nismo mogli prikazati pregled ove sobe" + "%1$s još ne podržava prostore. Prostorima možete pristupiti na internetu." + "Prostori još nisu podržani" + "Kliknite donji gumb i administrator sobe dobit će obavijest. Moći ćete se pridružiti razgovoru nakon što dobijete odobrenje." + "Morate biti član ove sobe da biste vidjeli povijest poruka." + "Želite li se pridružiti ovoj sobi?" + "Pregled nije dostupan" + diff --git a/features/joinroom/impl/src/main/res/values-hu/translations.xml b/features/joinroom/impl/src/main/res/values-hu/translations.xml index 8fafb9aa7a..facb3edbf2 100644 --- a/features/joinroom/impl/src/main/res/values-hu/translations.xml +++ b/features/joinroom/impl/src/main/res/values-hu/translations.xml @@ -1,7 +1,7 @@ "%1$s kitiltotta a szobából." - "Kitiltották ebből a szobából" + "Kitiltották" "Ok: %1$s." "Kérés visszavonása" "Igen, visszavonás" @@ -11,10 +11,10 @@ "Biztos, hogy elutasítja a meghívást, hogy csatlakozzon ehhez a szobához? Ez azt is megakadályozza, hogy %1$s kapcsolatba lépjen Önnel, vagy szobákba hívja." "Meghívó elutasítása és blokkolás" "Elutasítás és letiltás" - "A szobához való csatlakozás sikertelen." - "Ebbe a szobába csak meghívóval vagy tértagsággal lehet belépni." - "Szoba elfelejtése" - "Meghívóra van szüksége ahhoz, hogy csatlakozzon ehhez a szobához" + "A csatlakozás sikertelen" + "Csatlakozáshoz meghívóra van szükség, vagy lehet, hogy korlátozva van a hozzáférés." + "Elfelejt" + "A csatlakozáshoz meghívóra van szükség." "Meghívta:" "Csatlakozás" "A csatlakozáshoz meghívásra vagy tértagságra lehet szüksége." diff --git a/features/joinroom/impl/src/main/res/values-it/translations.xml b/features/joinroom/impl/src/main/res/values-it/translations.xml index deb025db27..756d6edc40 100644 --- a/features/joinroom/impl/src/main/res/values-it/translations.xml +++ b/features/joinroom/impl/src/main/res/values-it/translations.xml @@ -1,7 +1,7 @@ - "Sei stato bandito da questa stanza da %1$s." - "Sei stato bandito da questa stanza" + "Sei stato bannato da %1$s ." + "Sei stato bannato" "Motivo: %1$s" "Cancella richiesta" "Sì, annulla" @@ -11,10 +11,11 @@ "Sei sicuro di voler rifiutare l\'invito a entrare in questa stanza? Ciò impedirà a %1$s di contattarti o invitarti nuovamente in una stanza." "Rifiuta invito e blocca" "Rifiuta e blocca" - "L\'accesso alla stanza non è riuscito." - "Questa stanza è solo su invito o potrebbero esserci delle restrizioni all\'accesso al livello dello spazio." - "Dimentica questa stanza" - "Hai bisogno di un invito per entrare in questa stanza" + "Partecipazione non riuscita" + "Devi essere invitato per partecipare o potrebbero esserci delle restrizioni di accesso." + "Dimentica" + "Per partecipare è necessario un invito" + "Invitato da" "Entra" "Potrebbe essere necessario essere invitati o essere membro di uno spazio per partecipare." "Bussa per partecipare" diff --git a/features/joinroom/impl/src/main/res/values-pl/translations.xml b/features/joinroom/impl/src/main/res/values-pl/translations.xml index dea02ea7cd..fcac55d546 100644 --- a/features/joinroom/impl/src/main/res/values-pl/translations.xml +++ b/features/joinroom/impl/src/main/res/values-pl/translations.xml @@ -1,7 +1,7 @@ - "Zostałeś zbanowany z tego pokoju przez %1$s." - "Zostałeś zbanowany z tego pokoju" + "Zostałeś zbanowany przez %1$s ." + "Zostałeś zbanowany" "Powód: %1$s." "Anuluj prośbę" "Tak, anuluj" @@ -11,10 +11,11 @@ "Czy na pewno chcesz odrzucić zaproszenie dołączenia do tego pokoju? %1$s nie będzie mógł się również z Tobą skontaktować, ani zaprosić Cię do pokoju." "Odrzuć zaproszenie i zablokuj" "Odrzuć i zablokuj" - "Nie udało się dołączyć do pokoju." - "Ten pokój wymaga zaproszenia lub jest ograniczony z poziomu przestrzeni." - "Zapomnij o tym pokoju" - "Potrzebujesz zaproszenia, aby dołączyć do tego pokoju" + "Nie udało się dołączyć do pokoju" + "Ten pokój wymaga zaproszenia lub dołączanie zostało ograniczone." + "Zapomnij" + "Aby dołączyć, potrzebujesz zaproszenia" + "Zaproszony przez" "Dołącz" "Aby dołączyć, musisz uzyskać zaproszenie lub być członkiem danej przestrzeni." "Wyślij prośbę o dołączenie" diff --git a/features/joinroom/impl/src/main/res/values-pt-rBR/translations.xml b/features/joinroom/impl/src/main/res/values-pt-rBR/translations.xml index ed553bd93a..247532946f 100644 --- a/features/joinroom/impl/src/main/res/values-pt-rBR/translations.xml +++ b/features/joinroom/impl/src/main/res/values-pt-rBR/translations.xml @@ -1,7 +1,7 @@ - "Você foi banido desta sala por %1$s." - "Você foi banido desta sala" + "Você foi banido por %1$s." + "Você foi banido" "Motivo: %1$s." "Cancelar pedido" "Sim, cancelar" @@ -11,10 +11,11 @@ "Tem certeza de que quer recusar o convite para entrar nesta sala? Isso também impedirá que %1$s entre em contato com você ou o convide para salas." "Recusar convite e bloquear" "Recusar e bloquear" - "A entrada na sala falhou." - "Esta sala é apenas para convidados ou pode haver restrições de acesso a nível do espaço." - "Esquecer esta sala" - "Você precisa de um convite para entrar nesta sala" + "Falha ao entrar" + "Você precisa ser convidado ou pode haver restrições ao acesso." + "Esquecer" + "Você precisa de um convite para entrar" + "Convidado por" "Entrar" "Talvez você precise ser convidado ou ser membro de um espaço para participar." "Enviar solicitação para entrar" diff --git a/features/joinroom/impl/src/main/res/values-pt/translations.xml b/features/joinroom/impl/src/main/res/values-pt/translations.xml index 4ed5e4219f..5a20d62b8c 100644 --- a/features/joinroom/impl/src/main/res/values-pt/translations.xml +++ b/features/joinroom/impl/src/main/res/values-pt/translations.xml @@ -1,7 +1,7 @@ - "Foste banido desta sala por %1$s." - "Foste banido desta sala." + "Foste banido(a) por %1$s." + "Foste banido(a)" "Razão: %1$s." "Cancelar pedido" "Sim, cancelar" @@ -11,10 +11,10 @@ "Tens a certeza de que queres recusar o convite para entrar nesta sala? Isto também evitará que %1$s te contacte ou te convide para salas." "Recusar convite & bloquear" "Recusar e bloquear" - "Falha ao entrar na sala." - "A entrada nesta sala ou está limitada a convites ou a alguma configuração de espaço." - "Esquecer esta sala" - "Precisas de um convite para entrares nesta sala" + "Falha ao entrar" + "A entrada pode estar limitada a convites ou pode haver uma outra limitação de acesso." + "Esquecer" + "Precisas de um convite para entrares" "Convidado por" "Entrar" "Podes ter que ser convidado ou pertenceres a um espaço para poderes entrar." diff --git a/features/joinroom/impl/src/main/res/values-ru/translations.xml b/features/joinroom/impl/src/main/res/values-ru/translations.xml index 9b50f35da4..207bc4bfde 100644 --- a/features/joinroom/impl/src/main/res/values-ru/translations.xml +++ b/features/joinroom/impl/src/main/res/values-ru/translations.xml @@ -1,7 +1,7 @@ - "Вам был запрещен доступ в комнату %1$s." - "Вам был запрещен доступ в эту комнату" + "Вы были заблокированы в комнате %1$s." + "Вы были заблокированы в комнате" "Причина: %1$s." "Отменить запрос" "Да, отменить" @@ -12,7 +12,7 @@ "Отклонить приглашение и заблокировать" "Отклонить и заблокировать" "Не удалось присоединиться к комнате." - "Доступ в эту комнату возможен только по приглашениям или может быть ограничен на уровне пространства." + "Доступ к комнате ограничен. Возможно вам нужно приглашение в комнату" "Забыть эту комнату" "Вам необходимо приглашение для того, чтобы присоединиться к этой комнате" "Приглашен" diff --git a/features/joinroom/impl/src/main/res/values-sk/translations.xml b/features/joinroom/impl/src/main/res/values-sk/translations.xml index 35cc31af32..b6af52ca83 100644 --- a/features/joinroom/impl/src/main/res/values-sk/translations.xml +++ b/features/joinroom/impl/src/main/res/values-sk/translations.xml @@ -1,7 +1,7 @@ - "Používateľ %1$s vám zakázal prístup do tejto miestnosti." - "Bol vám zakázaný vstup do tejto miestnosti" + "Používateľ %1$s vám zakázal prístup." + "Bol vám zakázaný vstup" "Dôvod: %1$s." "Zrušiť žiadosť" "Áno, zrušiť" @@ -11,10 +11,11 @@ "Ste si istí, že chcete odmietnuť pozvanie na vstup do tejto miestnosti? To tiež zabráni tomu, aby vás %1$s kontaktoval/a alebo vás pozval/a do miestností." "Odmietnuť pozvánku a zablokovať" "Odmietnuť a zablokovať" - "Pripojenie do miestnosti zlyhalo." - "Táto miestnosť je buď len pre pozvaných, alebo môžu existovať obmedzenia na prístup na úrovni priestoru." - "Zabudnúť túto miestnosť" - "Potrebujete pozvanie, aby ste sa mohli pripojiť k tejto miestnosti" + "Vstup sa nepodaril" + "Buď musíte byť pozvaní pripojiť sa, alebo môžu existovať obmedzenia prístupu." + "Zabudnúť" + "Potrebujete pozvanie, aby ste sa mohli pripojiť" + "Pozvaný/á používateľom" "Pripojiť sa" "Možno budete musieť byť pozvaní alebo byť členom priestoru, aby ste sa mohli pripojiť." "Zaklopaním sa pripojíte" diff --git a/features/joinroom/impl/src/main/res/values-uz/translations.xml b/features/joinroom/impl/src/main/res/values-uz/translations.xml index e8c50922c4..de8b10f2b1 100644 --- a/features/joinroom/impl/src/main/res/values-uz/translations.xml +++ b/features/joinroom/impl/src/main/res/values-uz/translations.xml @@ -1,15 +1,29 @@ + "Siz %1$s tomonidan ushbu xonadan ban qilingansiz." + "Siz bu xonadan chetlashtirilgansiz" + "Sababi: %1$s ." "So‘rovni bekor qilish" "Ha, bekor qiling" "Bu xonaga qo‘shilish so‘rovingizni bekor qilishni xohlayotganingizga ishonchingiz komilmi?" "Qo‘shilish so‘rovini bekor qilish" + "Ha, rad etish va bloklash" + "Ushbu xonaga qo‘shilish taklifini rad etishga ishonchingiz komilmi? Bu %1$sning siz bilan bog‘lanishiga yoki sizni xonalarga taklif qilishiga ham to‘sqinlik qiladi." + "Taklifni rad etish va bloklash" "Rad etish va bloklash" + "Xonaga qo‘shilish amalga oshmadi" + "Bu xona faqat taklif etilganlar uchun yoki bu maydonga kirish huquqi cheklangan bo‘lishi mumkin." + "Bu xonani esdan chiqarish" + "Bu xonaga kirish uchun taklifnoma kerak" "Qo\'shilish" + "Qo‘shilish uchun sizga taklif kerak yoki siz maydonga a’zo bo‘lishingiz kerak." "Qoʻshilish soʻrovini yuborish" + "Ruxsat etilgan belgilar: %1$d / %2$d" "Xabar (ixtiyoriy)" "Agar so‘rovingiz qabul qilinsa, xonaga qo‘shilish taklifini olasiz." "Qo‘shilish so‘rovi yuborildi" + "Xona ko‘rinishini namoyish eta olmadik. Bu tarmoq yoki server muammolari tufayli yuz bergan bo‘lishi mumkin." + "Biz bu xonani oldindan ko‘rishni ko‘rsata olmadik " "%1$s hali maydon xizmatini qoʻllab-quvvatlamaydi. maydonga veb-sayt orqali kirishingiz mumkin." "Maydonlar hali qoʻllab-quvvatlanmaydi" "Quyidagi tugmani bosing va xona administratoriga xabar beriladi. Ruxsat berilgandan soʻng suhbatga qoʻshilishingiz mumkin boʻladi." diff --git a/features/joinroom/impl/src/main/res/values-zh-rTW/translations.xml b/features/joinroom/impl/src/main/res/values-zh-rTW/translations.xml index aa7bbc261d..00887ad12a 100644 --- a/features/joinroom/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/joinroom/impl/src/main/res/values-zh-rTW/translations.xml @@ -1,7 +1,7 @@ - "您被 %1$s 禁止進入此聊天室。" - "您被禁止進入此聊天室" + "您被 %1$s 禁止。" + "您被禁止了" "理由:%1$s。" "取消請求" "是的,取消" @@ -11,10 +11,11 @@ "您確定要拒絕加入此聊天室的邀請嗎?這也會防止 %1$s 聯絡您或邀請您加入聊天室。" "拒絕邀請並封鎖" "拒絕並封鎖" - "加入聊天室失敗。" - "此聊天室僅有受邀者才能進入,或是在空間層級有存取限制。" - "忘記此聊天室" - "您需要獲得邀請才能加入此聊天室" + "加入失敗。" + "您必須獲得邀請才能加入,或者可能存在存取限制。" + "忘記" + "您需要獲得邀請才能加入" + "邀請者" "加入" "您可能需要被邀請成為空間的成員才能加入。" "傳送加入請求" diff --git a/features/joinroom/impl/src/main/res/values-zh/translations.xml b/features/joinroom/impl/src/main/res/values-zh/translations.xml index 52361e01aa..7bea362c81 100644 --- a/features/joinroom/impl/src/main/res/values-zh/translations.xml +++ b/features/joinroom/impl/src/main/res/values-zh/translations.xml @@ -1,7 +1,7 @@ - "你被 %1$s 从此房间封禁。" - "你已被此房间封禁" + "您已被禁止访问%1$s。" + "你已被禁止访问" "理由:%1$s。" "取消请求" "是的,取消" @@ -11,10 +11,10 @@ "您确定要拒绝加入此房间的邀请吗?这也将阻止%1$s 与您联系或邀请您加入房间。" "拒绝邀请并屏蔽" "拒绝并屏蔽" - "加入房间失败。" - "要么此房间仅限受邀者,要么可能在空间层级有加入限制。" - "忘记这个房间" - "你需要邀请才能加入这个房间" + "加入失败" + "您需要被邀请加入,否则可能会受到访问限制。" + "忘记" + "您需要邀请才能加入" "受邀于" "加入" "您可能需要受到邀请或成为某个空间的成员才能加入。" diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/DefaultJoinRoomEntryPointTest.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/DefaultJoinRoomEntryPointTest.kt index af75fd528c..3f44650c3f 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/DefaultJoinRoomEntryPointTest.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/DefaultJoinRoomEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,12 +10,10 @@ package io.element.android.features.joinroom.impl import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node import com.bumble.appyx.testing.junit4.util.MainDispatcherRule import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.JoinedRoom -import io.element.android.features.invite.api.InviteData -import io.element.android.features.invite.api.declineandblock.DeclineInviteAndBlockEntryPoint +import io.element.android.features.invite.test.declineandblock.FakeDeclineInviteAndBlockEntryPoint import io.element.android.features.joinroom.api.JoinRoomEntryPoint import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias import io.element.android.libraries.matrix.test.A_ROOM_ID @@ -40,9 +39,7 @@ class DefaultJoinRoomEntryPointTest { plugins = plugins, presenterFactory = { _, _, _, _, _ -> createJoinRoomPresenter() }, acceptDeclineInviteView = { _, _, _, _ -> lambdaError() }, - declineAndBlockEntryPoint = object : DeclineInviteAndBlockEntryPoint { - override fun createNode(parentNode: Node, buildContext: BuildContext, inviteData: InviteData) = lambdaError() - } + declineAndBlockEntryPoint = FakeDeclineInviteAndBlockEntryPoint(), ) } val inputs = JoinRoomEntryPoint.Inputs( @@ -52,7 +49,11 @@ class DefaultJoinRoomEntryPointTest { serverNames = emptyList(), trigger = JoinedRoom.Trigger.RoomDirectory, ) - val result = entryPoint.createNode(parentNode, BuildContext.root(null), inputs) + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + inputs = inputs, + ) assertThat(result).isInstanceOf(JoinRoomFlowNode::class.java) assertThat(result.plugins).contains(inputs) } diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeCancelKnockRoom.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeCancelKnockRoom.kt index e7f487fade..8badc1bc2c 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeCancelKnockRoom.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeCancelKnockRoom.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeForgetRoom.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeForgetRoom.kt index 48d3eec077..b7e0fc5b07 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeForgetRoom.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeForgetRoom.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeKnockRoom.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeKnockRoom.kt index 6ee281c853..d80312fb62 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeKnockRoom.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/FakeKnockRoom.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt index 10b5c1a712..aec50d1761 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -1193,46 +1194,8 @@ class JoinRoomPresenterTest { skipItems(1) awaitItem().also { state -> assertThat(state.contentState).isEqualTo( - ContentState.Failure(error = AN_EXCEPTION) + ContentState.UnknownRoom ) - state.eventSink(JoinRoomEvents.RetryFetchingContent) - } - skipItems(1) - awaitItem().also { state -> - assertThat(state.contentState).isEqualTo(ContentState.Loading) - } - awaitItem().also { state -> - assertThat(state.contentState).isEqualTo( - ContentState.Failure(error = AN_EXCEPTION) - ) - } - } - } - - @Test - fun `present - when room is not known RoomPreview is loaded with error - dismiss`() = runTest { - val client = FakeMatrixClient( - getNotJoinedRoomResult = { _, _ -> - Result.failure(AN_EXCEPTION) - }, - spaceService = FakeSpaceService( - spaceRoomListResult = { FakeSpaceRoomList() }, - ), - ) - val presenter = createJoinRoomPresenter( - matrixClient = client - ) - presenter.test { - skipItems(1) - awaitItem().also { state -> - assertThat(state.contentState).isEqualTo( - ContentState.Failure(error = AN_EXCEPTION) - ) - state.eventSink(JoinRoomEvents.DismissErrorAndHideContent) - } - skipItems(1) - awaitItem().also { state -> - assertThat(state.contentState).isEqualTo(ContentState.Dismissing) } } } diff --git a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt index f3487a43b6..0a3b1ca3c6 100644 --- a/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt +++ b/features/joinroom/impl/src/test/kotlin/io/element/android/features/joinroom/impl/JoinRoomViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/knockrequests/api/build.gradle.kts b/features/knockrequests/api/build.gradle.kts index 4beea72bed..25f419acff 100644 --- a/features/knockrequests/api/build.gradle.kts +++ b/features/knockrequests/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/KnockRequestPermissions.kt b/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/KnockRequestPermissions.kt new file mode 100644 index 0000000000..82bceb5be0 --- /dev/null +++ b/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/KnockRequestPermissions.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.knockrequests.api + +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions + +data class KnockRequestPermissions( + val canAccept: Boolean, + val canDecline: Boolean, + val canBan: Boolean, +) { + val hasAny = canAccept || canDecline || canBan + + companion object { + val DEFAULT = KnockRequestPermissions( + canAccept = false, + canDecline = false, + canBan = false, + ) + } +} + +fun RoomPermissions.knockRequestPermissions(): KnockRequestPermissions { + return KnockRequestPermissions( + canAccept = canOwnUserInvite(), + canDecline = canOwnUserKick(), + canBan = canOwnUserBan(), + ) +} diff --git a/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/banner/KnockRequestsBannerRenderer.kt b/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/banner/KnockRequestsBannerRenderer.kt index 9886c439e1..34061db335 100644 --- a/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/banner/KnockRequestsBannerRenderer.kt +++ b/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/banner/KnockRequestsBannerRenderer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/list/KnockRequestsListEntryPoint.kt b/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/list/KnockRequestsListEntryPoint.kt index b1b174580e..9975911266 100644 --- a/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/list/KnockRequestsListEntryPoint.kt +++ b/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/list/KnockRequestsListEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/knockrequests/impl/build.gradle.kts b/features/knockrequests/impl/build.gradle.kts index 41fadb00da..6f030479f5 100644 --- a/features/knockrequests/impl/build.gradle.kts +++ b/features/knockrequests/impl/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt index 612ddc5a6a..32e551a09d 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,12 +11,10 @@ package io.element.android.features.knockrequests.impl.banner import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.knockrequests.api.banner.KnockRequestsBannerRenderer import io.element.android.libraries.di.RoomScope @ContributesBinding(RoomScope::class) -@Inject class DefaultKnockRequestsBannerRenderer( private val presenter: KnockRequestsBannerPresenter, ) : KnockRequestsBannerRenderer { diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt index 2100e1f5ff..740982b612 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt index a1db930500..f340d597b1 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -48,11 +49,11 @@ class KnockRequestsBannerPresenter( val shouldShowBanner by remember { derivedStateOf { - permissions.canHandle && knockRequests.isNotEmpty() + permissions.hasAny && knockRequests.isNotEmpty() } } - fun handleEvents(event: KnockRequestsBannerEvents) { + fun handleEvent(event: KnockRequestsBannerEvents) { when (event) { is KnockRequestsBannerEvents.AcceptSingleRequest -> { sessionCoroutineScope.acceptSingleKnockRequest( @@ -73,7 +74,7 @@ class KnockRequestsBannerPresenter( displayAcceptError = showAcceptError.value, canAccept = permissions.canAccept, isVisible = shouldShowBanner, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt index 1912891afa..e9291b501a 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt index 3cdbeba9b2..67f1aaae8f 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt index 2ee070d41c..431bd14d10 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestFixture.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestFixture.kt index 81603d6a68..17fd0ec64a 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestFixture.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestFixture.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestPermissions.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestPermissions.kt deleted file mode 100644 index 4de596a623..0000000000 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestPermissions.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.knockrequests.impl.data - -import io.element.android.libraries.matrix.api.room.JoinedRoom -import io.element.android.libraries.matrix.api.room.powerlevels.canBan -import io.element.android.libraries.matrix.api.room.powerlevels.canInvite -import io.element.android.libraries.matrix.api.room.powerlevels.canKick -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map - -data class KnockRequestPermissions( - val canAccept: Boolean, - val canDecline: Boolean, - val canBan: Boolean, -) { - val canHandle = canAccept || canDecline || canBan -} - -fun JoinedRoom.knockRequestPermissionsFlow(): Flow { - return syncUpdateFlow.map { - val canAccept = canInvite().getOrDefault(false) - val canDecline = canKick().getOrDefault(false) - val canBan = canBan().getOrDefault(false) - KnockRequestPermissions(canAccept, canDecline, canBan) - } -} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestPresentable.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestPresentable.kt index 1e2e549db6..dc6aeb8f0d 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestPresentable.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestPresentable.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestWrapper.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestWrapper.kt index de3bcd6872..15af1b2fc5 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestWrapper.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestWrapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsException.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsException.kt index 71cab7134e..059b128482 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsException.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsException.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsModule.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsModule.kt index 8236222f07..b51b78f105 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsModule.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,10 +12,13 @@ import dev.zacsweers.metro.BindingContainer import dev.zacsweers.metro.ContributesTo import dev.zacsweers.metro.Provides import dev.zacsweers.metro.SingleIn +import io.element.android.features.knockrequests.api.KnockRequestPermissions +import io.element.android.features.knockrequests.api.knockRequestPermissions import io.element.android.libraries.di.RoomScope import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.room.JoinedRoom +import io.element.android.libraries.matrix.api.room.powerlevels.permissionsFlow @BindingContainer @ContributesTo(RoomScope::class) @@ -24,7 +28,9 @@ object KnockRequestsModule { fun knockRequestsService(room: JoinedRoom, featureFlagService: FeatureFlagService): KnockRequestsService { return KnockRequestsService( knockRequestsFlow = room.knockRequestsFlow, - permissionsFlow = room.knockRequestPermissionsFlow(), + permissionsFlow = room.permissionsFlow(KnockRequestPermissions.DEFAULT) { perms -> + perms.knockRequestPermissions() + }, isKnockFeatureEnabledFlow = featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock), coroutineScope = room.roomCoroutineScope ) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt index ccfc0b1b1f..98570e6b28 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/data/KnockRequestsService.kt @@ -1,12 +1,14 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.knockrequests.impl.data +import io.element.android.features.knockrequests.api.KnockRequestPermissions import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.room.knock.KnockRequest diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/DefaultKnockRequestsListEntryPoint.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/DefaultKnockRequestsListEntryPoint.kt index 675f3bee9e..74a8b712d3 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/DefaultKnockRequestsListEntryPoint.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/DefaultKnockRequestsListEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,12 +12,10 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultKnockRequestsListEntryPoint : KnockRequestsListEntryPoint { override fun createNode(parentNode: Node, buildContext: BuildContext): Node { return parentNode.createNode(buildContext) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt index c00ffdaa50..f1350b1669 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt index a9bec1556b..f3c8b06884 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt index ff943fcf6b..a56dc40731 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -38,7 +39,7 @@ class KnockRequestsListPresenter( val coroutineScope = rememberCoroutineScope() - fun handleEvents(event: KnockRequestsListEvents) { + fun handleEvent(event: KnockRequestsListEvents) { when (event) { KnockRequestsListEvents.AcceptAll -> { currentAction = KnockRequestsAction.AcceptAll @@ -73,7 +74,7 @@ class KnockRequestsListPresenter( currentAction = currentAction, permissions = permissions, asyncAction = asyncAction.value, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt index 1042a3646f..ae770a297d 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListState.kt @@ -1,14 +1,15 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.knockrequests.impl.list import androidx.compose.runtime.Immutable -import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions +import io.element.android.features.knockrequests.api.KnockRequestPermissions import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt index a83450e9bd..85fc0675ad 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt @@ -1,14 +1,15 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.knockrequests.impl.list import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions +import io.element.android.features.knockrequests.api.KnockRequestPermissions import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable import io.element.android.libraries.architecture.AsyncAction diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index 987125f451..1ccc0742d6 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/knockrequests/impl/src/main/res/values-hr/translations.xml b/features/knockrequests/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..7f8d39503f --- /dev/null +++ b/features/knockrequests/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,37 @@ + + + "Da, prihvati sve" + "Jeste li sigurni da želite prihvatiti sve zahtjeve za pridruživanje?" + "Prihvati sve zahtjeve" + "Prihvati sve" + "Nismo mogli prihvatiti sve zahtjeve. Želite li pokušati ponovno?" + "Prihvaćanje svih zahtjeva nije uspjelo" + "Prihvaćanje svih zahtjeva za pridruživanje" + "Nismo mogli prihvatiti ovaj zahtjev. Želite li pokušati ponovno?" + "Prihvaćanje zahtjeva nije uspjelo" + "Prihvaća se zahtjev za pridruživanje" + "Da, odbij i zabrani" + "Jeste li sigurni da želite odbiti i zabraniti korisnika %1$s? Taj korisnik neće moći ponovno zatražiti pristup ovoj sobi." + "Odbij i zabrani pristup" + "Odbijanje i zabrana pristupa" + "Da, odbij" + "Jeste li sigurni da želite odbiti zahtjev korisnika %1$s za pridruživanje ovoj sobi?" + "Odbij pristup" + "Odbij i zabrani" + "Nismo mogli odbiti ovaj zahtjev. Želite li pokušati ponovno?" + "Odbijanje zahtjeva nije uspjelo" + "Odbijanje zahtjeva za pridruživanje" + "Kada netko zatraži pridruživanje sobi, ovdje ćete moći vidjeti njihov zahtjev." + "Nema zahtjeva za pridruživanje koji su na čekanju" + "Učitavanje zahtjeva za pridruživanje…" + "Zahtjevi za pridruživanje" + + "%1$s i još %2$d želi se pridružiti ovoj sobi" + "%1$s i još %2$d želi se pridružiti ovoj sobi" + "%1$s i još %2$d želi se pridružiti ovoj sobi" + + "Prikaži sve" + "Prihvati" + "%1$s želi se pridružiti ovoj sobi" + "Prikaz" + diff --git a/features/knockrequests/impl/src/main/res/values-ru/translations.xml b/features/knockrequests/impl/src/main/res/values-ru/translations.xml index f5e91a469a..8080d273fb 100644 --- a/features/knockrequests/impl/src/main/res/values-ru/translations.xml +++ b/features/knockrequests/impl/src/main/res/values-ru/translations.xml @@ -11,7 +11,7 @@ "Не удалось принять запрос" "Принятие заявки на присоединение" "Да, отклонить и запретить" - "Вы уверен, что хочешь отклонить и запретить %1$s? Этот пользователь больше не сможет запросить доступ к этой комнате." + "Вы уверены, что хотите отклонить и запретить %1$s? Этот пользователь больше не сможет запросить доступ к этой комнате." "Отклонить и запретить доступ" "Отклонение и запрет доступа" "Да, отклонить" diff --git a/features/knockrequests/impl/src/main/res/values-uz/translations.xml b/features/knockrequests/impl/src/main/res/values-uz/translations.xml index 8cad1166df..24d8fb3fac 100644 --- a/features/knockrequests/impl/src/main/res/values-uz/translations.xml +++ b/features/knockrequests/impl/src/main/res/values-uz/translations.xml @@ -1,5 +1,36 @@ + "Ha, hammasini qabul qiling" + "Barcha qo‘shilish so‘rovlarini qabul qilishga ishonchingiz komilmi?" + "Barcha so‘rovlarni qabul qilish" + "Hammasini qabul qiling" + "Biz barcha so‘rovlarni qabul qila olmadik. Qayta urinib koʻrmoqchimisiz?" + "Barcha so‘rovlar qabul qilinmadi" + "Qo‘shilish so‘rovi qabul qilinmoqda" + "Biz bu so‘rovni qabul qila olmadik. Yana bir bor urinib ko‘rishni xohlaysizmi?" + "So‘rovni qabul qilib bo‘lmadi" + "Qo‘shilish so‘rovi qabul qilinmoqda" + "Ha, rad eting va taqiqlang" + "Siz %1$sʼni rad etib, taqiqlashni xohlayotganingizga ishonchingiz komilmi? Bu foydalanuvchi ushbu xonaga qayta kirish uchun ruxsat so‘ray olmaydi." + "Rad etish va kirishni taqiqlash" + "Kirishni rad etish va taqiqlash" + "Ha, rad etish" + "%1$sning bu xonaga qo‘shilish so‘rovini rad etasizmi?" + "Kirishni rad etish" + "Rad etish va taqiqlash" + "Biz bu iltimosni rad etolmasdik. Yana bir bor urinib ko‘rishni xohlaysizmi?" + "So‘rovni rad etib bo‘lmadi" + "Qo‘shilish so‘rovi rad etilayapti" + "Kimdir xonaga qo‘shilishni so‘raganda, uning iltimosini shu yerda ko‘rishingiz mumkin." + "Qo‘shilish so‘rovi kutilmayapti" + "Qo‘shilish uchun so‘rovlar yuklanmoqda…" + "Qo‘shilish uchun so‘rovlar" + + "%1$s + %2$d kishi bu xonaga qo‘shilmoqchi" + "%1$s + %2$d kishi bu xonaga qo‘shilmoqchi" + + "Hammasini ko\'rish" "Qabul qiling" + "%1$s bu xonaga qo‘shilmoqchi" "Ko\'rish" diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt index 252a0ceaeb..3161d3e81f 100644 --- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenterTest.kt @@ -1,14 +1,15 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.knockrequests.impl.banner import com.google.common.truth.Truth.assertThat -import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions +import io.element.android.features.knockrequests.api.KnockRequestPermissions import io.element.android.features.knockrequests.impl.data.KnockRequestsService import io.element.android.libraries.matrix.api.room.knock.KnockRequest import io.element.android.libraries.matrix.test.A_USER_ID diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt index e5dbb39447..a9fea0905e 100644 --- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/DefaultKnockRequestsListEntryPointTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/DefaultKnockRequestsListEntryPointTest.kt index b6b6d766c7..2ef357c7c7 100644 --- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/DefaultKnockRequestsListEntryPointTest.kt +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/DefaultKnockRequestsListEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt index 18269e06f3..7102b01773 100644 --- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,7 @@ package io.element.android.features.knockrequests.impl.list import com.google.common.truth.Truth.assertThat -import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions +import io.element.android.features.knockrequests.api.KnockRequestPermissions import io.element.android.features.knockrequests.impl.data.KnockRequestsService import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData diff --git a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt index 5b4b8fb789..188dcc7e56 100644 --- a/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt +++ b/features/knockrequests/impl/src/test/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/knockrequests/test/build.gradle.kts b/features/knockrequests/test/build.gradle.kts new file mode 100644 index 0000000000..dc3407da01 --- /dev/null +++ b/features/knockrequests/test/build.gradle.kts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.knockrequests.test" +} + +dependencies { + implementation(projects.features.knockrequests.api) + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) + implementation(projects.tests.testutils) +} diff --git a/features/knockrequests/test/src/main/kotlin/io/element/android/features/knockrequests/test/FakeKnockRequestsListEntryPoint.kt b/features/knockrequests/test/src/main/kotlin/io/element/android/features/knockrequests/test/FakeKnockRequestsListEntryPoint.kt new file mode 100644 index 0000000000..95fc36d693 --- /dev/null +++ b/features/knockrequests/test/src/main/kotlin/io/element/android/features/knockrequests/test/FakeKnockRequestsListEntryPoint.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.knockrequests.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeKnockRequestsListEntryPoint : KnockRequestsListEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + ): Node = lambdaError() +} diff --git a/features/leaveroom/api/build.gradle.kts b/features/leaveroom/api/build.gradle.kts index 48d78b6e7e..b548081653 100644 --- a/features/leaveroom/api/build.gradle.kts +++ b/features/leaveroom/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomEvent.kt b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomEvent.kt index aaa7753aba..6d228ee887 100644 --- a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomEvent.kt +++ b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomEvent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomRenderer.kt b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomRenderer.kt index 80252a83a6..852193d874 100644 --- a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomRenderer.kt +++ b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomRenderer.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomState.kt b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomState.kt index 28cee237a1..92a5916858 100644 --- a/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomState.kt +++ b/features/leaveroom/api/src/main/kotlin/io/element/android/features/leaveroom/api/LeaveRoomState.kt @@ -1,12 +1,16 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.leaveroom.api +import androidx.compose.runtime.Immutable + +@Immutable interface LeaveRoomState { val eventSink: (LeaveRoomEvent) -> Unit } diff --git a/features/leaveroom/api/src/main/res/values-fa/translations.xml b/features/leaveroom/api/src/main/res/values-fa/translations.xml index 72a275d85c..167070891f 100644 --- a/features/leaveroom/api/src/main/res/values-fa/translations.xml +++ b/features/leaveroom/api/src/main/res/values-fa/translations.xml @@ -2,5 +2,7 @@ "مطمئنید که می‌خواهید این اتاق را ترک کنید؟ تنها فرد این‌جا هستید. در صورت ترک، هیچ‌کسی از جمله خودتان در آینده نخواهد توانست به آن بپیوندد." "مطمئنید که می‌خواهید این اتاق را ترک کنید؟ این اتاق عمومی نبوده قادر نخواهید بود بدون دعوت دوباره بپیوندید." + "گزینش مالکان" + "انتقال مالکیت" "مطمئنید که می‌خواهید این اتاق را ترک کنید؟" diff --git a/features/leaveroom/api/src/main/res/values-hr/translations.xml b/features/leaveroom/api/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..4eb9e3405c --- /dev/null +++ b/features/leaveroom/api/src/main/res/values-hr/translations.xml @@ -0,0 +1,10 @@ + + + "Jeste li sigurni da želite napustiti ovaj razgovor? Ovaj razgovor nije javan i nećete se moći ponovno pridružiti bez pozivnice." + "Jeste li sigurni da želite napustiti ovu sobu? Ovdje ste jedino vi. Ako odete, nitko se ubuduće neće moći pridružiti, pa ni vi." + "Jeste li sigurni da želite napustiti ovu sobu? Ova soba nije javna i nećete joj se moći ponovno pridružiti bez pozivnice." + "Odaberi vlasnike" + "Vi ste jedini vlasnik ove sobe. Morate prenijeti vlasništvo na nekog drugog prije nego što napustite sobu." + "Prenesi vlasništvo" + "Jeste li sigurni da želite napustiti sobu?" + diff --git a/features/leaveroom/api/src/main/res/values-uz/translations.xml b/features/leaveroom/api/src/main/res/values-uz/translations.xml index 374fd347fc..e04bbf4b7f 100644 --- a/features/leaveroom/api/src/main/res/values-uz/translations.xml +++ b/features/leaveroom/api/src/main/res/values-uz/translations.xml @@ -3,5 +3,8 @@ "Bu suhbatni tark etmoqchi ekanligingizga ishonchingiz komilmi? Bu suhbat hammaga ochiq emas va siz taklifsiz qayta qo‘shila olmaysiz." "Bu xonani tark etmoqchi ekanligingizga ishonchingiz komilmi? Siz bu yerda yagona odamsiz. Agar siz tark etsangiz, kelajakda hech kim qo\'shila olmaydi, jumladan siz ham." "Bu xonani tark etmoqchi ekanligingizga ishonchingiz komilmi? Bu xona ochiq emas va siz taklifsiz qayta qo‘shila olmaysiz." + "Egalarni tanlang" + "Siz bu xonaning yagona egasisiz. Xonadan chiqishdan oldin egalikni boshqaga topshirishingiz kerak." + "Egalikni topshirish" "Xonani tark etmoqchi ekanligingizga ishonchingiz komilmi?" diff --git a/features/leaveroom/impl/build.gradle.kts b/features/leaveroom/impl/build.gradle.kts index 81aedb2025..cc07e668c1 100644 --- a/features/leaveroom/impl/build.gradle.kts +++ b/features/leaveroom/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/InternalLeaveRoomEvent.kt b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/InternalLeaveRoomEvent.kt index 80aaddc442..37ec70e065 100644 --- a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/InternalLeaveRoomEvent.kt +++ b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/InternalLeaveRoomEvent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/InternalLeaveRoomRenderer.kt b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/InternalLeaveRoomRenderer.kt index be1aa3b55a..c9335bbf08 100644 --- a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/InternalLeaveRoomRenderer.kt +++ b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/InternalLeaveRoomRenderer.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,14 +11,12 @@ package io.element.android.features.leaveroom.impl import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.leaveroom.api.LeaveRoomRenderer import io.element.android.features.leaveroom.api.LeaveRoomState import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.RoomId @ContributesBinding(SessionScope::class) -@Inject class InternalLeaveRoomRenderer : LeaveRoomRenderer { @Composable override fun Render(state: LeaveRoomState, onSelectNewOwners: (RoomId) -> Unit, modifier: Modifier) { diff --git a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/InternalLeaveRoomState.kt b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/InternalLeaveRoomState.kt index 91d5ac84c4..68607f21d7 100644 --- a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/InternalLeaveRoomState.kt +++ b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/InternalLeaveRoomState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/InternalLeaveRoomStateProvider.kt b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/InternalLeaveRoomStateProvider.kt index 9437752c89..769015f301 100644 --- a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/InternalLeaveRoomStateProvider.kt +++ b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/InternalLeaveRoomStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenter.kt b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenter.kt index 50242ab7ea..6455b45659 100644 --- a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenter.kt +++ b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -95,10 +96,7 @@ class LeaveRoomPresenter( } else { val hasPrivilegedCreatorRole = roomInfoFlow.value.privilegedCreatorRole if (!hasPrivilegedCreatorRole) return false - - val creators = usersWithRole(RoomMember.Role.Owner(isCreator = true)).first() - val superAdmins = usersWithRole(RoomMember.Role.Owner(isCreator = false)).first() - val owners = creators + superAdmins + val owners = usersWithRole { role -> role is RoomMember.Role.Owner }.first() return owners.size == 1 && owners.first().userId == sessionId } } diff --git a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomView.kt b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomView.kt index 87e4537b1a..131801b014 100644 --- a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomView.kt +++ b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/di/LeaveRoomModule.kt b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/di/LeaveRoomModule.kt index b13d7d3078..db5b1416ee 100644 --- a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/di/LeaveRoomModule.kt +++ b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/di/LeaveRoomModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveBaseRoomPresenterTest.kt b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveBaseRoomPresenterTest.kt index 9e8c4113ae..59d2c1ce23 100644 --- a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveBaseRoomPresenterTest.kt +++ b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveBaseRoomPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/licenses/api/build.gradle.kts b/features/licenses/api/build.gradle.kts index 0774deb56b..fcbf81f210 100644 --- a/features/licenses/api/build.gradle.kts +++ b/features/licenses/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/features/licenses/api/src/main/kotlin/io/element/android/features/licenses/api/OpenSourceLicensesEntryPoint.kt b/features/licenses/api/src/main/kotlin/io/element/android/features/licenses/api/OpenSourceLicensesEntryPoint.kt index a2dbdde60d..835af35ca6 100644 --- a/features/licenses/api/src/main/kotlin/io/element/android/features/licenses/api/OpenSourceLicensesEntryPoint.kt +++ b/features/licenses/api/src/main/kotlin/io/element/android/features/licenses/api/OpenSourceLicensesEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/licenses/impl/build.gradle.kts b/features/licenses/impl/build.gradle.kts index 59ad326b6f..52f21ac0bd 100644 --- a/features/licenses/impl/build.gradle.kts +++ b/features/licenses/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/DefaultOpenSourcesLicensesEntryPoint.kt b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/DefaultOpenSourcesLicensesEntryPoint.kt index 8ffbc05ed3..671510e6bb 100644 --- a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/DefaultOpenSourcesLicensesEntryPoint.kt +++ b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/DefaultOpenSourcesLicensesEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,12 +12,10 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.licenses.api.OpenSourceLicensesEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultOpenSourcesLicensesEntryPoint : OpenSourceLicensesEntryPoint { override fun createNode(parentNode: Node, buildContext: BuildContext): Node { return parentNode.createNode(buildContext) diff --git a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/DependenciesFlowNode.kt b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/DependenciesFlowNode.kt index 30e094e38e..34e734a64f 100644 --- a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/DependenciesFlowNode.kt +++ b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/DependenciesFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -52,7 +53,7 @@ class DependenciesFlowNode( return when (navTarget) { is NavTarget.LicensesList -> { val callback = object : DependencyLicensesListNode.Callback { - override fun onOpenLicense(license: DependencyLicenseItem) { + override fun navigateToLicense(license: DependencyLicenseItem) { backstack.push(NavTarget.LicenseDetails(license)) } } diff --git a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/LicensesProvider.kt b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/LicensesProvider.kt index c5c0fa84f5..7ae6fa740a 100644 --- a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/LicensesProvider.kt +++ b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/LicensesProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.features.licenses.impl import android.content.Context import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.licenses.impl.model.DependencyLicenseItem import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.annotations.ApplicationContext @@ -24,7 +24,6 @@ interface LicensesProvider { } @ContributesBinding(AppScope::class) -@Inject class AssetLicensesProvider( @ApplicationContext private val context: Context, private val dispatchers: CoroutineDispatchers, diff --git a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/details/DependenciesDetailsNode.kt b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/details/DependenciesDetailsNode.kt index 4f55f8dfa8..83e7ae231f 100644 --- a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/details/DependenciesDetailsNode.kt +++ b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/details/DependenciesDetailsNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/details/DependenciesDetailsView.kt b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/details/DependenciesDetailsView.kt index 063579414f..f463a6b8d1 100644 --- a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/details/DependenciesDetailsView.kt +++ b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/details/DependenciesDetailsView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListEvent.kt b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListEvent.kt index d1c8a88717..bf53b05d18 100644 --- a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListEvent.kt +++ b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListEvent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListNode.kt b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListNode.kt index 7280c2ad41..424dad415e 100644 --- a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListNode.kt +++ b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,12 +13,12 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.licenses.impl.model.DependencyLicenseItem +import io.element.android.libraries.architecture.callback @ContributesNode(AppScope::class) @AssistedInject @@ -30,13 +31,10 @@ class DependencyLicensesListNode( plugins = plugins ) { interface Callback : Plugin { - fun onOpenLicense(license: DependencyLicenseItem) + fun navigateToLicense(license: DependencyLicenseItem) } - private fun onOpenLicense(license: DependencyLicenseItem) { - plugins() - .forEach { it.onOpenLicense(license) } - } + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { @@ -44,7 +42,7 @@ class DependencyLicensesListNode( DependencyLicensesListView( state = state, onBackClick = ::navigateUp, - onOpenLicense = ::onOpenLicense, + onOpenLicense = callback::navigateToLicense, ) } } diff --git a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListPresenter.kt b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListPresenter.kt index 0af5d1cc47..fe6973958e 100644 --- a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListPresenter.kt +++ b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -56,10 +57,10 @@ class DependencyLicensesListPresenter( } } - fun handleEvent(dependencyLicensesListEvent: DependencyLicensesListEvent) { - when (dependencyLicensesListEvent) { + fun handleEvent(event: DependencyLicensesListEvent) { + when (event) { is DependencyLicensesListEvent.SetFilter -> { - filter = dependencyLicensesListEvent.filter + filter = event.filter } } } diff --git a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListState.kt b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListState.kt index 7e9be25b5a..d3aeedeb57 100644 --- a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListState.kt +++ b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListStateProvider.kt b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListStateProvider.kt index 2bd6a5a88e..6a74cc01eb 100644 --- a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListStateProvider.kt +++ b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListView.kt b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListView.kt index 3419665b65..f60c28ef2b 100644 --- a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListView.kt +++ b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/model/DependencyLicenseItem.kt b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/model/DependencyLicenseItem.kt index 6599768acc..d26d062d57 100644 --- a/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/model/DependencyLicenseItem.kt +++ b/features/licenses/impl/src/main/kotlin/io/element/android/features/licenses/impl/model/DependencyLicenseItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/licenses/impl/src/test/kotlin/io/element/android/features/licenses/impl/DefaultOpenSourcesLicensesEntryPointTest.kt b/features/licenses/impl/src/test/kotlin/io/element/android/features/licenses/impl/DefaultOpenSourcesLicensesEntryPointTest.kt index 3209e28fe6..4b6e3d98ef 100644 --- a/features/licenses/impl/src/test/kotlin/io/element/android/features/licenses/impl/DefaultOpenSourcesLicensesEntryPointTest.kt +++ b/features/licenses/impl/src/test/kotlin/io/element/android/features/licenses/impl/DefaultOpenSourcesLicensesEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/licenses/impl/src/test/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListPresenterTest.kt b/features/licenses/impl/src/test/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListPresenterTest.kt index d52300b546..abc827d4e2 100644 --- a/features/licenses/impl/src/test/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListPresenterTest.kt +++ b/features/licenses/impl/src/test/kotlin/io/element/android/features/licenses/impl/list/DependencyLicensesListPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/licenses/impl/src/test/kotlin/io/element/android/features/licenses/impl/list/FakeLicensesProvider.kt b/features/licenses/impl/src/test/kotlin/io/element/android/features/licenses/impl/list/FakeLicensesProvider.kt index 6fba7ba651..ebbad0e16b 100644 --- a/features/licenses/impl/src/test/kotlin/io/element/android/features/licenses/impl/list/FakeLicensesProvider.kt +++ b/features/licenses/impl/src/test/kotlin/io/element/android/features/licenses/impl/list/FakeLicensesProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/licenses/test/build.gradle.kts b/features/licenses/test/build.gradle.kts new file mode 100644 index 0000000000..d9a2870aee --- /dev/null +++ b/features/licenses/test/build.gradle.kts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.licenses.test" +} + +dependencies { + implementation(projects.features.licenses.api) + implementation(projects.libraries.architecture) + implementation(projects.tests.testutils) +} diff --git a/features/licenses/test/src/main/kotlin/io/element/android/features/licenses/test/FakeOpenSourceLicensesEntryPoint.kt b/features/licenses/test/src/main/kotlin/io/element/android/features/licenses/test/FakeOpenSourceLicensesEntryPoint.kt new file mode 100644 index 0000000000..366aacbe25 --- /dev/null +++ b/features/licenses/test/src/main/kotlin/io/element/android/features/licenses/test/FakeOpenSourceLicensesEntryPoint.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.licenses.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.licenses.api.OpenSourceLicensesEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeOpenSourceLicensesEntryPoint : OpenSourceLicensesEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + ): Node { + lambdaError() + } +} diff --git a/features/linknewdevice/api/build.gradle.kts b/features/linknewdevice/api/build.gradle.kts new file mode 100644 index 0000000000..7d368f0a63 --- /dev/null +++ b/features/linknewdevice/api/build.gradle.kts @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.linknewdevice.api" +} + +dependencies { + implementation(projects.libraries.architecture) +} diff --git a/features/linknewdevice/api/src/main/kotlin/io/element/android/features/linknewdevice/api/LinkNewDeviceEntryPoint.kt b/features/linknewdevice/api/src/main/kotlin/io/element/android/features/linknewdevice/api/LinkNewDeviceEntryPoint.kt new file mode 100644 index 0000000000..061bbeb084 --- /dev/null +++ b/features/linknewdevice/api/src/main/kotlin/io/element/android/features/linknewdevice/api/LinkNewDeviceEntryPoint.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.api + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import io.element.android.libraries.architecture.FeatureEntryPoint + +interface LinkNewDeviceEntryPoint : FeatureEntryPoint { + interface Callback : Plugin { + fun onDone() + } + + fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: Callback, + ): Node +} diff --git a/features/linknewdevice/impl/build.gradle.kts b/features/linknewdevice/impl/build.gradle.kts new file mode 100644 index 0000000000..9c1aa9e990 --- /dev/null +++ b/features/linknewdevice/impl/build.gradle.kts @@ -0,0 +1,63 @@ +import extension.setupDependencyInjection +import extension.testCommonDependencies + +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-compose-library") + id("kotlin-parcelize") + alias(libs.plugins.kotlin.serialization) +} + +android { + namespace = "io.element.android.features.linknewdevice.impl" + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } +} + +setupDependencyInjection() + +dependencies { + // TODO Cleanup + implementation(projects.appconfig) + implementation(projects.features.enterprise.api) + implementation(projects.features.rageshake.api) + implementation(projects.libraries.core) + implementation(projects.libraries.androidutils) + implementation(projects.libraries.architecture) + implementation(projects.libraries.featureflag.api) + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.designsystem) + implementation(projects.libraries.testtags) + implementation(projects.libraries.uiStrings) + implementation(projects.libraries.permissions.api) + implementation(projects.libraries.sessionStorage.api) + implementation(projects.libraries.qrcode) + implementation(projects.libraries.oidc.api) + implementation(projects.libraries.uiUtils) + implementation(projects.libraries.wellknown.api) + implementation(libs.androidx.browser) + implementation(libs.androidx.webkit) + implementation(libs.serialization.json) + api(projects.features.linknewdevice.api) + + testCommonDependencies(libs, true) + testImplementation(projects.features.linknewdevice.test) + testImplementation(projects.features.enterprise.test) + testImplementation(projects.libraries.featureflag.test) + testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.oidc.test) + testImplementation(projects.libraries.permissions.test) + testImplementation(projects.libraries.sessionStorage.test) + testImplementation(projects.libraries.wellknown.test) +} diff --git a/features/linknewdevice/impl/src/main/AndroidManifest.xml b/features/linknewdevice/impl/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..d225716fc4 --- /dev/null +++ b/features/linknewdevice/impl/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/DefaultLinkNewDeviceEntryPoint.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/DefaultLinkNewDeviceEntryPoint.kt new file mode 100644 index 0000000000..5edc3cdfcd --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/DefaultLinkNewDeviceEntryPoint.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import dev.zacsweers.metro.ContributesBinding +import io.element.android.features.linknewdevice.api.LinkNewDeviceEntryPoint +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.SessionScope + +@ContributesBinding(SessionScope::class) +class DefaultLinkNewDeviceEntryPoint : LinkNewDeviceEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: LinkNewDeviceEntryPoint.Callback, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf( + callback, + ) + ) + } +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/LinkNewDesktopHandler.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/LinkNewDesktopHandler.kt new file mode 100644 index 0000000000..a8a8ff14b6 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/LinkNewDesktopHandler.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl + +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.SingleIn +import io.element.android.libraries.core.log.logger.LoggerTag +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopHandler +import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopStep +import io.element.android.libraries.matrix.api.logs.LoggerTags +import kotlinx.coroutines.Job +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 timber.log.Timber + +private val loggerTag = LoggerTag("LinkNewDesktopHandler", LoggerTags.linkNewDevice) + +@Inject +@SingleIn(SessionScope::class) +class LinkNewDesktopHandler( + private val matrixClient: MatrixClient, +) { + private val sessionScope = matrixClient.sessionCoroutineScope + private val linkDesktopStepFlow = MutableStateFlow( + LinkDesktopStep.Uninitialized + ) + + val stepFlow: StateFlow + get() = linkDesktopStepFlow.asStateFlow() + + private var currentJob: Job? = null + private var handler: LinkDesktopHandler? = null + + fun createNewHandler() { + currentJob?.cancel() + currentJob = null + handler = matrixClient.createLinkDesktopHandler().getOrNull() + } + + fun reset() { + currentJob?.cancel() + currentJob = null + sessionScope.launch { + linkDesktopStepFlow.emit(LinkDesktopStep.Uninitialized) + } + } + + fun onScannedCode(data: ByteArray) { + currentJob?.cancel() + currentJob = null + val currentHandler = handler + if (currentHandler == null) { + Timber.tag(loggerTag.value).e("onScannedCode: Handler is not initialized. Call createNewHandler() first.") + } else { + currentJob = matrixClient.sessionCoroutineScope.launch { + currentHandler.linkDesktopStep.onEach { + linkDesktopStepFlow.emit(it) + }.launchIn(this) + currentHandler.handleScannedQrCode(data) + } + } + } +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/LinkNewDeviceFlowNode.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/LinkNewDeviceFlowNode.kt new file mode 100644 index 0000000000..23c6b6ab2d --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/LinkNewDeviceFlowNode.kt @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl + +import android.app.Activity +import android.os.Parcelable +import androidx.activity.compose.LocalActivity +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.lifecycle.subscribe +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.newRoot +import com.bumble.appyx.navmodel.backstack.operation.pop +import com.bumble.appyx.navmodel.backstack.operation.push +import com.bumble.appyx.navmodel.backstack.operation.replace +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject +import io.element.android.annotations.ContributesNode +import io.element.android.compound.theme.ElementTheme +import io.element.android.features.linknewdevice.api.LinkNewDeviceEntryPoint +import io.element.android.features.linknewdevice.impl.screens.desktop.DesktopNoticeNode +import io.element.android.features.linknewdevice.impl.screens.error.ErrorNode +import io.element.android.features.linknewdevice.impl.screens.error.ErrorScreenType +import io.element.android.features.linknewdevice.impl.screens.number.EnterNumberNode +import io.element.android.features.linknewdevice.impl.screens.qrcode.ShowQrCodeNode +import io.element.android.features.linknewdevice.impl.screens.root.LinkNewDeviceRootNode +import io.element.android.features.linknewdevice.impl.screens.scan.ScanQrCodeNode +import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab +import io.element.android.libraries.architecture.BackstackView +import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.callback +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.core.log.logger.LoggerTag +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.di.annotations.SessionCoroutineScope +import io.element.android.libraries.matrix.api.linknewdevice.ErrorType +import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopStep +import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileStep +import io.element.android.libraries.matrix.api.logs.LoggerTags +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.parcelize.Parcelize +import timber.log.Timber + +private val tag = LoggerTag("LinkNewDeviceFlowNode", LoggerTags.linkNewDevice) + +@ContributesNode(SessionScope::class) +@AssistedInject +class LinkNewDeviceFlowNode( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + @SessionCoroutineScope + private val sessionCoroutineScope: CoroutineScope, + private val linkNewMobileHandler: LinkNewMobileHandler, + private val linkNewDesktopHandler: LinkNewDesktopHandler, +) : BaseFlowNode( + backstack = BackStack( + initialElement = NavTarget.Root, + savedStateMap = buildContext.savedStateMap, + ), + buildContext = buildContext, + plugins = plugins, +) { + private val callback: LinkNewDeviceEntryPoint.Callback = callback() + private var activity: Activity? = null + private var darkTheme: Boolean = false + + override fun onBuilt() { + super.onBuilt() + var linkMobileHandlerJob: Job? = null + var linkDesktopHandlerJob: Job? = null + + lifecycle.subscribe( + onCreate = { + linkNewMobileHandler.reset() + linkNewDesktopHandler.reset() + @Suppress("AssignedValueIsNeverRead") + linkMobileHandlerJob = observeLinkNewMobileHandler() + @Suppress("AssignedValueIsNeverRead") + linkDesktopHandlerJob = observeLinkNewDesktopHandler() + }, + onDestroy = { + linkMobileHandlerJob?.cancel() + linkDesktopHandlerJob?.cancel() + } + ) + } + + sealed interface NavTarget : Parcelable { + // Will display the not supported state or the device type selection + @Parcelize + data object Root : NavTarget + + @Parcelize + data class MobileShowQrCode( + val data: String, + ) : NavTarget + + @Parcelize + data object MobileEnterNumber : NavTarget + + @Parcelize + data object DesktopNotice : NavTarget + + @Parcelize + data object DesktopScanQrCode : NavTarget + + @Parcelize + data class Error( + val errorScreenType: ErrorScreenType, + ) : NavTarget + } + + private fun observeLinkNewMobileHandler(): Job { + Timber.tag(tag.value).d("startObservingLinkNewMobileHandler") + return linkNewMobileHandler.stepFlow + .onEach { linkMobileStep -> + Timber.tag(tag.value).d("step: ${linkMobileStep::class.java.simpleName}") + when (linkMobileStep) { + LinkMobileStep.Uninitialized -> Unit + LinkMobileStep.Done -> { + callback.onDone() + } + is LinkMobileStep.Error -> { + navigateToError(linkMobileStep.errorType) + } + is LinkMobileStep.QrReady -> { + // The QrCode is ready, navigate to its display + backstack.push(NavTarget.MobileShowQrCode(linkMobileStep.data)) + } + is LinkMobileStep.QrScanned -> { + backstack.replace(NavTarget.MobileEnterNumber) + } + LinkMobileStep.Starting -> { + // This step is not received at the moment, so do nothing + } + LinkMobileStep.SyncingSecrets -> { + // LinkMobileStep.Done is not received at the moment, so consider that the flow is done here + callback.onDone() + } + is LinkMobileStep.WaitingForAuth -> { + navigateToBrowser(linkMobileStep.verificationUri) + } + } + } + .launchIn(sessionCoroutineScope) + } + + private fun observeLinkNewDesktopHandler(): Job { + Timber.tag(tag.value).d("startObservingLinkNewDesktopHandler") + return linkNewDesktopHandler.stepFlow.onEach { linkDesktopStep -> + Timber.tag(tag.value).d("step: ${linkDesktopStep::class.java.simpleName}") + when (linkDesktopStep) { + LinkDesktopStep.Done -> callback.onDone() + is LinkDesktopStep.Error -> { + navigateToError(linkDesktopStep.errorType) + } + is LinkDesktopStep.EstablishingSecureChannel -> Unit + is LinkDesktopStep.InvalidQrCode -> { + // This error will be handled by the ScanQrCodeNode + } + LinkDesktopStep.Starting -> Unit + LinkDesktopStep.SyncingSecrets -> Unit + LinkDesktopStep.Uninitialized -> Unit + is LinkDesktopStep.WaitingForAuth -> { + navigateToBrowser(linkDesktopStep.verificationUri) + } + } + } + .launchIn(sessionCoroutineScope) + } + + private fun navigateToError(errorType: ErrorType) { + // Map the error to an error screen + // TODO Update this mapping + val error = when (errorType) { + is ErrorType.DeviceIdAlreadyInUse -> ErrorScreenType.UnknownError + is ErrorType.InvalidCheckCode -> ErrorScreenType.InsecureChannelDetected + is ErrorType.MissingSecretsBackup -> ErrorScreenType.UnknownError + is ErrorType.NotFound -> ErrorScreenType.Expired + is ErrorType.UnableToCreateDevice -> ErrorScreenType.UnknownError + is ErrorType.Unknown -> ErrorScreenType.UnknownError + is ErrorType.UnsupportedProtocol -> ErrorScreenType.UnknownError + } + // It is OK to push on backstack, since when user leaves the error screen, a new root will be set + backstack.push(NavTarget.Error(error)) + } + + override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { + return when (navTarget) { + NavTarget.Root -> { + val callback = object : LinkNewDeviceRootNode.Callback { + override fun onDone() { + callback.onDone() + } + + override fun linkDesktopDevice() { + linkNewDesktopHandler.reset() + backstack.push(NavTarget.DesktopNotice) + } + } + createNode(buildContext, listOf(callback)) + } + NavTarget.DesktopNotice -> { + val callback = object : DesktopNoticeNode.Callback { + override fun navigateBack() { + backstack.pop() + } + + override fun navigateToQrCodeScanner() { + backstack.push(NavTarget.DesktopScanQrCode) + } + } + createNode(buildContext, listOf(callback)) + } + NavTarget.DesktopScanQrCode -> { + val callback = object : ScanQrCodeNode.Callback { + override fun cancel() { + backstack.pop() + } + } + createNode(buildContext, listOf(callback)) + } + NavTarget.MobileEnterNumber -> { + val callback = object : EnterNumberNode.Callback { + override fun navigateToWrongNumberError() { + backstack.push(NavTarget.Error(ErrorScreenType.Mismatch2Digits)) + } + + override fun navigateBack() { + backstack.pop() + } + } + createNode(buildContext, listOf(callback)) + } + is NavTarget.MobileShowQrCode -> { + val callback = object : ShowQrCodeNode.Callback { + override fun navigateBack() { + linkNewMobileHandler.reset() + backstack.pop() + } + } + val inputs = ShowQrCodeNode.Inputs( + data = navTarget.data, + ) + createNode(buildContext, listOf(inputs, callback)) + } + is NavTarget.Error -> { + val callback = object : ErrorNode.Callback { + override fun onRetry() { + linkNewMobileHandler.reset() + linkNewDesktopHandler.reset() + backstack.newRoot(NavTarget.Root) + } + } + createNode(buildContext, listOf(callback, navTarget.errorScreenType)) + } + } + } + + private fun navigateToBrowser(url: String) { + activity?.openUrlInChromeCustomTab(null, darkTheme, url) + } + + @Composable + override fun View(modifier: Modifier) { + activity = requireNotNull(LocalActivity.current) + darkTheme = !ElementTheme.isLightTheme + DisposableEffect(Unit) { + onDispose { + activity = null + } + } + BackstackView() + } +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/LinkNewMobileHandler.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/LinkNewMobileHandler.kt new file mode 100644 index 0000000000..157d946eaa --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/LinkNewMobileHandler.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl + +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.SingleIn +import io.element.android.libraries.core.log.logger.LoggerTag +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileHandler +import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileStep +import io.element.android.libraries.matrix.api.logs.LoggerTags +import kotlinx.coroutines.Job +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 timber.log.Timber + +private val loggerTag = LoggerTag("LinkNewMobileHandler", LoggerTags.linkNewDevice) + +@Inject +@SingleIn(SessionScope::class) +class LinkNewMobileHandler( + private val matrixClient: MatrixClient, +) { + private val sessionScope = matrixClient.sessionCoroutineScope + private var currentJob: Job? = null + private var handler: LinkMobileHandler? = null + + private val linkMobileStepFlow = MutableStateFlow( + LinkMobileStep.Uninitialized + ) + + val stepFlow: StateFlow + get() = linkMobileStepFlow.asStateFlow() + + fun createAndStartNewHandler() { + Timber.tag(loggerTag.value).d("createAndStartNewHandler()") + currentJob?.cancel() + handler = matrixClient.createLinkMobileHandler().getOrNull() + handler?.let { h -> + currentJob = sessionScope.launch { + h.linkMobileStep + .onEach { + linkMobileStepFlow.emit(it) + } + .launchIn(this) + h.start() + } + } + } + + fun reset() { + currentJob?.cancel() + currentJob = null + sessionScope.launch { + linkMobileStepFlow.emit(LinkMobileStep.Uninitialized) + } + } +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeEvent.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeEvent.kt new file mode 100644 index 0000000000..3d94da3fc6 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeEvent.kt @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.desktop + +sealed interface DesktopNoticeEvent { + data object Continue : DesktopNoticeEvent +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeNode.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeNode.kt new file mode 100644 index 0000000000..895d02731a --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeNode.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.desktop + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject +import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback +import io.element.android.libraries.di.SessionScope + +@ContributesNode(SessionScope::class) +@AssistedInject +class DesktopNoticeNode( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: DesktopNoticePresenter, +) : Node(buildContext, plugins = plugins) { + interface Callback : Plugin { + fun navigateBack() + fun navigateToQrCodeScanner() + } + + private val callback: Callback = callback() + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + DesktopNoticeView( + state = state, + modifier = modifier, + onBackClick = callback::navigateBack, + onReadyToScanClick = callback::navigateToQrCodeScanner, + ) + } +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticePresenter.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticePresenter.kt new file mode 100644 index 0000000000..3b01725fe1 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticePresenter.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.desktop + +import android.Manifest +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import dev.zacsweers.metro.Inject +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.permissions.api.PermissionsEvent +import io.element.android.libraries.permissions.api.PermissionsPresenter + +@Inject +class DesktopNoticePresenter( + permissionsPresenterFactory: PermissionsPresenter.Factory, +) : Presenter { + private val cameraPermissionPresenter: PermissionsPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA) + private var pendingPermissionRequest by mutableStateOf(false) + + @Composable + override fun present(): DesktopNoticeState { + val cameraPermissionState = cameraPermissionPresenter.present() + var canContinue by remember { mutableStateOf(false) } + LaunchedEffect(cameraPermissionState.permissionGranted) { + if (cameraPermissionState.permissionGranted && pendingPermissionRequest) { + pendingPermissionRequest = false + canContinue = true + } + } + + fun handleEvent(event: DesktopNoticeEvent) { + when (event) { + DesktopNoticeEvent.Continue -> if (cameraPermissionState.permissionGranted) { + canContinue = true + } else { + pendingPermissionRequest = true + cameraPermissionState.eventSink(PermissionsEvent.RequestPermissions) + } + } + } + + return DesktopNoticeState( + cameraPermissionState = cameraPermissionState, + canContinue = canContinue, + eventSink = ::handleEvent, + ) + } +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeState.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeState.kt new file mode 100644 index 0000000000..81991b5ab2 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeState.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.desktop + +import io.element.android.libraries.permissions.api.PermissionsState + +data class DesktopNoticeState( + val cameraPermissionState: PermissionsState, + val canContinue: Boolean, + val eventSink: (DesktopNoticeEvent) -> Unit, +) diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeStateProvider.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeStateProvider.kt new file mode 100644 index 0000000000..194bd6fafc --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeStateProvider.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.desktop + +import android.Manifest +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.permissions.api.PermissionsState +import io.element.android.libraries.permissions.api.aPermissionsState + +open class DesktopNoticeStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aDesktopNoticeState(), + aDesktopNoticeState(cameraPermissionState = aPermissionsState(showDialog = true, permission = Manifest.permission.CAMERA)), + ) +} + +fun aDesktopNoticeState( + cameraPermissionState: PermissionsState = aPermissionsState( + showDialog = false, + permission = Manifest.permission.CAMERA, + ), + canContinue: Boolean = false, + eventSink: (DesktopNoticeEvent) -> Unit = {}, +) = DesktopNoticeState( + cameraPermissionState = cameraPermissionState, + canContinue = canContinue, + eventSink = eventSink +) diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeView.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeView.kt new file mode 100644 index 0000000000..c7f0bb61e7 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeView.kt @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +@file:OptIn(ExperimentalMaterial3Api::class) + +package io.element.android.features.linknewdevice.impl.screens.desktop + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.linknewdevice.impl.R +import io.element.android.libraries.designsystem.atomic.organisms.NumberedListOrganism +import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage +import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.LocalBuildMeta +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.utils.annotatedTextWithBold +import io.element.android.libraries.permissions.api.PermissionsView +import kotlinx.collections.immutable.persistentListOf + +/** + * Desktop notice screen: + * https://www.figma.com/design/pDlJZGBsri47FNTXMnEdXB/Compound-Android-Templates?node-id=2027-23618 + */ +@Composable +fun DesktopNoticeView( + state: DesktopNoticeState, + onBackClick: () -> Unit, + onReadyToScanClick: () -> Unit, + modifier: Modifier = Modifier, +) { + val latestOnReadyToScanClick by rememberUpdatedState(onReadyToScanClick) + LaunchedEffect(state.canContinue) { + if (state.canContinue) { + latestOnReadyToScanClick() + } + } + + val appName = LocalBuildMeta.current.applicationName + FlowStepPage( + onBackClick = onBackClick, + title = stringResource(R.string.screen_link_new_device_desktop_title, appName), + iconStyle = BigIcon.Style.Default(CompoundIcons.Computer()), + modifier = modifier, + buttons = { + Button( + text = stringResource(R.string.screen_link_new_device_desktop_submit), + onClick = { state.eventSink(DesktopNoticeEvent.Continue) }, + modifier = Modifier.fillMaxWidth(), + ) + } + ) { + Column( + Modifier.fillMaxWidth() + ) { + Spacer(modifier = Modifier.height(40.dp)) + NumberedListOrganism( + modifier = Modifier.fillMaxSize(), + items = persistentListOf( + AnnotatedString(stringResource(R.string.screen_link_new_device_desktop_step1, appName)), + annotatedTextWithBold( + text = stringResource( + id = R.string.screen_link_new_device_mobile_step2, + stringResource(R.string.screen_link_new_device_mobile_step2_action), + ), + boldText = stringResource(R.string.screen_link_new_device_mobile_step2_action) + ), + AnnotatedString(stringResource(R.string.screen_link_new_device_desktop_step3)), + ) + ) + } + } + + PermissionsView( + title = stringResource(R.string.screen_qr_code_login_no_camera_permission_state_title), + content = stringResource(R.string.screen_qr_code_login_no_camera_permission_state_description, appName), + icon = { Icon(imageVector = CompoundIcons.TakePhotoSolid(), contentDescription = null) }, + state = state.cameraPermissionState, + ) +} + +@PreviewsDayNight +@Composable +internal fun DesktopNoticeViewPreview( + @PreviewParameter(DesktopNoticeStateProvider::class) state: DesktopNoticeState, +) = ElementPreview { + DesktopNoticeView( + state = state, + onBackClick = { }, + onReadyToScanClick = { }, + ) +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorNode.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorNode.kt new file mode 100644 index 0000000000..70fd3b49a4 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorNode.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.error + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject +import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback +import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.di.SessionScope + +@ContributesNode(SessionScope::class) +@AssistedInject +class ErrorNode( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, +) : Node(buildContext = buildContext, plugins = plugins) { + interface Callback : Plugin { + fun onRetry() + } + + private val callback: Callback = callback() + private val errorScreenType = inputs() + + @Composable + override fun View(modifier: Modifier) { + ErrorView( + modifier = modifier, + errorScreenType = errorScreenType, + onRetry = callback::onRetry, + ) + } +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorScreenType.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorScreenType.kt new file mode 100644 index 0000000000..b92a19ef8a --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorScreenType.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.error + +import android.os.Parcelable +import androidx.compose.runtime.Immutable +import io.element.android.libraries.architecture.NodeInputs +import kotlinx.parcelize.Parcelize + +@Immutable +sealed interface ErrorScreenType : NodeInputs, Parcelable { + @Parcelize + data object Cancelled : ErrorScreenType + + @Parcelize + data object Expired : ErrorScreenType + + @Parcelize + data object Mismatch2Digits : ErrorScreenType + + @Parcelize + data object InsecureChannelDetected : ErrorScreenType + + @Parcelize + data object Declined : ErrorScreenType + + @Parcelize + data object ProtocolNotSupported : ErrorScreenType + + @Parcelize + data object SlidingSyncNotAvailable : ErrorScreenType + + @Parcelize + data object UnknownError : ErrorScreenType +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorScreenTypeProvider.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorScreenTypeProvider.kt new file mode 100644 index 0000000000..7fd699101b --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorScreenTypeProvider.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.error + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider + +class ErrorScreenTypeProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf( + ErrorScreenType.Cancelled, + ErrorScreenType.Declined, + ErrorScreenType.Expired, + ErrorScreenType.ProtocolNotSupported, + ErrorScreenType.Mismatch2Digits, + ErrorScreenType.InsecureChannelDetected, + ErrorScreenType.SlidingSyncNotAvailable, + ErrorScreenType.UnknownError, + ) +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorView.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorView.kt new file mode 100644 index 0000000000..3a77f19f49 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorView.kt @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.error + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.features.linknewdevice.impl.R +import io.element.android.libraries.designsystem.atomic.organisms.NumberedListOrganism +import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage +import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.LocalBuildMeta +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.persistentListOf + +@Composable +fun ErrorView( + errorScreenType: ErrorScreenType, + onRetry: () -> Unit, + modifier: Modifier = Modifier, +) { + val appName = LocalBuildMeta.current.applicationName + BackHandler(onBack = onRetry) + FlowStepPage( + modifier = modifier, + iconStyle = BigIcon.Style.AlertSolid, + title = titleText(errorScreenType, appName), + subTitle = subtitleText(errorScreenType, appName), + content = { Content(errorScreenType) }, + buttons = { Buttons(onRetry) }, + ) +} + +@Composable +private fun titleText(errorScreenType: ErrorScreenType, appName: String) = when (errorScreenType) { + ErrorScreenType.Cancelled -> stringResource(R.string.screen_qr_code_login_error_cancelled_title) + ErrorScreenType.Declined -> stringResource(R.string.screen_qr_code_login_error_declined_title) + ErrorScreenType.Expired -> stringResource(R.string.screen_qr_code_login_error_expired_title) + ErrorScreenType.ProtocolNotSupported -> stringResource(R.string.screen_qr_code_login_error_linking_not_suported_title) + ErrorScreenType.InsecureChannelDetected -> stringResource(id = R.string.screen_qr_code_login_connection_note_secure_state_title) + ErrorScreenType.Mismatch2Digits -> stringResource(id = R.string.screen_link_new_device_wrong_number_title) + ErrorScreenType.SlidingSyncNotAvailable -> stringResource(id = R.string.screen_qr_code_login_error_sliding_sync_not_supported_title, appName) + is ErrorScreenType.UnknownError -> stringResource(CommonStrings.common_something_went_wrong) +} + +@Composable +private fun subtitleText(errorScreenType: ErrorScreenType, appName: String) = when (errorScreenType) { + ErrorScreenType.Cancelled -> stringResource(R.string.screen_qr_code_login_error_cancelled_subtitle) + ErrorScreenType.Declined -> stringResource(R.string.screen_qr_code_login_error_declined_subtitle) + ErrorScreenType.Expired -> stringResource(R.string.screen_qr_code_login_error_expired_subtitle) + ErrorScreenType.ProtocolNotSupported -> stringResource(R.string.screen_qr_code_login_error_linking_not_suported_subtitle, appName) + ErrorScreenType.Mismatch2Digits -> stringResource(id = R.string.screen_link_new_device_wrong_number_subtitle) + ErrorScreenType.InsecureChannelDetected -> stringResource(id = R.string.screen_qr_code_login_connection_note_secure_state_description) + ErrorScreenType.SlidingSyncNotAvailable -> stringResource(id = R.string.screen_qr_code_login_error_sliding_sync_not_supported_subtitle, appName) + is ErrorScreenType.UnknownError -> stringResource(R.string.screen_qr_code_login_unknown_error_description) +} + +@Composable +private fun ColumnScope.InsecureChannelDetectedError() { + Text( + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()), + text = stringResource(R.string.screen_qr_code_login_connection_note_secure_state_list_header), + style = ElementTheme.typography.fontBodyLgMedium, + textAlign = TextAlign.Center, + ) + NumberedListOrganism( + modifier = Modifier.fillMaxSize(), + items = persistentListOf( + AnnotatedString(stringResource(R.string.screen_qr_code_login_connection_note_secure_state_list_item_1)), + AnnotatedString(stringResource(R.string.screen_qr_code_login_connection_note_secure_state_list_item_2)), + AnnotatedString(stringResource(R.string.screen_qr_code_login_connection_note_secure_state_list_item_3)), + ) + ) +} + +@Composable +private fun Content(errorScreenType: ErrorScreenType) { + when (errorScreenType) { + ErrorScreenType.InsecureChannelDetected -> { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + InsecureChannelDetectedError() + } + } + else -> Unit + } +} + +@Composable +private fun Buttons(onRetry: () -> Unit) { + Button( + modifier = Modifier.fillMaxWidth(), + text = stringResource(CommonStrings.action_start_over), + onClick = onRetry + ) +} + +@PreviewsDayNight +@Composable +internal fun ErrorViewPreview(@PreviewParameter(ErrorScreenTypeProvider::class) errorScreenType: ErrorScreenType) { + ElementPreview { + ErrorView( + errorScreenType = errorScreenType, + onRetry = {}, + ) + } +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/Config.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/Config.kt new file mode 100644 index 0000000000..b61cc82a22 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/Config.kt @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.number + +object Config { + const val VERIFICATION_CODE_LENGTH = 2 +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberEvent.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberEvent.kt new file mode 100644 index 0000000000..267b508669 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberEvent.kt @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.number + +sealed interface EnterNumberEvent { + data class UpdateNumber(val number: String) : EnterNumberEvent + data object Continue : EnterNumberEvent +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberNode.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberNode.kt new file mode 100644 index 0000000000..08d8cd02bd --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberNode.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.number + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject +import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback +import io.element.android.libraries.di.SessionScope + +interface EnterNumberNavigator { + fun navigateToWrongNumberError() +} + +@ContributesNode(SessionScope::class) +@AssistedInject +class EnterNumberNode( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + presenterFactory: EnterNumberPresenter.Factory, +) : Node(buildContext, plugins = plugins), EnterNumberNavigator { + private val presenter = presenterFactory.create(this) + + interface Callback : Plugin { + fun navigateToWrongNumberError() + fun navigateBack() + } + + private val callback: Callback = callback() + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + EnterNumberView( + state = state, + modifier = modifier, + onBackClick = callback::navigateBack, + ) + } + + override fun navigateToWrongNumberError() { + callback.navigateToWrongNumberError() + } +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberPresenter.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberPresenter.kt new file mode 100644 index 0000000000..74b5b6b294 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberPresenter.kt @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.number + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject +import io.element.android.features.linknewdevice.impl.LinkNewMobileHandler +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.core.log.logger.LoggerTag +import io.element.android.libraries.matrix.api.linknewdevice.CheckCodeSender +import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileStep +import io.element.android.libraries.matrix.api.logs.LoggerTags +import kotlinx.coroutines.launch +import timber.log.Timber + +private val tag = LoggerTag("EnterNumberPresenter", LoggerTags.linkNewDevice) + +@AssistedInject +class EnterNumberPresenter( + @Assisted private val navigator: EnterNumberNavigator, + private val linkNewMobileHandler: LinkNewMobileHandler, +) : Presenter { + @AssistedFactory + interface Factory { + fun create(navigator: EnterNumberNavigator): EnterNumberPresenter + } + + @Composable + override fun present(): EnterNumberState { + val coroutineScope = rememberCoroutineScope() + var number by remember { mutableStateOf("") } + var sendingCode by remember>> { mutableStateOf(AsyncAction.Uninitialized) } + + // Observe the flow to react on ErrorType.InvalidCheckCode + val linkMobileStep by linkNewMobileHandler.stepFlow.collectAsState() + + var checkCodeSender: CheckCodeSender? by remember { mutableStateOf(null) } + + LaunchedEffect(linkMobileStep) { + when (val step = linkMobileStep) { + is LinkMobileStep.QrScanned -> { + checkCodeSender = step.checkCodeSender + } + else -> Unit + } + } + + fun handleEvent(event: EnterNumberEvent) { + when (event) { + is EnterNumberEvent.UpdateNumber -> { + sendingCode = AsyncAction.Uninitialized + // Keep only digits as a safety measure + number = event.number.filter { it.isDigit() } + } + EnterNumberEvent.Continue -> coroutineScope.launch { + // Get the current code sender + val sender = checkCodeSender + if (sender == null) { + Timber.tag(tag.value).e("No check code sender available") + sendingCode = AsyncAction.Failure(IllegalStateException("No check code sender available")) + } else { + sendingCode = AsyncAction.Loading + val uByte = number.toUByte() + val isValid = sender.validate(uByte) + if (isValid) { + sender.send(uByte) + .fold( + onSuccess = { + Timber.tag(tag.value).d("Code sent successfully") + // Keep loading, do not set sendingCode to AsyncAction.Success(Unit) + }, + onFailure = { + Timber.tag(tag.value).e(it, "Failed to send number code") + sendingCode = AsyncAction.Failure(it) + } + ) + } else { + // Navigate to the error state + navigator.navigateToWrongNumberError() + } + } + } + } + } + + return EnterNumberState( + number = number, + sendingCode = sendingCode, + eventSink = ::handleEvent, + ) + } +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberState.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberState.kt new file mode 100644 index 0000000000..b8f66018dd --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberState.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.number + +import io.element.android.features.linknewdevice.impl.screens.number.model.Number +import io.element.android.libraries.architecture.AsyncAction + +data class EnterNumberState( + val number: String, + val sendingCode: AsyncAction, + val eventSink: (EnterNumberEvent) -> Unit, +) { + val numberEntry = Number.createEmpty(Config.VERIFICATION_CODE_LENGTH).fillWith(number) + val isContinueButtonEnabled: Boolean + get() = numberEntry.isComplete() && !sendingCode.isLoading() +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberStateProvider.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberStateProvider.kt new file mode 100644 index 0000000000..126bfeee52 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberStateProvider.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.number + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.linknewdevice.ErrorType + +open class EnterNumberStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aEnterNumberState(), + aEnterNumberState(number = "1"), + aEnterNumberState(number = "12"), + aEnterNumberState(number = "12", sendingCode = AsyncAction.Loading), + aEnterNumberState(number = "12", sendingCode = AsyncAction.Failure(ErrorType.InvalidCheckCode("Invalid"))), + aEnterNumberState(number = "12", sendingCode = AsyncAction.Failure(Exception("Failed to send code"))), + ) +} + +fun aEnterNumberState( + number: String = "", + sendingCode: AsyncAction = AsyncAction.Uninitialized, + eventSink: (EnterNumberEvent) -> Unit = {}, +) = EnterNumberState( + number = number, + sendingCode = sendingCode, + eventSink = eventSink, +) diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberView.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberView.kt new file mode 100644 index 0000000000..92b3447615 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberView.kt @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +@file:OptIn(ExperimentalMaterial3Api::class) + +package io.element.android.features.linknewdevice.impl.screens.number + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.linknewdevice.impl.R +import io.element.android.features.linknewdevice.impl.screens.number.component.NumberTextField +import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage +import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.matrix.api.linknewdevice.ErrorType +import io.element.android.libraries.ui.strings.CommonStrings + +/** + * Form to enter number: + * https://www.figma.com/design/pDlJZGBsri47FNTXMnEdXB/Compound-Android-Templates?node-id=2076-81604 + */ +@Composable +fun EnterNumberView( + state: EnterNumberState, + onBackClick: () -> Unit, + modifier: Modifier = Modifier, +) { + FlowStepPage( + onBackClick = onBackClick, + title = stringResource(R.string.screen_link_new_device_enter_number_title), + subTitle = stringResource(R.string.screen_link_new_device_enter_number_subtitle), + iconStyle = BigIcon.Style.Default(CompoundIcons.Computer()), + modifier = modifier, + buttons = { + Button( + text = stringResource(CommonStrings.action_continue), + onClick = { state.eventSink(EnterNumberEvent.Continue) }, + enabled = state.isContinueButtonEnabled, + showProgress = state.sendingCode.isLoading(), + modifier = Modifier.fillMaxWidth(), + ) + } + ) { + Column( + Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Spacer(modifier = Modifier.height(24.dp)) + Text( + text = stringResource(R.string.screen_link_new_device_enter_number_notice), + textAlign = TextAlign.Center, + style = ElementTheme.typography.fontBodyMdRegular, + color = ElementTheme.colors.textPrimary, + ) + Spacer(modifier = Modifier.height(8.dp)) + NumberTextField( + number = state.numberEntry, + onValueChange = { state.eventSink(EnterNumberEvent.UpdateNumber(it)) }, + onDone = { + if (state.isContinueButtonEnabled) { + state.eventSink(EnterNumberEvent.Continue) + } + }, + ) + val failure = state.sendingCode.errorOrNull() + if (failure != null) { + Spacer(modifier = Modifier.height(4.dp)) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + Icon( + modifier = Modifier.size(14.dp), + imageVector = CompoundIcons.ErrorSolid(), + contentDescription = null, + tint = ElementTheme.colors.iconCriticalPrimary, + ) + val errorMessage = when (failure) { + is ErrorType.InvalidCheckCode -> stringResource(R.string.screen_link_new_device_enter_number_error_numbers_do_not_match) + else -> failure.message ?: stringResource(CommonStrings.error_unknown) + } + Text( + text = errorMessage, + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textCriticalPrimary, + ) + } + } + } + } +} + +@PreviewsDayNight +@Composable +internal fun EnterNumberViewPreview( + @PreviewParameter(EnterNumberStateProvider::class) state: EnterNumberState, +) = ElementPreview { + EnterNumberView( + state = state, + onBackClick = { }, + ) +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/component/NumberTextField.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/component/NumberTextField.kt new file mode 100644 index 0000000000..568a729ed6 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/component/NumberTextField.kt @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.number.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.features.linknewdevice.impl.screens.number.model.Digit +import io.element.android.features.linknewdevice.impl.screens.number.model.Number +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import kotlinx.coroutines.delay + +@Composable +fun NumberTextField( + number: Number, + onValueChange: (String) -> Unit, + onDone: () -> Unit, + modifier: Modifier = Modifier, +) { + val interactionSource = remember { MutableInteractionSource() } + val isFocused = LocalInspectionMode.current || interactionSource.collectIsFocusedAsState().value + BasicTextField( + modifier = modifier, + value = number.toText(), + onValueChange = { + onValueChange(it) + }, + interactionSource = interactionSource, + maxLines = 1, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Done, + ), + keyboardActions = KeyboardActions( + onDone = { + onDone() + } + ), + decorationBox = { + NumberRow( + number = number, + hasFocus = isFocused, + ) + } + ) +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +private fun NumberRow( + number: Number, + hasFocus: Boolean, +) { + FlowRow( + horizontalArrangement = Arrangement.spacedBy(12.dp, alignment = Alignment.CenterHorizontally), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + val length = number.length() + number.digits.forEachIndexed { index, digit -> + DigitView( + digit = digit, + isCurrent = index == length, + drawCursor = hasFocus, + ) + } + } +} + +@Composable +private fun DigitView( + digit: Digit, + isCurrent: Boolean, + drawCursor: Boolean, +) { + val shape = RoundedCornerShape(4.dp) + val appearanceModifier = when (digit) { + Digit.Empty -> { + val color = if (isCurrent) { + ElementTheme.colors.textPrimary + } else { + ElementTheme.colors.borderInteractiveSecondary + } + Modifier.border(1.dp, color, shape) + } + is Digit.Filled -> { + Modifier.background(ElementTheme.colors.bgActionSecondaryPressed, shape) + } + } + Box( + modifier = Modifier + .size(42.dp, 56.dp) + .then(appearanceModifier), + contentAlignment = Alignment.Center, + ) { + if (digit is Digit.Filled) { + Text( + text = digit.value.toString(), + style = ElementTheme.typography.fontHeadingLgBold, + color = ElementTheme.colors.textPrimary, + ) + } else if (drawCursor && isCurrent) { + // Draw a blinking cursor + BlinkingCursor() + } + } +} + +@Composable +private fun BlinkingCursor() { + var isCursorVisible by remember { mutableStateOf(true) } + LaunchedEffect(isCursorVisible) { + delay(500) + // Toggle cursor visibility + isCursorVisible = !isCursorVisible + } + if (isCursorVisible) { + Spacer( + modifier = Modifier + .size(2.dp, 24.dp) + .offset(x = (-5).dp) + .background(ElementTheme.colors.textPrimary, RoundedCornerShape(1.dp)) + ) + } +} + +@PreviewsDayNight +@Composable +internal fun NumberTextFieldPreview() { + ElementPreview { + val number = Number.createEmpty(4).fillWith("12") + NumberTextField( + number = number, + onValueChange = {}, + onDone = {}, + ) + } +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/model/Digit.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/model/Digit.kt new file mode 100644 index 0000000000..b8565ea6ca --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/model/Digit.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.number.model + +import androidx.compose.runtime.Immutable + +@Immutable +sealed interface Digit { + data object Empty : Digit + data class Filled(val value: Char) : Digit + + fun toText(): String { + return when (this) { + is Empty -> "" + is Filled -> value.toString() + } + } +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/model/Number.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/model/Number.kt new file mode 100644 index 0000000000..be60f27f76 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/number/model/Number.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.number.model + +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList + +data class Number( + val digits: ImmutableList, +) { + companion object { + fun createEmpty(size: Int): Number { + val digits = List(size) { Digit.Empty } + return Number( + digits = digits.toImmutableList() + ) + } + } + + val size = digits.size + + /** + * Fill the first digits with the given text. + * Can't be more than the size of the NumberEntry + * Keep the Empty digits at the end + * @return the new NumberEntry + */ + fun fillWith(text: String): Number { + val newDigits = MutableList(size) { Digit.Empty } + text.forEachIndexed { index, char -> + if (index < size && char.isDigit()) { + newDigits[index] = Digit.Filled(char) + } + } + return copy(digits = newDigits.toImmutableList()) + } + + fun length(): Int { + return digits.count { it is Digit.Filled } + } + + fun toText(): String { + return digits.joinToString("") { + it.toText() + } + } + + fun isComplete(): Boolean { + return digits.all { it is Digit.Filled } + } +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/qrcode/ShowQrCodeNode.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/qrcode/ShowQrCodeNode.kt new file mode 100644 index 0000000000..a884c3e97f --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/qrcode/ShowQrCodeNode.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.qrcode + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject +import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.callback +import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.di.SessionScope + +@ContributesNode(SessionScope::class) +@AssistedInject +class ShowQrCodeNode( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, +) : Node(buildContext, plugins = plugins) { + class Inputs( + val data: String, + ) : NodeInputs + + interface Callback : Plugin { + fun navigateBack() + } + + private val inputs: Inputs = inputs() + private val callback: Callback = callback() + + @Composable + override fun View(modifier: Modifier) { + ShowQrCodeView( + data = inputs.data, + modifier = modifier, + onBackClick = callback::navigateBack, + ) + } +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/qrcode/ShowQrCodeView.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/qrcode/ShowQrCodeView.kt new file mode 100644 index 0000000000..501415f621 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/qrcode/ShowQrCodeView.kt @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +@file:OptIn(ExperimentalMaterial3Api::class) + +package io.element.android.features.linknewdevice.impl.screens.qrcode + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.unit.dp +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.linknewdevice.impl.R +import io.element.android.libraries.designsystem.atomic.organisms.NumberedListOrganism +import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage +import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.LocalBuildMeta +import io.element.android.libraries.designsystem.utils.annotatedTextWithBold +import io.element.android.libraries.qrcode.QrCodeImage +import kotlinx.collections.immutable.persistentListOf + +/** + * QrCode display screen: + * https://www.figma.com/design/pDlJZGBsri47FNTXMnEdXB/Compound-Android-Templates?node-id=2027-23617 + */ +@Composable +fun ShowQrCodeView( + data: String, + onBackClick: () -> Unit, + modifier: Modifier = Modifier, +) { + val appName = LocalBuildMeta.current.applicationName + FlowStepPage( + onBackClick = onBackClick, + title = stringResource(R.string.screen_link_new_device_mobile_title, appName), + iconStyle = BigIcon.Style.Default(CompoundIcons.TakePhotoSolid()), + modifier = modifier, + ) { + Column( + Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + QrCodeImage( + data = data, + modifier = Modifier + .size(220.dp) + ) + Spacer(modifier = Modifier.height(32.dp)) + NumberedListOrganism( + modifier = Modifier.fillMaxSize(), + items = persistentListOf( + AnnotatedString(stringResource(R.string.screen_link_new_device_mobile_step1, appName)), + annotatedTextWithBold( + text = stringResource( + id = R.string.screen_link_new_device_mobile_step2, + stringResource(R.string.screen_link_new_device_mobile_step2_action), + ), + boldText = stringResource(R.string.screen_link_new_device_mobile_step2_action) + ), + AnnotatedString(stringResource(R.string.screen_link_new_device_mobile_step3)), + ) + ) + } + } +} + +@PreviewsDayNight +@Composable +internal fun ShowQrCodeViewPreview() = ElementPreview { + ShowQrCodeView( + data = "DATA", + onBackClick = { }, + ) +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootEvent.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootEvent.kt new file mode 100644 index 0000000000..8ce6af90b0 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootEvent.kt @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.root + +sealed interface LinkNewDeviceRootEvent { + data object LinkMobileDevice : LinkNewDeviceRootEvent + data object CloseDialog : LinkNewDeviceRootEvent +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootNode.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootNode.kt new file mode 100644 index 0000000000..f43ffa64df --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootNode.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.root + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject +import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback +import io.element.android.libraries.di.SessionScope + +@ContributesNode(SessionScope::class) +@AssistedInject +class LinkNewDeviceRootNode( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: LinkNewDeviceRootPresenter, +) : Node(buildContext, plugins = plugins) { + interface Callback : Plugin { + fun onDone() + fun linkDesktopDevice() + } + + private val callback: Callback = callback() + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + LinkNewDeviceRootView( + state = state, + modifier = modifier, + onBackClick = callback::onDone, + onLinkDesktopDeviceClick = callback::linkDesktopDevice, + ) + } +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootPresenter.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootPresenter.kt new file mode 100644 index 0000000000..a17ed88fd3 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootPresenter.kt @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.root + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import dev.zacsweers.metro.Inject +import io.element.android.features.linknewdevice.impl.LinkNewMobileHandler +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileStep +import kotlinx.coroutines.launch + +@Inject +class LinkNewDeviceRootPresenter( + private val matrixClient: MatrixClient, + private val linkNewMobileHandler: LinkNewMobileHandler, +) : Presenter { + @Composable + override fun present(): LinkNewDeviceRootState { + val coroutineScope = rememberCoroutineScope() + var isSupported by remember { mutableStateOf>(AsyncData.Uninitialized) } + var qrCodeData by remember { mutableStateOf>(AsyncData.Uninitialized) } + + LaunchedEffect(Unit) { + matrixClient.canLinkNewDevice().fold( + onSuccess = { supported -> + isSupported = AsyncData.Success(supported) + }, + onFailure = { + isSupported = AsyncData.Failure(it) + } + ) + } + + val step by linkNewMobileHandler.stepFlow.collectAsState() + + LaunchedEffect(step) { + when (val finalStep = step) { + is LinkMobileStep.Uninitialized -> { + qrCodeData = AsyncData.Uninitialized + } + is LinkMobileStep.QrReady -> { + qrCodeData = AsyncData.Success(Unit) + } + is LinkMobileStep.Error -> { + qrCodeData = AsyncData.Failure(finalStep.errorType) + } + else -> Unit + } + } + + fun handleEvent(event: LinkNewDeviceRootEvent) { + when (event) { + LinkNewDeviceRootEvent.LinkMobileDevice -> coroutineScope.launch { + qrCodeData = AsyncData.Loading() + // Wait for the QrCode to be ready + linkNewMobileHandler.reset() + linkNewMobileHandler.createAndStartNewHandler() + } + LinkNewDeviceRootEvent.CloseDialog -> coroutineScope.launch { + linkNewMobileHandler.reset() + } + } + } + + return LinkNewDeviceRootState( + isSupported = isSupported, + qrCodeData = qrCodeData, + eventSink = ::handleEvent, + ) + } +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootState.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootState.kt new file mode 100644 index 0000000000..6cf6694b2a --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootState.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.root + +import io.element.android.libraries.architecture.AsyncData + +data class LinkNewDeviceRootState( + val isSupported: AsyncData, + val qrCodeData: AsyncData, + val eventSink: (LinkNewDeviceRootEvent) -> Unit, +) diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootStateProvider.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootStateProvider.kt new file mode 100644 index 0000000000..f1bb7ad455 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootStateProvider.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.root + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.linknewdevice.ErrorType + +open class LinkNewDeviceRootStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aLinkNewDeviceRootState(), + aLinkNewDeviceRootState(isSupported = AsyncData.Success(true)), + aLinkNewDeviceRootState(isSupported = AsyncData.Success(false)), + aLinkNewDeviceRootState(isSupported = AsyncData.Failure(Exception("Should not happen"))), + aLinkNewDeviceRootState( + isSupported = AsyncData.Success(true), + qrCodeData = AsyncData.Loading(), + ), + aLinkNewDeviceRootState( + isSupported = AsyncData.Success(true), + qrCodeData = AsyncData.Failure(ErrorType.NotFound("The rendezvous session was not found and might have expired")), + ), + ) +} + +fun aLinkNewDeviceRootState( + isSupported: AsyncData = AsyncData.Uninitialized, + qrCodeData: AsyncData = AsyncData.Uninitialized, + eventSink: (LinkNewDeviceRootEvent) -> Unit = { }, +) = LinkNewDeviceRootState( + isSupported = isSupported, + qrCodeData = qrCodeData, + eventSink = eventSink, +) diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootView.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootView.kt new file mode 100644 index 0000000000..d9249d3e89 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootView.kt @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +@file:OptIn(ExperimentalMaterial3Api::class) + +package io.element.android.features.linknewdevice.impl.screens.root + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.linknewdevice.impl.R +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.designsystem.atomic.atoms.LoadingButtonAtom +import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage +import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.IconSource +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.ui.strings.CommonStrings + +/** + * Device selection screen: + * https://www.figma.com/design/pDlJZGBsri47FNTXMnEdXB/Compound-Android-Templates?node-id=2027-23616 + * Not supported screen: + * https://www.figma.com/design/pDlJZGBsri47FNTXMnEdXB/Compound-Android-Templates?node-id=2186-70004 + */ +@Composable +fun LinkNewDeviceRootView( + state: LinkNewDeviceRootState, + onBackClick: () -> Unit, + onLinkDesktopDeviceClick: () -> Unit, + modifier: Modifier = Modifier, +) { + val (title, subtitle, iconStyle) = if (state.isSupported.dataOrNull() == false) { + Triple( + stringResource(R.string.screen_link_new_device_error_not_supported_title), + stringResource(R.string.screen_link_new_device_error_not_supported_subtitle), + BigIcon.Style.AlertSolid + ) + } else { + Triple( + stringResource(R.string.screen_link_new_device_root_title), + null, + BigIcon.Style.Default(CompoundIcons.Devices()) + ) + } + FlowStepPage( + onBackClick = onBackClick, + title = title, + subTitle = subtitle, + iconStyle = iconStyle, + buttons = { + when (state.isSupported) { + is AsyncData.Uninitialized, + is AsyncData.Loading -> { + LoadingButtonAtom() + } + is AsyncData.Failure -> { + Text( + text = stringResource(id = CommonStrings.error_unknown), + color = ElementTheme.colors.textCriticalPrimary, + style = ElementTheme.typography.fontBodyMdRegular, + textAlign = TextAlign.Center, + ) + Button( + onClick = onBackClick, + text = stringResource(CommonStrings.action_dismiss), + modifier = Modifier.fillMaxWidth(), + ) + } + is AsyncData.Success -> { + if (state.isSupported.data) { + when (state.qrCodeData) { + AsyncData.Uninitialized, + is AsyncData.Failure -> { + Button( + onClick = { state.eventSink(LinkNewDeviceRootEvent.LinkMobileDevice) }, + text = stringResource(id = R.string.screen_link_new_device_root_mobile_device), + modifier = Modifier.fillMaxWidth(), + leadingIcon = IconSource.Vector(CompoundIcons.Mobile()), + ) + Button( + onClick = onLinkDesktopDeviceClick, + text = stringResource(id = R.string.screen_link_new_device_root_desktop_computer), + modifier = Modifier.fillMaxWidth(), + leadingIcon = IconSource.Vector(CompoundIcons.Computer()), + ) + } + is AsyncData.Loading, + is AsyncData.Success -> { + Button( + onClick = { state.eventSink(LinkNewDeviceRootEvent.LinkMobileDevice) }, + text = stringResource(id = R.string.screen_link_new_device_root_loading_qr_code), + showProgress = true, + enabled = false, + modifier = Modifier.fillMaxWidth(), + ) + Button( + onClick = onLinkDesktopDeviceClick, + text = stringResource(id = R.string.screen_link_new_device_root_desktop_computer), + modifier = Modifier.fillMaxWidth(), + enabled = false, + leadingIcon = IconSource.Vector(CompoundIcons.Computer()), + ) + } + } + } else { + Button( + onClick = onBackClick, + text = stringResource(CommonStrings.action_dismiss), + modifier = Modifier.fillMaxWidth(), + ) + } + } + } + }, + modifier = modifier, + ) + + val failure = state.qrCodeData.errorOrNull() + if (failure != null) { + ErrorDialog( + content = failure.message ?: stringResource(CommonStrings.error_unknown), + onSubmit = { state.eventSink(LinkNewDeviceRootEvent.CloseDialog) }, + ) + } +} + +@PreviewsDayNight +@Composable +internal fun LinkNewDeviceRootViewPreview( + @PreviewParameter(LinkNewDeviceRootStateProvider::class) state: LinkNewDeviceRootState +) = ElementPreview { + LinkNewDeviceRootView( + state = state, + onBackClick = { }, + onLinkDesktopDeviceClick = { }, + ) +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeEvent.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeEvent.kt new file mode 100644 index 0000000000..c17a28649c --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeEvent.kt @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.scan + +sealed interface ScanQrCodeEvent { + data class QrCodeScanned(val data: ByteArray) : ScanQrCodeEvent + data object TryAgain : ScanQrCodeEvent +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeNode.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeNode.kt new file mode 100644 index 0000000000..ff4f88f9ae --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeNode.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.scan + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject +import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback +import io.element.android.libraries.di.SessionScope + +@ContributesNode(SessionScope::class) +@AssistedInject +class ScanQrCodeNode( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: ScanQrCodePresenter, +) : Node(buildContext, plugins = plugins) { + interface Callback : Plugin { + fun cancel() + } + + private val callback: Callback = callback() + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + ScanQrCodeView( + state = state, + onBackClick = callback::cancel, + modifier = modifier + ) + } +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodePresenter.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodePresenter.kt new file mode 100644 index 0000000000..5f0131a8df --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodePresenter.kt @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.scan + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import dev.zacsweers.metro.Inject +import io.element.android.features.linknewdevice.impl.LinkNewDesktopHandler +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopStep +import kotlinx.coroutines.launch + +@Inject +class ScanQrCodePresenter( + private val linkNewDesktopHandler: LinkNewDesktopHandler, +) : Presenter { + @Composable + override fun present(): ScanQrCodeState { + val coroutineScope = rememberCoroutineScope() + var scanAction: AsyncAction by remember { mutableStateOf(AsyncAction.Loading) } + + // Observe the flow to react on LinkDesktopStep.InvalidQrCode + val linkDesktopStep by linkNewDesktopHandler.stepFlow.collectAsState() + + LaunchedEffect(Unit) { + linkNewDesktopHandler.createNewHandler() + } + + LaunchedEffect(linkDesktopStep) { + when (val step = linkDesktopStep) { + is LinkDesktopStep.InvalidQrCode -> { + scanAction = AsyncAction.Failure(Exception(step.error)) + } + else -> Unit + } + } + + fun handleEvent(event: ScanQrCodeEvent) { + when (event) { + ScanQrCodeEvent.TryAgain -> { + scanAction = AsyncAction.Loading + } + is ScanQrCodeEvent.QrCodeScanned -> coroutineScope.launch { + // In this case the scanning will stop and a loader will be shown + scanAction = AsyncAction.Success(Unit) + try { + linkNewDesktopHandler.onScannedCode(event.data) + } catch (e: Exception) { + // Should not happen as errors are handled through the LinkDesktopStep flow + scanAction = AsyncAction.Failure(e) + } + } + } + } + + return ScanQrCodeState( + scanAction = scanAction, + eventSink = ::handleEvent, + ) + } +} diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeState.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeState.kt new file mode 100644 index 0000000000..6cb0363836 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeState.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.scan + +import io.element.android.libraries.architecture.AsyncAction + +data class ScanQrCodeState( + val scanAction: AsyncAction, + val eventSink: (ScanQrCodeEvent) -> Unit, +) diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeStateProvider.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeStateProvider.kt new file mode 100644 index 0000000000..40e7df8884 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeStateProvider.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.scan + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.architecture.AsyncAction + +open class ScanQrCodeStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aScanQrCodeState(), + aScanQrCodeState(scanAction = AsyncAction.Loading), + aScanQrCodeState(scanAction = AsyncAction.Success(Unit)), + aScanQrCodeState(scanAction = AsyncAction.Failure(Exception("Scan failed"))), + ) +} + +fun aScanQrCodeState( + scanAction: AsyncAction = AsyncAction.Uninitialized, + eventSink: (ScanQrCodeEvent) -> Unit = {}, +) = ScanQrCodeState( + scanAction = scanAction, + eventSink = eventSink +) diff --git a/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeView.kt b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeView.kt new file mode 100644 index 0000000000..937aee08b4 --- /dev/null +++ b/features/linknewdevice/impl/src/main/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeView.kt @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.scan + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.progressSemantics +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.linknewdevice.impl.R +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage +import io.element.android.libraries.designsystem.components.BigIcon +import io.element.android.libraries.designsystem.modifiers.cornerBorder +import io.element.android.libraries.designsystem.modifiers.squareSize +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.qrcode.QrCodeCameraView +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun ScanQrCodeView( + state: ScanQrCodeState, + onBackClick: () -> Unit, + modifier: Modifier = Modifier, +) { + FlowStepPage( + modifier = modifier, + onBackClick = onBackClick, + iconStyle = BigIcon.Style.Default(CompoundIcons.Computer()), + title = stringResource(R.string.screen_link_new_device_desktop_scanning_title), + content = { Content(state = state) }, + buttons = { Buttons(state = state) } + ) +} + +@Composable +private fun Content( + state: ScanQrCodeState, +) { + BoxWithConstraints( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.Center, + ) { + val modifier = if (constraints.maxWidth > constraints.maxHeight) { + Modifier.fillMaxHeight() + } else { + Modifier.fillMaxWidth() + }.then( + Modifier + .padding(start = 20.dp, end = 20.dp, top = 50.dp, bottom = 32.dp) + .squareSize() + .cornerBorder( + strokeWidth = 4.dp, + color = ElementTheme.colors.textPrimary, + cornerSizeDp = 42.dp, + ) + ) + Box( + modifier = modifier, + contentAlignment = Alignment.Center, + ) { + QrCodeCameraView( + modifier = Modifier.fillMaxSize(), + onScanQrCode = { state.eventSink.invoke(ScanQrCodeEvent.QrCodeScanned(it)) }, + isScanning = state.scanAction.isLoading(), + ) + } + } +} + +@Composable +private fun ColumnScope.Buttons( + state: ScanQrCodeState, +) { + Column(Modifier.heightIn(min = 130.dp)) { + when (state.scanAction) { + is AsyncAction.Failure -> { + Button( + text = stringResource(id = CommonStrings.action_try_again), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), + onClick = { + state.eventSink.invoke(ScanQrCodeEvent.TryAgain) + } + ) + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = CompoundIcons.ErrorSolid(), + tint = ElementTheme.colors.iconCriticalPrimary, + contentDescription = null, + modifier = Modifier.size(24.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = stringResource(R.string.screen_qr_code_login_invalid_scan_state_subtitle), + textAlign = TextAlign.Center, + color = ElementTheme.colors.textCriticalPrimary, + style = ElementTheme.typography.fontBodySmMedium, + ) + } + Text( + text = stringResource(R.string.screen_qr_code_login_invalid_scan_state_description), + textAlign = TextAlign.Center, + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textSecondary, + ) + } + } + is AsyncAction.Success -> { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + CircularProgressIndicator( + modifier = Modifier + .progressSemantics() + .size(20.dp), + strokeWidth = 2.dp + ) + } + } + AsyncAction.Loading, + AsyncAction.Uninitialized, + is AsyncAction.Confirming -> Unit + } + } +} + +@PreviewsDayNight +@Composable +internal fun ScanQrCodeViewPreview(@PreviewParameter(ScanQrCodeStateProvider::class) state: ScanQrCodeState) = ElementPreview { + ScanQrCodeView( + state = state, + onBackClick = {}, + ) +} diff --git a/features/linknewdevice/impl/src/main/res/values-be/translations.xml b/features/linknewdevice/impl/src/main/res/values-be/translations.xml new file mode 100644 index 0000000000..16372fa6e4 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-be/translations.xml @@ -0,0 +1,38 @@ + + + "Сканіраваць QR-код" + "Адсканіруйце QR-код з дапамогай гэтай прылады" + "Гатовы да сканіравання" + "Ваш правайдар уліковага запісу не падтрымлівае %1$s." + "%1$s не падтрымліваецца" + "QR-код не падтрымліваецца" + "Уваход быў адменены на іншай прыладзе." + "Запыт на ўваход скасаваны" + "Тэрмін уваходу скончыўся. Калі ласка, паспрабуйце яшчэ раз." + "Уваход у сістэму не быў завершаны своечасова" + "Выберыце %1$s" + "Не атрымалася ўсталяваць бяспечнае злучэнне з новай прыладай. Існуючыя прылады па-ранейшаму ў бяспецы, і вам не трэба турбавацца пра іх." + "Што зараз?" + "Паспрабуйце зноў увайсці ў сістэму з дапамогай QR-кода, калі гэта была сеткавая праблема" + "Калі вы сутыкнуліся з той жа праблемай, паспрабуйце іншую сетку Wi-Fi або скарыстайцеся мабільнымі дадзенымі замест Wi-Fi." + "Калі гэта не дапамагло, увайдзіце ўручную" + "Злучэнне небяспечнае" + "Уваход быў адменены на іншай прыладзе." + "Запыт на ўваход скасаваны" + "Уваход на іншай прыладзе быў адхілены." + "Уваход адхілены" + "Тэрмін уваходу скончыўся. Калі ласка, паспрабуйце яшчэ раз." + "Уваход у сістэму не быў завершаны своечасова" + "Ваша іншая прылада не падтрымлівае ўваход у %s з дапамогай QR-кода. + +Паспрабуйце ўвайсці ў сістэму ўручную або адсканіруйце QR-код з дапамогай іншай прылады." + "QR-код не падтрымліваецца" + "Ваш правайдар уліковага запісу не падтрымлівае %1$s." + "%1$s не падтрымліваецца" + "Выкарыстоўвайце QR-код, паказаны на іншай прыладзе." + "Паўтарыць спробу" + "Няправільны QR-код" + "Каб працягнуць, вам неабходна дазволіць %1$s выкарыстоўваць камеру вашай прылады." + "Дазвольце доступ да камеры для сканіравання QR-кода" + "Адбылася нечаканая памылка. Калі ласка, паспрабуйце яшчэ раз." + diff --git a/features/deactivation/impl/src/main/res/values-eo/translations.xml b/features/linknewdevice/impl/src/main/res/values-bg/translations.xml similarity index 50% rename from features/deactivation/impl/src/main/res/values-eo/translations.xml rename to features/linknewdevice/impl/src/main/res/values-bg/translations.xml index 75c2c252e7..e8a8ea95d3 100644 --- a/features/deactivation/impl/src/main/res/values-eo/translations.xml +++ b/features/linknewdevice/impl/src/main/res/values-bg/translations.xml @@ -1,4 +1,4 @@ - "Delete your account information from our server." + "Повторен опит" diff --git a/features/linknewdevice/impl/src/main/res/values-cs/translations.xml b/features/linknewdevice/impl/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..de08f790e3 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-cs/translations.xml @@ -0,0 +1,38 @@ + + + "Naskenujte QR kód" + "Naskenujte QR kód pomocí tohoto zařízení" + "Připraveno ke skenování" + "Váš poskytovatel účtu nepodporuje %1$s." + "%1$s není podporováno" + "QR kód není podporován" + "Přihlášení bylo na druhém zařízení zrušeno." + "Žádost o přihlášení zrušena" + "Platnost přihlášení vypršela. Zkuste to prosím znovu." + "Přihlášení nebylo dokončeno včas" + "Vybrat %1$s" + "K novému zařízení se nepodařilo navázat bezpečné připojení. Vaše stávající zařízení jsou stále v bezpečí a nemusíte se o ně obávat." + "Co teď?" + "Zkuste se znovu přihlásit pomocí QR kódu v případě, že se jednalo o problém se sítí" + "Pokud narazíte na stejný problém, zkuste jinou síť wifi nebo použijte mobilní data místo wifi" + "Pokud to nefunguje, přihlaste se ručně" + "Připojení není zabezpečené" + "Přihlášení bylo na druhém zařízení zrušeno." + "Žádost o přihlášení zrušena" + "Přihlášení bylo na druhém zařízení odmítnuto." + "Přihlášení odmítnuto" + "Platnost přihlášení vypršela. Zkuste to prosím znovu." + "Přihlášení nebylo dokončeno včas" + "Vaše druhé zařízení nepodporuje přihlášení k %su pomocí QR kódu. + +Zkuste se přihlásit ručně nebo naskenujte QR kód pomocí jiného zařízení." + "QR kód není podporován" + "Váš poskytovatel účtu nepodporuje %1$s." + "%1$s není podporováno" + "Použijte QR kód zobrazený na druhém zařízení." + "Zkusit znovu" + "Špatný QR kód" + "Abyste mohli pokračovat, musíte aplikaci %1$s udělit povolení k použití kamery vašeho zařízení." + "Povolte přístup k fotoaparátu a naskenujte QR kód" + "Vyskytla se neočekávaná chyba. Prosím zkuste to znovu." + diff --git a/features/linknewdevice/impl/src/main/res/values-cy/translations.xml b/features/linknewdevice/impl/src/main/res/values-cy/translations.xml new file mode 100644 index 0000000000..b26aed52ef --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-cy/translations.xml @@ -0,0 +1,38 @@ + + + "Sganiwch y cod QR" + "Sganiwch y cod QR gyda\'r ddyfais hon" + "Yn barod i sganio" + "Nid yw darparwr eich cyfrif yn cefnogi %1$s." + "%1$s heb ei gefnogi" + "Nid yw\'r cod QR yn cael ei gefnogi" + "Cafodd y mewngofnodi ei ddiddymu ar y ddyfais arall." + "Cais mewngofnodi wedi\'i ddiddymu" + "Mewngofnodi wedi dod i ben. Ceisiwch eto." + "Heb gwblhau\'r mewngofnodi mewn pryd" + "Dewiswch %1$s" + "Nid oedd modd gwneud cysylltiad diogel â\'r ddyfais newydd. Mae eich dyfeisiau presennol yn dal yn ddiogel a does dim angen i chi boeni amdanyn nhw." + "Beth nawr?" + "Ceisiwch fewngofnodi eto gyda chod QR rhag ofn bod hyn yn broblem rhwydwaith" + "Os ydych chi\'n dod ar draws yr un broblem, rhowch gynnig ar rwydwaith wifi gwahanol neu defnyddiwch eich data symudol yn lle wifi" + "Os nad yw hynny\'n gweithio, mewngofnodwch â llaw" + "Nid yw\'r cysylltiad yn ddiogel" + "Cafodd y mewngofnodi ei ddiddymu ar y ddyfais arall." + "Cais mewngofnodi wedi\'i ddiddymu" + "Cafodd y mewngofnodi ar y ddyfais arall ei wrthod." + "Gwrthodwyd y mewngofnodi" + "Mewngofnodi wedi dod i ben. Ceisiwch eto." + "Heb gwblhau\'r mewngofnodi mewn pryd" + "Nid yw eich dyfais arall yn cefnogi mewngofnodi i %s gyda chod QR. + +Ceisiwch fewngofnodi â llaw, neu sganiwch y cod QR gyda dyfais arall." + "Nid yw\'r cod QR yn cael ei gefnogi" + "Nid yw darparwr eich cyfrif yn cefnogi %1$s." + "%1$s heb ei gefnogi" + "Defnyddiwch y cod QR sy\'n cael ei ddangos ar y ddyfais arall." + "Ceisiwch eto" + "Cod QR anghywir" + "Mae angen i chi roi caniatâd i %1$s ddefnyddio camera eich dyfais er mwyn parhau." + "Caniatáu mynediad camera i sganio\'r cod QR" + "Digwyddodd gwall annisgwyl. Ceisiwch eto." + diff --git a/features/linknewdevice/impl/src/main/res/values-da/translations.xml b/features/linknewdevice/impl/src/main/res/values-da/translations.xml new file mode 100644 index 0000000000..a31175c1d5 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-da/translations.xml @@ -0,0 +1,38 @@ + + + "Scan QR-koden" + "Scan QR-koden med denne enhed" + "Klar til at scanne" + "Din kontoudbyder understøtter ikke %1$s." + "%1$s understøttes ikke" + "QR-kode understøttes ikke" + "Login blev annulleret på den anden enhed." + "Anmodning om login annulleret" + "Login er udløbet. Prøv venligst igen." + "Login blev ikke afsluttet i tide" + "Vælg %1$s" + "Der kunne ikke oprettes en sikker forbindelse til den nye enhed. Dine eksisterende enheder er stadig sikre, og du behøver ikke bekymre dig om dem." + "Hvad nu?" + "Prøv at logge ind igen med en QR-kode, hvis dette skyldtes et netværksproblem" + "Hvis du støder på det samme problem, kan du prøve et andet wifi-netværk eller bruge dine mobildata i stedet for wifi" + "Hvis det ikke virker, skal du logge ind manuelt" + "Forbindelsen er ikke sikker" + "Login blev annulleret på den anden enhed." + "Anmodning om login annulleret" + "Login blev afvist på den anden enhed." + "Login afvist" + "Login er udløbet. Prøv venligst igen." + "Login blev ikke afsluttet i tide" + "Din anden enhed understøtter ikke at logge ind på %s med en QR-kode. + +Prøv at logge ind manuelt, eller scan QR-koden med en anden enhed." + "QR-kode understøttes ikke" + "Din kontoudbyder understøtter ikke %1$s." + "%1$s understøttes ikke" + "Brug den QR-kode, der bliver vist på den anden enhed." + "Prøv igen" + "Forkert QR-kode" + "Du skal give tilladelse til at %1$s kan benytte enhedens kamera, for at fortsætte." + "Tillad kameraadgang for at scanne QR-koden" + "Der opstod en uventet fejl. Prøv venligst igen." + diff --git a/features/linknewdevice/impl/src/main/res/values-de/translations.xml b/features/linknewdevice/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..25343c93c2 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,38 @@ + + + "QR-Code scannen" + "Scanne den QR-Code mit diesem Gerät" + "Bereit zum Scannen" + "Dein Kontoanbieter unterstützt %1$s nicht." + "%1$s wird nicht unterstützt" + "QR-Code wird nicht unterstützt" + "Die Anmeldung wurde auf dem anderen Gerät abgebrochen." + "Anmeldeanfrage abgebrochen" + "Die Anmeldung ist abgelaufen. Bitte versuche es erneut." + "Die Anmeldung wurde nicht rechtzeitig abgeschlossen" + "Wähle %1$s" + "Es konnte keine sichere Verbindung zu dem neuen Gerät hergestellt werden." + "Und jetzt?" + "Versuche, dich erneut mit einem QR-Code anzumelden, falls dies ein Netzwerkproblem war." + "Wenn das Problem bestehen bleibt, versuche es mit einem anderen WLAN-Netzwerk oder verwende deine mobilen Daten statt WLAN." + "Wenn das nicht funktioniert, melde dich manuell an" + "Die Verbindung ist nicht sicher" + "Die Anmeldung wurde auf dem anderen Gerät abgebrochen." + "Anmeldeanfrage abgebrochen" + "Die Anmeldung auf dem anderen Gerät wurde abgelehnt." + "Anmelden abgelehnt" + "Die Anmeldung ist abgelaufen. Bitte versuche es erneut." + "Die Anmeldung wurde nicht rechtzeitig abgeschlossen" + "Dein anderes Gerät unterstützt die Anmeldung bei %s mit einem QR-Code nicht. + +Versuche, dich manuell anzumelden, oder scanne den QR-Code mit einem anderen Gerät." + "QR-Code wird nicht unterstützt" + "Dein Kontoanbieter unterstützt %1$s nicht." + "%1$s wird nicht unterstützt" + "Verwende den QR-Code, der auf dem anderen Gerät angezeigt wird." + "Erneut versuchen" + "Falscher QR-Code" + "Du musst %1$s die Berechtigung erteilen, die Kamera deines Geräts zu verwenden, um fortzufahren." + "Erlaube Zugriff auf die Kamera zum Scannen des QR-Codes" + "Ein unerwarteter Fehler ist aufgetreten. Bitte versuche es erneut." + diff --git a/features/linknewdevice/impl/src/main/res/values-el/translations.xml b/features/linknewdevice/impl/src/main/res/values-el/translations.xml new file mode 100644 index 0000000000..2133bb352c --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-el/translations.xml @@ -0,0 +1,38 @@ + + + "Σάρωση κωδικού QR" + "Σάρωσε τον κωδικό QR με αυτήν τη συσκευή" + "Έτοιμο για σάρωση" + "Ο πάροχος λογαριασμού σου δεν υποστηρίζει το %1$s." + "Το %1$s δεν υποστηρίζεται" + "Ο κωδικός QR δεν υποστηρίζεται" + "Η σύνδεση ακυρώθηκε στην άλλη συσκευή." + "Το αίτημα σύνδεσης ακυρώθηκε" + "Η είσοδος έληξε. Παρακαλώ προσπάθησε ξανά." + "Η σύνδεση δεν ολοκληρώθηκε εγκαίρως" + "Επιλογή %1$s" + "Δεν ήταν δυνατή η πραγματοποίηση ασφαλούς σύνδεσης στη νέα συσκευή. Οι υπάρχουσες συσκευές σας εξακολουθούν να είναι ασφαλείς και δεν χρειάζεται να ανησυχείς για αυτές." + "Τί είναι πάλι;" + "Δοκίμασε να συνδεθείς ξανά με έναν κωδικό QR σε περίπτωση που ήταν πρόβλημα του δικτύου" + "Εάν αντιμετωπίσεις το ίδιο πρόβλημα, δοκίμασε ένα διαφορετικό δίκτυο wifi ή χρησιμοποίησε τα δεδομένα του κινητού σου αντί για wifi" + "Εάν δεν λειτουργήσει, συνδέσου χειροκίνητα" + "Η σύνδεση δεν είναι ασφαλής" + "Η σύνδεση ακυρώθηκε στην άλλη συσκευή." + "Το αίτημα σύνδεσης ακυρώθηκε" + "Η σύνδεση απορρίφθηκε στην άλλη συσκευή." + "Η σύνδεση απορρίφθηκε" + "Η είσοδος έληξε. Παρακαλώ προσπάθησε ξανά." + "Η σύνδεση δεν ολοκληρώθηκε εγκαίρως" + "Η άλλη σου συσκευή δεν υποστηρίζει σύνδεση στο %s με κωδικό QR. + +Δοκίμασε να συνδεθείς χειροκίνητα ή σάρωσε τον κωδικό QR με άλλη συσκευή." + "Ο κωδικός QR δεν υποστηρίζεται" + "Ο πάροχος λογαριασμού σου δεν υποστηρίζει το %1$s." + "Το %1$s δεν υποστηρίζεται" + "Χρησιμοποίησε τον κωδικό QR που εμφανίζεται στην άλλη συσκευή." + "Προσπάθησε ξανά" + "Λάθος κωδικός QR" + "Πρέπει να δώσεις άδεια για %1$s για να χρησιμοποιήσεις την κάμερα της συσκευής σου και να συνεχίσεις." + "Επέτρεψε την πρόσβαση της κάμερας για σάρωση του κωδικού QR" + "Παρουσιάστηκε ένα απροσδόκητο σφάλμα. Παρακαλώ προσπάθησε ξανά." + diff --git a/features/login/impl/src/main/res/values-eo/translations.xml b/features/linknewdevice/impl/src/main/res/values-en-rUS/translations.xml similarity index 51% rename from features/login/impl/src/main/res/values-eo/translations.xml rename to features/linknewdevice/impl/src/main/res/values-en-rUS/translations.xml index d8ecc9f5d6..6395ce9e54 100644 --- a/features/login/impl/src/main/res/values-eo/translations.xml +++ b/features/linknewdevice/impl/src/main/res/values-en-rUS/translations.xml @@ -1,4 +1,4 @@ - "A secure connection could not be made to the new device. Your existing linked devices are still safe and you don\'t need to worry about them." + "If you encounter the same problem, try a different Wi-Fi network or use your mobile data instead of Wi-Fi" diff --git a/features/linknewdevice/impl/src/main/res/values-es/translations.xml b/features/linknewdevice/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..032813a2c4 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,38 @@ + + + "Escanea el código QR" + "Escanea el código QR con este dispositivo" + "Listo para escanear" + "Tu proveedor de cuentas no es compatible con %1$s." + "%1$s no admitido" + "Código QR no admitido" + "El inicio de sesión se canceló en el otro dispositivo." + "Solicitud de inicio de sesión cancelada" + "El inicio de sesión ha caducado. Inténtalo de nuevo." + "El inicio de sesión no se completó a tiempo" + "Selecciona %1$s" + "No se pudo establecer una conexión segura con el nuevo dispositivo. Tus dispositivos actuales siguen siendo seguros y no tienes que preocuparte por ellos." + "¿Y ahora qué?" + "Intenta iniciar sesión de nuevo con un código QR en caso de que se trate de un problema de red" + "Si te encuentras con el mismo problema, prueba con una red wifi diferente o usa tus datos móviles en lugar de wifi" + "Si eso no funciona, inicia sesión manualmente" + "La conexión no es segura" + "El inicio de sesión se canceló en el otro dispositivo." + "Solicitud de inicio de sesión cancelada" + "El inicio de sesión se rechazó en el otro dispositivo." + "Inicio de sesión rechazado" + "El inicio de sesión ha caducado. Inténtalo de nuevo." + "El inicio de sesión no se completó a tiempo" + "Tu otro dispositivo no admite el inicio de sesión en %s con un código QR. + +Intenta iniciar sesión manualmente o escanea el código QR con otro dispositivo." + "Código QR no admitido" + "Tu proveedor de cuentas no es compatible con %1$s." + "%1$s no admitido" + "Usa el código QR que se muestra en el otro dispositivo." + "Intentar de nuevo" + "Código QR incorrecto" + "Tienes que dar permiso a %1$s para que utilice la cámara de tu dispositivo y así poder continuar." + "Permite el acceso a la cámara para escanear el código QR" + "Se ha producido un error inesperado. Vuelve a intentarlo." + diff --git a/features/linknewdevice/impl/src/main/res/values-et/translations.xml b/features/linknewdevice/impl/src/main/res/values-et/translations.xml new file mode 100644 index 0000000000..6aa1398e0a --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-et/translations.xml @@ -0,0 +1,57 @@ + + + "Skaneeri QR-koodi" + "Ava %1$s kas oma süle- või lauaarvutis" + "Skaneeri QR-koodi selle seadmega" + "Skaneerimiseks valmis" + "QR-koodi laadimiseks ava %1$s süle- või lauaarvutis" + "Numbrid ei klapi" + "Sisesta kahekohaline kood" + "Sellega verifitseerime, et ühendus sinu teise seadmega on turvaline." + "Sisesta teises seadmes kuvatud number" + "Sinu teenusepakkuja ei toeta rakendust %1$s." + "%1$s pole toetatud" + "Sinu kasutajakonto teenusepakkuja ei toeta võimalust logida sisse QR-koodi abil." + "QR-kood pole toetatud" + "Sisselogimine katkestati teises seadmes." + "Sisselogimispäring on tühistatud" + "Sisselogimine aegus. Palun proovi uuesti." + "Sisselogimine jäi etteantud aja jooksul tegemata" + "Ava %1$s teises seadmes" + "Vali %1$s" + "„Logi sisse QR-koodiga“" + "Skaneeri siin näidatud QR-koodi teise seadmega" + "Ava %1$s teises seadmes" + "Lauaarvuti" + "Laadin QR-koodi…" + "Nutiseade" + "Mis tüüpi seadet soovid siduda?" + "Palun proovi uuesti ja veendu, et sisestasid kahekohalise koodi õigesti. Kui numbrid ikka ei klapi, võta ühendust oma kasutajakonto teenusepakkujaga." + "Numbrid ei klapi" + "Turvalise ühenduse loomine uue seadmega ei õnnestunud. Sinu olemasolevad seadmed on jätkuvalt turvatud ja sa ei pea nende pärast muretsema." + "Mida järgmiseks teeme?" + "Kui see juhtumisi oli võrguühenduse viga, siis proovi uuesti QR-koodiga sisse logida" + "Kui sama probleem kordub, siis kasuta mõnda muud WiFi- või mobiilset andmedsideühendust" + "Kui see ka ei aita, siis logi sisse käsitsi" + "Ühendus pole turvaline" + "Sisselogimine katkestati teises seadmes." + "Sisselogimispäring on tühistatud" + "Sisselogimisest on teises seadmes keeldutud." + "Sisselogimisest on keeldutud" + "Sa ei pea enam midagi muud tegema." + "Sinu muu seade on juba sisse logitud" + "Sisselogimine aegus. Palun proovi uuesti." + "Sisselogimine jäi etteantud aja jooksul tegemata" + "Sinu teine seade ei toeta %s sisselogimist QR-koodiga. + +Proovi käsitsi sisselogimist või skaneeri QR-koodi mõne muu seadmega." + "QR-kood pole toetatud" + "Sinu teenusepakkuja ei toeta rakendust %1$s." + "%1$s pole toetatud" + "Kasuta teises seadmes näidatavat QR-koodi" + "Proovi uuesti" + "Vale QR-kood" + "Jätkamiseks pead lubama, et %1$s saab kasutada sinu nutiseadme kaamerat" + "QR-koodi lugemiseks luba kaamerat kasutada" + "Tekkis ootamatu viga. Palun proovi uuesti." + diff --git a/features/linknewdevice/impl/src/main/res/values-eu/translations.xml b/features/linknewdevice/impl/src/main/res/values-eu/translations.xml new file mode 100644 index 0000000000..06cc0fd857 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-eu/translations.xml @@ -0,0 +1,36 @@ + + + "Eskaneatu QR kodea" + "Eskaneatu QR kodea gailu honekin" + "Eskaneatzeko prest" + "Zure kontu-hornitzailea ez da %1$s-ekin bateragarria." + "%1$s ez da bateragarria" + "QR kodea ez da bateragarria" + "Saioa hasteko eskaera bertan behera utzi da beste gailuan" + "Saioa hasteko eskaera bertan behera utzi da" + "Saio-hasiera iraungi da. Saiatu berriro." + "Saio-hasiera ez da garaiz gauzatu." + "Hautatu %1$s" + "Ezin izan da konexio segururik ezarri gailu berriarekin. Lehendik dauden gailuak seguru daude oraindik ere eta ez duzu haietaz kezkatu beharrik." + "Orain zer?" + "Saiatu berriro QR kodearekin saioa hasten sare-arazo bat izan bada" + "Horrek ez badu funtzionatzen, hasi saioa eskuz" + "Konexioa ez da segurua" + "Saioa hasteko eskaera bertan behera utzi da beste gailuan" + "Saioa hasteko eskaera bertan behera utzi da" + "Saioa hasteari uko egin zaio beste dispositiboan." + "Saio-hasiera ukatu da" + "Saio-hasiera iraungi da. Saiatu berriro." + "Saio-hasiera ez da garaiz gauzatu." + "Beste gailua ez da bateragarria QR kodeak erabiliz %s(e)n saioa hastearekin. + +Saiatu saioa eskuz hasten, edo eskaneatu QR kodea beste gailu batean." + "QR kodea ez da bateragarria" + "Zure kontu-hornitzailea ez da %1$s-ekin bateragarria." + "%1$s ez da bateragarria" + "Erabili beste gailuan agertzen den QR kodea." + "Saiatu berriro" + "QR kode okerra" + "Baimendu kameraren sarbidea QR kodea eskaneatzeko" + "Ustekabeko errore bat gertatu da. Saiatu berriro." + diff --git a/features/linknewdevice/impl/src/main/res/values-fa/translations.xml b/features/linknewdevice/impl/src/main/res/values-fa/translations.xml new file mode 100644 index 0000000000..804fa653ad --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-fa/translations.xml @@ -0,0 +1,36 @@ + + + "پویش کد پاس" + "پویش کد پاس با این افزاره" + "آمادهٔ پویش" + "فراهم کنندهٔ حسابتان از %1$s پشتیبانی نمی‌کند." + "%1$s پشتیبانی نمی‌شود" + "کد پاس پشتیبانی نمی‌شود" + "ورود روی افزارهٔ دیگر لغو شد." + "درخواست ورد لغو شد" + "ورود منقضی شد. لطفاً دوباره تلاش کنید." + "ورود در زمان معیّن کامل نشد" + "گزینش %1$s" + "نتوانست اتّصالی امن به افزارهٔ جدید بسازد. افزاره‌های موجودتان هنوز امنند و نیازی نیست نگرانشان باشید." + "اکنون چه؟" + "ورود دستی در صورت کار نکردنش" + "اتّصال ناامن" + "ورود روی افزارهٔ دیگر لغو شد." + "درخواست ورد لغو شد" + "ورود به دست افزارهٔ دیگر رد شد." + "ورود رد شد" + "ورود منقضی شد. لطفاً دوباره تلاش کنید." + "ورود در زمان معیّن کامل نشد" + "افزارهٔ دیگرتان از ورود به %s با کد پاس پشتیبانی نمی‌کند. + +آزمودن ورود دستی یا پویش کد پاس با افزاره‌ای دیگر." + "کد پاس پشتیبانی نمی‌شود" + "فراهم کنندهٔ حسابتان از %1$s پشتیبانی نمی‌کند." + "%1$s پشتیبانی نمی‌شود" + "استفاده از کد پاس نشان داده روی افزارهٔ دیگر." + "تلاش دوباره" + "کد پاس اشتباه" + "برای ادامه باید اجازهٔ استفادهٔ %1$s از دوربین افزاره‌تان را بدهید." + "اجازهٔ دسترسی دوربین برای پویش کد پاس" + "خطایی غیرمنتظره رخ داد. لطفاً دوباره تلاش کنید." + diff --git a/features/linknewdevice/impl/src/main/res/values-fi/translations.xml b/features/linknewdevice/impl/src/main/res/values-fi/translations.xml new file mode 100644 index 0000000000..3ed954a842 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-fi/translations.xml @@ -0,0 +1,38 @@ + + + "Skannaa QR-koodi" + "Skannaa QR-koodi tällä laitteella" + "Valmis skannaamaan" + "Palveluntarjoajasi ei tue %1$s -sovellusta" + "%1$s -sovellusta ei tueta" + "QR-koodia ei tueta" + "Kirjautuminen peruutettiin toisella laitteella." + "Kirjautumispyyntö peruutettu" + "Kirjautuminen vanhentui. Yritä uudelleen." + "Kirjautumista ei suoritettu ajoissa" + "Valitse %1$s" + "Turvallista yhteyttä uuteen laitteeseen ei voitu muodostaa. Olemassa olevat laitteesi ovat edelleen turvassa, eikä sinun tarvitse huolehtia niistä." + "Mitä nyt?" + "Yritä kirjautua sisään uudelleen QR-koodilla, jos kyseessä oli verkko-ongelma" + "Jos kohtaat saman ongelman, kokeile toista wifi-verkkoa tai käytä mobiilidataa wifi-yhteyden sijaan" + "Jos tämä ei auta, kirjaudu sisään manuaalisesti" + "Yhteys ei ole turvallinen" + "Kirjautuminen peruutettiin toisella laitteella." + "Kirjautumispyyntö peruutettu" + "Kirjautuminen hylättiin toisella laitteella." + "Kirjautuminen hylätty" + "Kirjautuminen vanhentui. Yritä uudelleen." + "Kirjautumista ei suoritettu ajoissa" + "Toinen laitteesi ei tue kirjautumista %s -sovellukseen QR-koodilla. + +Yritä kirjautua sisään manuaalisesti tai skannaa QR-koodi toisella laitteella." + "QR-koodia ei tueta" + "Palveluntarjoajasi ei tue %1$s -sovellusta" + "%1$s -sovellusta ei tueta" + "Käytä toisessa laitteessa näkyvää QR-koodia." + "Yritä uudelleen" + "Väärä QR-koodi" + "Jatkaaksesi sinun on annettava lupa %1$s -sovellukselle käyttää laitteesi kameraa." + "Salli lupa kameraan QR-koodin skannaamiseksi" + "Tapahtui odottamaton virhe. Yritä uudelleen." + diff --git a/features/linknewdevice/impl/src/main/res/values-fr/translations.xml b/features/linknewdevice/impl/src/main/res/values-fr/translations.xml new file mode 100644 index 0000000000..f1ffe128cc --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-fr/translations.xml @@ -0,0 +1,36 @@ + + + "Scannez le QR code" + "Scanner le QR code avec cet appareil" + "Prêt à scanner" + "Votre fournisseur de compte ne supporte pas %1$s." + "%1$s n’est pas supporté" + "QR code non supporté" + "La connexion a été annulée sur l’autre appareil." + "Demande de connexion annulée" + "Connexion expirée. Veuillez essayer à nouveau." + "La connexion a pris trop de temps." + "Choisissez %1$s" + "Aucune connexion sécurisée n’a pu être établie avec la nouvelle session. Vos sessions existantes sont toujours en sécurité et vous n’avez pas à vous en soucier." + "Et maintenant ?" + "Essayez de vous connecter à nouveau à l’aide du QR code au cas où il s’agirait d’un problème réseau" + "Si vous rencontrez le même problème, essayez un autre réseau wifi ou utilisez vos données mobiles au lieu du wifi" + "Si cela ne fonctionne pas, connectez-vous manuellement" + "La connexion n’est pas sécurisée" + "La connexion a été annulée sur l’autre appareil." + "Demande de connexion annulée" + "La connexion a été refusée sur l’autre appareil." + "Connexion refusée" + "Connexion expirée. Veuillez essayer à nouveau." + "La connexion a pris trop de temps." + "Votre autre appareil ne supporte pas la connexion à %s avec un QR code. Essayer de vous connecter manuellement, ou scanner le QR code avec un autre appareil." + "QR code non supporté" + "Votre fournisseur de compte ne supporte pas %1$s." + "%1$s n’est pas supporté" + "Scannez le QR code affiché sur l’autre appareil." + "Essayer à nouveau" + "QR code erroné" + "Vous devez autoriser %1$s à utiliser la camera de votre appareil pour continuer." + "Autoriser l’usage de la caméra pour scanner le QR code" + "Une erreur inattendue s’est produite. Veuillez réessayer." + diff --git a/features/linknewdevice/impl/src/main/res/values-hr/translations.xml b/features/linknewdevice/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..20c194ef93 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,57 @@ + + + "Skeniraj QR kod" + "Otvorite %1$s na prijenosnom ili stolnom računalu" + "Skenirajte QR kod ovim uređajem" + "Spremno za skeniranje" + "Otvorite %1$s na stolnom računalu kako biste dobili QR kod" + "Brojevi se ne podudaraju" + "Unesite dvoznamenkasti kod" + "Time ćete potvrditi da je veza s vašim drugim uređajem sigurna." + "Unesite broj prikazan na vašem drugom uređaju" + "Vaš davatelj usluga računa ne podržava %1$s ." + "%1$s nije podržan" + "Vaš davatelj usluga računa ne podržava prijavu na novi uređaj pomoću QR koda." + "QR kod nije podržan" + "Prijava je otkazana na drugom uređaju." + "Zahtjev za prijavu je otkazan" + "Prijava je istekla. Pokušajte ponovno." + "Prijava nije dovršena na vrijeme" + "Otvorite %1$s na drugom uređaju" + "Odaberite %1$s" + "“Prijavi se pomoću QR koda”" + "Skenirajte ovdje prikazani QR kod drugim uređajem" + "Otvorite %1$s na drugom uređaju" + "Stolno računalo" + "Učitavanje QR koda…" + "Mobilni uređaj" + "Koju vrstu uređaja želite povezati?" + "Pokušajte ponovno i provjerite jeste li ispravno unijeli dvoznamenkasti kod. Ako se brojevi i dalje ne podudaraju, obratite se davatelju usluge računa." + "Brojevi se ne podudaraju" + "Nije moguće uspostaviti sigurnu vezu s novim uređajem. Vaši postojeći uređaji i dalje su sigurni i ne morate se brinuti zbog njih." + "Što sad?" + "Pokušajte se ponovno prijaviti pomoću QR koda u slučaju da se radilo o problemu s mrežom" + "Ako se problem ponovi, pokušajte s drugom Wi-Fi mrežom ili mobilnim podatcima umjesto Wi-Fi-ja." + "Ako to ne uspije, prijavite se ručno" + "Veza nije sigurna" + "Prijava je otkazana na drugom uređaju." + "Zahtjev za prijavu je otkazan" + "Prijava je odbijena na drugom uređaju." + "Prijava je odbijena" + "Ne morate ništa drugo napraviti." + "Vaš drugi uređaj već je prijavljen" + "Prijava je istekla. Pokušajte ponovno." + "Prijava nije dovršena na vrijeme" + "Vaš drugi uređaj ne podržava prijavu na %s pomoću QR koda. + +Pokušajte se prijaviti ručno ili skenirajte QR kod drugim uređajem." + "QR kod nije podržan" + "Vaš davatelj usluga računa ne podržava %1$s ." + "%1$s nije podržan" + "Upotrijebite QR kod prikazan na drugom uređaju." + "Pokušajte ponovno" + "Pogrešan QR kod" + "Za nastavak morate dati dopuštenje za %1$s da biste se mogli služiti kamerom svog uređaja." + "Dopustite pristup kameri kako biste mogli skenirati QR kod" + "Došlo je do neočekivane pogreške. Pokušajte ponovno." + diff --git a/features/linknewdevice/impl/src/main/res/values-hu/translations.xml b/features/linknewdevice/impl/src/main/res/values-hu/translations.xml new file mode 100644 index 0000000000..b06d7e2fd6 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-hu/translations.xml @@ -0,0 +1,38 @@ + + + "Olvassa be a QR-kódot" + "Olvassa be a QR-kódot ezzel az eszközzel" + "Készen áll a beolvasásra" + "A fiókszolgáltatója nem támogatja az %1$s-et." + "Az %1$s nem támogatott" + "A QR-kód nem támogatott" + "A bejelentkezést megszakították a másik eszközön." + "Bejelentkezési kérés törölve" + "A bejelentkezés lejárt. Próbálja újra." + "A bejelentkezés nem fejeződött be időben" + "Válassza ezt: %1$s" + "Nem sikerült biztonságos kapcsolatot létesíteni az új eszközzel. A meglévő eszközei továbbra is biztonságban vannak, és nem kell aggódnia miattuk." + "Most mi lesz?" + "Próbáljon meg újra bejelentkezni egy QR-kóddal, ha ez hálózati probléma volt." + "Ha ugyanezzel a problémával találkozik, próbálkozzon másik Wi-Fi-hálózattal, vagy a Wi-Fi helyett használja a mobil-adatkapcsolatát" + "Ha ez nem működik, jelentkezzen be kézileg" + "A kapcsolat nem biztonságos" + "A bejelentkezést megszakították a másik eszközön." + "Bejelentkezési kérés törölve" + "A bejelentkezést elutasították a másik eszközön." + "A bejelentkezés elutasítva" + "A bejelentkezés lejárt. Próbálja újra." + "A bejelentkezés nem fejeződött be időben" + "A másik eszköz nem támogatja QR-kóddal történő bejelentkezést az %sbe. + +Próbáljon meg kézileg bejelentkezni, vagy olvassa be a QR-kódot egy másik eszközzel." + "A QR-kód nem támogatott" + "A fiókszolgáltatója nem támogatja az %1$s-et." + "Az %1$s nem támogatott" + "Használja a másik eszközön látható QR-kódot." + "Próbálja újra" + "Hibás QR-kód" + "A folytatáshoz engedélyeznie kell, hogy az %1$s használhassa az eszköz kameráját." + "Engedélyezze a kamera elérését a QR-kód beolvasásához" + "Váratlan hiba történt. Próbálja meg újra." + diff --git a/features/linknewdevice/impl/src/main/res/values-in/translations.xml b/features/linknewdevice/impl/src/main/res/values-in/translations.xml new file mode 100644 index 0000000000..20badba9ba --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-in/translations.xml @@ -0,0 +1,38 @@ + + + "Pindai kode QR" + "Pindai kode QR dengan perangkat ini" + "Siap untuk memindai" + "Penyedia akun Anda tidak mendukung %1$s." + "%1$s tidak didukung" + "Kode QR tidak didukung" + "Proses masuk dibatalkan di perangkat lain." + "Permintaan masuk dibatalkan" + "Masa masuk kedaluwarsa. Silakan coba lagi." + "Proses masuk tidak selesai tepat waktu" + "Pilih %1$s" + "Koneksi aman tidak dapat dibuat ke perangkat baru. Perangkat Anda yang ada masih aman dan Anda tidak perlu khawatir tentang mereka." + "Apa sekarang?" + "Coba masuk lagi dengan kode QR jika ini adalah masalah jaringan" + "Jika Anda mengalami masalah yang sama, coba jaringan Wi-Fi yang berbeda atau gunakan data seluler Anda daripada Wi-Fi" + "Jika tidak berhasil, masuk secara manual" + "Koneksi tidak aman" + "Proses masuk dibatalkan di perangkat lain." + "Permintaan masuk dibatalkan" + "Proses masuk ditolak di perangkat lain." + "Proses masuk ditolak" + "Masa masuk kedaluwarsa. Silakan coba lagi." + "Proses masuk tidak selesai tepat waktu" + "Perangkat Anda yang lain tidak mendukung masuk ke %s dengan kode QR. + +Coba masuk secara manual, atau pindai kode QR dengan perangkat lain." + "Kode QR tidak didukung" + "Penyedia akun Anda tidak mendukung %1$s." + "%1$s tidak didukung" + "Gunakan kode QR yang ditampilkan di perangkat lain." + "Coba lagi" + "Kode QR salah" + "Anda perlu memberikan izin ke %1$s untuk menggunakan kamera perangkat Anda untuk melanjutkan." + "Izinkan akses kamera untuk memindai kode QR" + "Terjadi kesalahan tak terduga. Silakan coba lagi." + diff --git a/features/linknewdevice/impl/src/main/res/values-it/translations.xml b/features/linknewdevice/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..0cf548c19b --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,38 @@ + + + "Scansiona il codice QR" + "Scansiona il codice QR con questo dispositivo" + "Pronto per la scansione" + "Il tuo fornitore di account non supporta %1$s." + "%1$s non supportato" + "Codice QR non supportato" + "L\'accesso è stato annullato sull\'altro dispositivo." + "Richiesta di accesso annullata" + "L\'accesso è scaduto. Riprova." + "L\'accesso non è stato completato in tempo" + "Seleziona %1$s" + "Non è stato possibile stabilire una connessione sicura con il nuovo dispositivo. I tuoi dispositivi esistenti sono ancora al sicuro e non devi preoccuparti di loro." + "E adesso?" + "Prova ad accedere di nuovo con un codice QR nel caso si sia verificato un problema di rete." + "Se riscontri lo stesso problema, prova con un altra rete wifi o usa i dati mobili al posto del wifi." + "Se il problema persiste, accedi manualmente" + "La connessione non è sicura" + "L\'accesso è stato annullato sull\'altro dispositivo." + "Richiesta di accesso annullata" + "L\'accesso è stato rifiutato sull\'altro dispositivo." + "Accesso rifiutato" + "L\'accesso è scaduto. Riprova." + "L\'accesso non è stato completato in tempo" + "L\'altro dispositivo non supporta l\'accesso a %s con un codice QR. + +Prova ad accedere manualmente o scansiona il codice QR con un altro dispositivo." + "Codice QR non supportato" + "Il tuo fornitore di account non supporta %1$s." + "%1$s non supportato" + "Usa il codice QR mostrato sull\'altro dispositivo." + "Riprova" + "Codice QR sbagliato" + "Per continuare, è necessario fornire l\'autorizzazione a %1$s per utilizzare la fotocamera del dispositivo." + "Consenti l\'accesso alla fotocamera per la scansione del codice QR" + "Si è verificato un errore inatteso. Riprova." + diff --git a/features/linknewdevice/impl/src/main/res/values-ka/translations.xml b/features/linknewdevice/impl/src/main/res/values-ka/translations.xml new file mode 100644 index 0000000000..f378e585ce --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-ka/translations.xml @@ -0,0 +1,4 @@ + + + "ხელახლა ცდა" + diff --git a/features/linknewdevice/impl/src/main/res/values-ko/translations.xml b/features/linknewdevice/impl/src/main/res/values-ko/translations.xml new file mode 100644 index 0000000000..6af49fd3cf --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-ko/translations.xml @@ -0,0 +1,38 @@ + + + "QR 코드를 스캔하세요" + "이 기기로 QR 코드를 스캔하세요." + "스캔 준비 완료" + "귀하의 계정 제공자는 지원하지 않습니다 %1$s ." + "%1$s 지원되지 않습니다" + "QR 코드는 지원되지 않습니다" + "다른 기기에서 로그인이 취소되었습니다." + "로그인 요청이 취소되었습니다" + "로그인이 만료되었습니다. 다시 시도해 주세요." + "로그인 시간이 초과되었습니다." + "선택 %1$s" + "새 장치에 안전하게 연결할 수 없습니다. 기존 장치는 여전히 안전하므로 걱정할 필요가 없습니다." + "이제 어떻게 해야 할까?" + "네트워크 문제로 인해 로그인에 실패한 경우 QR 코드로 다시 로그인해 보세요." + "동일한 문제를 겪으신 경우 다른 Wi-Fi 네트워크를 사용해 보거나 Wi-Fi 대신 모바일 데이터를 사용해 보세요." + "만약 작동하지 않는 경우, 수동으로 로그인하세요." + "연결이 안전하지 않습니다" + "다른 기기에서 로그인이 취소되었습니다." + "로그인 요청이 취소되었습니다" + "다른 기기에서 로그인이 거부되었습니다." + "로그인 거부됨" + "로그인이 만료되었습니다. 다시 시도해 주세요." + "로그인 시간이 초과되었습니다." + "다른 기기에서는 QR 코드로 %s 에 로그인할 수 없습니다. + +수동으로 로그인하거나 다른 기기로 QR 코드를 스캔해 보세요." + "QR 코드는 지원되지 않습니다" + "귀하의 계정 제공자는 지원하지 않습니다 %1$s ." + "%1$s 지원되지 않습니다" + "다른 기기에 표시된 QR 코드를 사용하세요." + "다시 시도하기" + "잘못된 QR 코드" + "계속하려면 %1$s 가 기기의 카메라를 사용할 수 있도록 권한을 부여해야 합니다." + "카메라 액세스를 허용하여 QR 코드를 스캔하세요" + "예기치 않은 오류가 발생했습니다. 다시 시도해 주세요." + diff --git a/features/linknewdevice/impl/src/main/res/values-nb/translations.xml b/features/linknewdevice/impl/src/main/res/values-nb/translations.xml new file mode 100644 index 0000000000..af3559d3e8 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-nb/translations.xml @@ -0,0 +1,38 @@ + + + "Skann QR-koden" + "Skann QR-koden med denne enheten" + "Klar til å skanne" + "Kontotilbyderen din støtter ikke %1$s." + "%1$s støttes ikke" + "QR-kode støttes ikke" + "Påloggingen ble kansellert på den andre enheten." + "Påloggingsforespørsel kansellert" + "Påloggingen er utløpt. Vennligst prøv igjen." + "Påloggingen ble ikke fullført i tide" + "Velg %1$s" + "En sikker tilkobling kunne ikke opprettes til den nye enheten. Dine eksisterende enheter er fortsatt trygge, og du trenger ikke å bekymre deg for dem." + "Hva nå?" + "Prøv å logge på igjen med en QR-kode i tilfelle dette var et nettverksproblem" + "Hvis du støter på det samme problemet, kan du prøve et annet wifi-nettverk eller bruke mobildata i stedet for wifi" + "Hvis det ikke fungerer, kan du logge på manuelt" + "Forbindelsen er ikke sikker" + "Påloggingen ble kansellert på den andre enheten." + "Påloggingsforespørsel kansellert" + "Påloggingen ble avvist på den andre enheten." + "Pålogging avslått" + "Påloggingen er utløpt. Vennligst prøv igjen." + "Påloggingen ble ikke fullført i tide" + "Den andre enheten din støtter ikke pålogging på %s med en QR-kode. + +Prøv å logge på manuelt, eller skann QR-koden med en annen enhet." + "QR-kode støttes ikke" + "Kontotilbyderen din støtter ikke %1$s." + "%1$s støttes ikke" + "Bruk QR-koden som vises på den andre enheten." + "Prøv igjen" + "Feil QR-kode" + "Du må gi tillatelse til at %1$s kan bruke enhetens kamera for å fortsette." + "Tillat kameratilgang for å skanne QR-koden" + "Det oppstod en uventet feil. Prøv igjen." + diff --git a/features/linknewdevice/impl/src/main/res/values-nl/translations.xml b/features/linknewdevice/impl/src/main/res/values-nl/translations.xml new file mode 100644 index 0000000000..407a470e48 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-nl/translations.xml @@ -0,0 +1,38 @@ + + + "Scan de QR-code" + "Scan de QR-code met dit apparaat" + "Klaar om te scannen" + "Je accountprovider ondersteunt geen %1$s." + "%1$s wordt niet ondersteund" + "QR-code wordt niet ondersteund" + "De aanmelding is geannuleerd op het andere apparaat." + "Login verzoek geannuleerd" + "Aanmelden is verlopen. Probeer het opnieuw." + "De aanmelding was niet op tijd voltooid" + "Selecteer %1$s" + "Er kon geen beveiligde verbinding worden gemaakt met het nieuwe apparaat. Je bestaande apparaten zijn nog steeds veilig en je hoeft je daarover geen zorgen te maken." + "Wat nu?" + "Probeer opnieuw in te loggen met een QR-code voor het geval dit een netwerkprobleem was" + "Als je hetzelfde probleem ondervindt, probeer dan een ander wifi-netwerk of gebruik je mobiele data in plaats van wifi." + "Als dat niet werkt, log dan handmatig in" + "Verbinding niet veilig" + "De aanmelding is geannuleerd op het andere apparaat." + "Login verzoek geannuleerd" + "De aanmelding is geweigerd op het andere apparaat." + "Aanmelden geweigerd" + "Aanmelden is verlopen. Probeer het opnieuw." + "De aanmelding was niet op tijd voltooid" + "Jouw andere apparaat ondersteunt geen inloggen op %s met een QR code. + +Probeer handmatig in te loggen, of scan de QR code met een ander apparaat." + "QR-code wordt niet ondersteund" + "Je accountprovider ondersteunt geen %1$s." + "%1$s wordt niet ondersteund" + "Gebruik de QR-code die op het andere apparaat wordt weergegeven." + "Probeer het opnieuw" + "Verkeerde QR-code" + "Je moet %1$s toestemming geven om de camera van je apparaat te gebruiken om verder te gaan." + "Cameratoegang toestaan om de QR-code te scannen" + "Er is een onverwachte fout opgetreden. Probeer het opnieuw." + diff --git a/features/linknewdevice/impl/src/main/res/values-pl/translations.xml b/features/linknewdevice/impl/src/main/res/values-pl/translations.xml new file mode 100644 index 0000000000..4db42a2a49 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-pl/translations.xml @@ -0,0 +1,38 @@ + + + "Skanuj kod QR" + "Zeskanuj kod QR za pomocą tego urządzenia" + "Gotowy do skanowania" + "Twój dostawca konta nie obsługuje %1$s." + "%1$s nie jest wspierany" + "Kod QR nie jest wspierany" + "Logowanie zostało anulowane na drugim urządzeniu." + "Prośba o logowanie została anulowana" + "Logowanie wygasło. Spróbuj ponownie." + "Logowanie nie zostało ukończone na czas" + "Wybierz %1$s" + "Nie udało się nawiązać bezpiecznego połączenia z nowym urządzeniem. Twoje istniejące urządzenia są nadal bezpieczne i nie musisz się o nie martwić." + "Co teraz?" + "Spróbuj zalogować się ponownie za pomocą kodu QR, jeśli byłby to problem z siecią" + "Jeśli napotkasz ten sam problem, użyj innej sieci Wi-FI lub danych mobilnych" + "Jeśli to nie zadziała, zaloguj się ręcznie" + "Połączenie nie jest bezpieczne" + "Logowanie zostało anulowane na drugim urządzeniu." + "Prośba o logowanie została anulowana" + "Logowanie zostało odrzucone na drugim urządzeniu." + "Logowanie odrzucone" + "Logowanie wygasło. Spróbuj ponownie." + "Logowanie nie zostało ukończone na czas" + "Twoje drugie urządzenie nie wspiera logowania się do %s za pomocą kodu QR. + +Spróbuj zalogować się ręcznie lub zeskanuj kod QR na innym urządzeniu." + "Kod QR nie jest wspierany" + "Twój dostawca konta nie obsługuje %1$s." + "%1$s nie jest wspierany" + "Użyj kodu QR widocznego na drugim urządzeniu." + "Spróbuj ponownie" + "Błędny kod QR" + "Musisz przyznać uprawnienia %1$s do korzystania z kamery, aby kontynuować." + "Zezwól na dostęp do kamery, aby zeskanować kod QR" + "Wystąpił nieoczekiwany błąd. Spróbuj ponownie." + diff --git a/features/linknewdevice/impl/src/main/res/values-pt-rBR/translations.xml b/features/linknewdevice/impl/src/main/res/values-pt-rBR/translations.xml new file mode 100644 index 0000000000..8fb178f3e5 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-pt-rBR/translations.xml @@ -0,0 +1,54 @@ + + + "Leia o código QR" + "Abra o %1$s em um computador" + "Leia o código QR com este dispositivo" + "Pronto para ler" + "Abra o %1$s em um computador para receber o código QR" + "Os números não conferem" + "Digite o código de 2 dígitos" + "Isso verificará que a conexão com o seu outro dispositivo é segura." + "Digite o número exibido no outro dispositivo" + "Seu provedor de conta não tem suporte ao %1$s." + "%1$s não suportado" + "O seu provedor de conta não tem suporte a autenticação de dispositivos novos com um código QR." + "Código QR não suportado" + "A entrada foi cancelada no outro dispositivo." + "Solicitação de entrada foi cancelada" + "O processo de entrada expirou. Tente novamente." + "A entrada não foi concluída a tempo" + "Abra o %1$s no outro dispositivo" + "Selecione %1$s" + "\"Entrar com código QR\"" + "Leia o código QR exibido aqui com o outro dispositivo" + "Abra o %1$s no outro dispositivo" + "Computador" + "Carregando código QR…" + "Dispositivo móvel" + "Que tipo de dispositivo você deseja vincular?" + "Os números não conferem" + "Não foi possível estabelecer uma conexão segura com o novo dispositivo. Seus dispositivos existentes ainda estão seguros e você não precisa se preocupar com eles." + "E agora?" + "Tente entrar novamente com um código QR caso seja um problema de rede" + "Se o problema persistir, tente uma rede Wi-Fi diferente ou use seus dados móveis em vez de Wi-Fi" + "Se isso não funcionar, entre manualmente" + "Conexão insegura" + "A entrada foi cancelada no outro dispositivo." + "Solicitação de entrada foi cancelada" + "A entrada foi recusada no outro dispositivo." + "Entrada recusada" + "O processo de entrada expirou. Tente novamente." + "A entrada não foi concluída a tempo" + "Seu outro dispositivo não tem suporte a entrar no %s com um código QR. + +Tente entrar manualmente ou ler o código QR com outro dispositivo." + "Código QR não suportado" + "Seu provedor de conta não tem suporte ao %1$s." + "%1$s não suportado" + "Use o código QR exibido no outro dispositivo." + "Tente novamente" + "Código QR errado" + "Você deve permitir que o %1$s use a câmera do seu dispositivo para continuar." + "Permita o acesso à câmera para ler o código QR" + "Ocorreu um erro inesperado. Tente novamente." + diff --git a/features/linknewdevice/impl/src/main/res/values-pt/translations.xml b/features/linknewdevice/impl/src/main/res/values-pt/translations.xml new file mode 100644 index 0000000000..da6da08f38 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-pt/translations.xml @@ -0,0 +1,38 @@ + + + "Ler o código QR" + "Lê o código QR com este dispositivo" + "Pronto para ler" + "O teu operador de conta não suporta %1$s." + "%1$s não suportado" + "Código QR não suportado" + "O início de sessão foi cancelado no outro dispositivo." + "Pedido de início de sessão cancelado" + "O início de sessão expirou. Por favor, tenta novamente." + "O início de sessão não foi concluído a tempo" + "Seleciona %1$s" + "Não foi possível estabelecer uma ligação segura com o novo dispositivo. Os teus outros dispositivos continuam seguros, não precisas de te preocupar com eles." + "E agora?" + "Tenta iniciar sessão novamente com um código QR, caso se trate de um problema de rede" + "Se tiveres o mesmo problema, experimenta uma rede Wi-Fi diferente ou utiliza os teus dados móveis." + "Se isso não funcionar, inicia sessão manualmente" + "Ligação insegura" + "O início de sessão foi cancelado no outro dispositivo." + "Pedido de início de sessão cancelado" + "O início de sessão foi rejeitado no outro dispositivo." + "Início de sessão rejeitado" + "O início de sessão expirou. Por favor, tenta novamente." + "O início de sessão não foi concluído a tempo" + "O teu outro dispositivo não suporta o início de sessão na %s com um código QR. + +Tenta iniciar a sessão manualmente ou digitaliza o código QR com outro dispositivo." + "Código QR não suportado" + "O teu operador de conta não suporta %1$s." + "%1$s não suportado" + "Lê o código QR apresentado no outro dispositivo." + "Tentar novamente" + "Código QR inválido" + "Para continuar, tens que dar permissão à %1$s para aceder à câmara do teu dispositivo." + "Permitir o acesso à câmara para ler o código QR" + "Ocorreu um erro inesperado. Tenta novamente." + diff --git a/features/linknewdevice/impl/src/main/res/values-ro/translations.xml b/features/linknewdevice/impl/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..f1a4f3db59 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-ro/translations.xml @@ -0,0 +1,54 @@ + + + "Scanați codul QR" + "Deschide %1$s pe un laptop sau un computer desktop" + "Scanați codul QR cu acest dispozitiv" + "Gata de scanare" + "Deschide %1$s pe un computer desktop pentru a obține codul QR" + "Numerele nu se potrivesc" + "Introduceți codul de 2 cifre" + "Aceasta va verifica dacă conexiunea cu celălalt dispozitiv este sigură." + "Introduceți numărul afișat pe celălalt dispozitiv" + "Furnizorul dumneavoastră de cont nu acceptă %1$s." + "%1$s nu este acceptat" + "Furnizorul contului dumneavoastră nu acceptă conectarea la un dispozitiv nou cu un cod QR." + "Formatul codului QR nu este acceptat." + "Autentificarea a fost anulată de pe celălalt dispozitiv." + "Cererea de autentificare a fost anulată" + "Autentificarea a expirat. Vă rugăm să încercați din nou." + "Autentificarea nu a fost finalizată la timp" + "Deschideți %1$s pe celălalt dispozitiv" + "Selectați %1$s" + "“Conectați-vă cu un cod QR”" + "Scanați codul QR afișat aici cu celălalt dispozitiv." + "Deschideți %1$s pe celălalt dispozitiv" + "Calculator desktop" + "Se încarcă codul QR…" + "Dispozitiv mobil" + "Ce tip de dispozitiv doriți să conectați?" + "Numerele nu se potrivesc" + "Nu a putut fi făcută o conexiune sigură la noul dispozitiv. Dispozitivele existente sunt încă în siguranță și nu trebuie să vă faceți griji cu privire la ele." + "Și acum?" + "Încercați să vă conectați din nou cu un cod QR în cazul în care a fost o problemă de rețea." + "Dacă întâmpinați aceeași problemă, încercați o altă rețea Wi-Fi sau utilizați datele mobile în loc de Wi-Fi." + "Dacă nu funcționează, conectați-vă manual" + "Conexiunea nu este sigură" + "Autentificarea a fost anulată de pe celălalt dispozitiv." + "Cererea de autentificare a fost anulată" + "Autentificarea a fost refuzată pe celălalt dispozitiv." + "Autentificarea a fost refuzată" + "Autentificarea a expirat. Vă rugăm să încercați din nou." + "Autentificarea nu a fost finalizată la timp" + "Celălalt dispozitiv nu acceptă autentificarea la %s cu un cod QR. + +Încercați să vă autentificați manual sau să scanați codul QR cu un alt dispozitiv." + "Formatul codului QR nu este acceptat." + "Furnizorul dumneavoastră de cont nu acceptă %1$s." + "%1$s nu este acceptat" + "Utilizați codul QR afișat pe celălalt dispozitiv." + "Încercați din nou" + "Cod QR greșit" + "Trebuie să acordați permisiunea ca %1$s să folosească camera dispozitivului pentru a continua." + "Permiteți accesul la cameră pentru a scana codul QR" + "A apărut o eroare neașteptată. Vă rugăm să încercați din nou." + diff --git a/features/linknewdevice/impl/src/main/res/values-ru/translations.xml b/features/linknewdevice/impl/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..16ed8eeb37 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-ru/translations.xml @@ -0,0 +1,38 @@ + + + "Сканировать QR-код" + "Отсканируйте QR-код с помощью этого устройства" + "Готово к сканированию" + "Поставщик учетной записи не поддерживает %1$s." + "%1$s не поддерживается" + "QR-код не поддерживается" + "Вход на другом устройстве был отменен." + "Запрос на вход отменен" + "Срок действия входа истек. Пожалуйста, попробуйте еще раз." + "Вход в систему не был выполнен вовремя" + "Выберите %1$s" + "Не удалось установить безопасное соединение с новым устройством. Существующие устройства по-прежнему в безопасности, и вам не нужно беспокоиться о них." + "Что теперь?" + "Попробуйте снова войти в систему с помощью QR-кода, если это была проблема с соединением" + "Если вы столкнулись с той же проблемой, попробуйте сменить точку доступа Wi-Fi или используйте мобильные данные" + "Если это не помогло, войдите вручную" + "Соединение не защищено" + "Вход на другом устройстве был отменен." + "Запрос на вход отменен" + "Вход в систему был отклонен на другом устройстве." + "Вход отклонен" + "Срок действия входа истек. Пожалуйста, попробуйте еще раз." + "Вход в систему не был выполнен вовремя" + "Другое устройство не поддерживает вход в %s с помощью QR-кода. + +Попробуйте войти вручную или отсканируйте QR-код на другом устройстве." + "QR-код не поддерживается" + "Поставщик учетной записи не поддерживает %1$s." + "%1$s не поддерживается" + "Используйте QR-код, показанный на другом устройстве." + "Повторить попытку" + "Неверный QR-код" + "Чтобы продолжить, вам необходимо разрешить %1$s использовать камеру вашего устройства." + "Разрешите доступ к камере для сканирования QR-кода" + "Произошла непредвиденная ошибка. Пожалуйста, попробуйте еще раз." + diff --git a/features/linknewdevice/impl/src/main/res/values-sk/translations.xml b/features/linknewdevice/impl/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..9617df3acc --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-sk/translations.xml @@ -0,0 +1,38 @@ + + + "Naskenovať QR kód" + "Naskenujte QR kód pomocou tohto zariadenia" + "Pripravené na skenovanie" + "Poskytovateľ vášho účtu nepodporuje %1$s." + "%1$s nie je podporovaný" + "QR kód nie je podporovaný" + "Prihlásenie bolo zrušené na druhom zariadení." + "Žiadosť o prihlásenie bola zrušená" + "Platnosť prihlásenia vypršala. Skúste to prosím znova." + "Prihlásenie nebolo včas dokončené" + "Vyberte %1$s" + "K novému zariadeniu sa nepodarilo vytvoriť bezpečné pripojenie. Vaše existujúce zariadenia sú stále v bezpečí a nemusíte sa o ne obávať." + "Čo teraz?" + "Skúste sa znova prihlásiť pomocou QR kódu v prípade, že ide o problém so sieťou" + "Ak narazíte na rovnaký problém, vyskúšajte inú sieť Wi-Fi alebo namiesto siete Wi-Fi použite mobilné dáta" + "Ak to nefunguje, prihláste sa manuálne" + "Pripojenie nie je bezpečené" + "Prihlásenie bolo zrušené na druhom zariadení." + "Žiadosť o prihlásenie bola zrušená" + "Prihlásenie bolo zamietnuté na druhom zariadení." + "Prihlásenie bolo odmietnuté" + "Platnosť prihlásenia vypršala. Skúste to prosím znova." + "Prihlásenie nebolo včas dokončené" + "Vaše druhé zariadenie nepodporuje prihlásenie do aplikácie %s pomocou QR kódu. + +Skúste sa prihlásiť manuálne alebo naskenujte QR kód pomocou iného zariadenia." + "QR kód nie je podporovaný" + "Poskytovateľ vášho účtu nepodporuje %1$s." + "%1$s nie je podporovaný" + "Použite QR kód zobrazený na druhom zariadení." + "Skúste to znova" + "Nesprávny QR kód" + "Ak chcete pokračovať, musíte udeliť povolenie aplikácii %1$s používať fotoaparát vášho zariadenia." + "Povoľte prístup k fotoaparátu na naskenovanie QR kódu" + "Vyskytla sa neočakávaná chyba. Prosím, skúste to znova." + diff --git a/features/linknewdevice/impl/src/main/res/values-sv/translations.xml b/features/linknewdevice/impl/src/main/res/values-sv/translations.xml new file mode 100644 index 0000000000..8a1bef434a --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-sv/translations.xml @@ -0,0 +1,38 @@ + + + "Skanna QR-koden" + "Skanna QR-koden med den här enheten" + "Redo att skanna" + "Din kontoleverantör stöder inte %1$s." + "%1$s stöds inte" + "QR-kod stöds inte" + "Inloggningen avbröts på den andra enheten." + "Inloggningsförfrågan avbröts" + "Inloggningen har löpt ut. Vänligen försök igen." + "Inloggningen slutfördes inte i tid" + "Välj %1$s" + "En säker anslutning kunde inte göras till den nya enheten. Dina befintliga enheter är fortfarande säkra och du behöver inte oroa dig för dem." + "Nu då?" + "Pröva att logga in igen med en QR-kod ifall detta skulle vara ett nätverksproblem" + "Om du stöter på samma problem, prova ett annat wifi-nätverk eller använd din mobildata istället för wifi" + "Om det inte fungerar, logga in manuellt" + "Anslutningen är inte säker" + "Inloggningen avbröts på den andra enheten." + "Inloggningsförfrågan avbröts" + "Inloggningen avvisades på den andra enheten." + "Inloggning avvisad" + "Inloggningen har löpt ut. Vänligen försök igen." + "Inloggningen slutfördes inte i tid" + "Din andra enhet stöder inte inloggning i %s med en QR-kod. + +Prova att logga in manuellt eller skanna QR-koden med en annan enhet." + "QR-kod stöds inte" + "Din kontoleverantör stöder inte %1$s." + "%1$s stöds inte" + "Använd QR-koden som visas på den andra enheten." + "Försök igen" + "Fel QR-kod" + "Du måste ge tillstånd för %1$s att använda enhetens kamera för att kunna fortsätta." + "Tillåt kameraåtkomst för att skanna QR-koden" + "Ett oväntat fel inträffade. Vänligen försök igen." + diff --git a/features/linknewdevice/impl/src/main/res/values-tr/translations.xml b/features/linknewdevice/impl/src/main/res/values-tr/translations.xml new file mode 100644 index 0000000000..2d3bebcad8 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-tr/translations.xml @@ -0,0 +1,38 @@ + + + "QR kodunu tara" + "QR kodunu bu cihazla tarayın" + "Taramaya hazır" + "Hesap sağlayıcınız %1$s desteklemiyor." + "%1$s desteklenmiyor" + "QR kodu desteklenmiyor" + "Oturum açma işlemi diğer cihazda iptal edildi." + "Oturum açma isteği iptal edildi" + "Oturum açma süresi doldu. Lütfen tekrar deneyin." + "Oturum açma işlemi zamanında tamamlanmadı" + "Seç %1$s" + "Yeni cihaza güvenli bir bağlantı kurulamadı. Mevcut cihazlarınız hala güvende ve onlar için endişelenmenize gerek yok." + "Şimdi ne olacak?" + "Bunun bir ağ sorunu olması ihtimaline karşı bir QR koduyla tekrar oturum açmayı deneyin" + "Aynı sorunla karşılaşırsanız, farklı bir wifi ağı deneyin veya wifi yerine mobil verinizi kullanın" + "Bu işe yaramazsa, manuel olarak oturum açın" + "Bağlantı güvenli değil" + "Oturum açma işlemi diğer cihazda iptal edildi." + "Oturum açma isteği iptal edildi" + "Diğer cihazda oturum açma işlemi reddedildi." + "Oturum açma reddedildi" + "Oturum açma süresi doldu. Lütfen tekrar deneyin." + "Oturum açma işlemi zamanında tamamlanmadı" + "Diğer cihazınız %s QR koduyla oturum açmayı desteklemiyor. + +Manuel olarak oturum açmayı deneyin veya QR kodunu başka bir cihazla tarayın." + "QR kodu desteklenmiyor" + "Hesap sağlayıcınız %1$s desteklemiyor." + "%1$s desteklenmiyor" + "Diğer cihazda gösterilen QR kodunu kullan." + "Tekrar deneyin" + "Yanlış QR kodu" + "Devam etmek için %1$s cihazınızın kamerasını kullanmasına izin vermeniz gerekir." + "QR kodunu taramak için kamera erişimine izin verin" + "Beklenmeyen bir hata oluştu. Lütfen tekrar deneyin." + diff --git a/features/linknewdevice/impl/src/main/res/values-uk/translations.xml b/features/linknewdevice/impl/src/main/res/values-uk/translations.xml new file mode 100644 index 0000000000..e7104a914d --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-uk/translations.xml @@ -0,0 +1,38 @@ + + + "Зіскануйте QR-код" + "Зіскануйте QR-код цим пристроєм" + "Готовий до сканування" + "Постачальник вашого облікового запису не підтримує %1$s." + "%1$s не підтримується" + "QR-код не підтримується" + "Вхід було скасовано на іншому пристрої." + "Запит на вхід скасовано" + "Термін входу сплив. Будь ласка, спробуйте ще раз." + "Вхід не було завершено вчасно" + "Виберіть %1$s" + "Не вдалося встановити безпечне з\'єднання з новим пристроєм. Ваші наявні пристрої досі в безпеці, і вам не потрібно про них турбуватися." + "Що тепер?" + "Спробуйте увійти ще раз за допомогою QR-коду, якщо це була проблема з мережею" + "Якщо ви зіткнулися з тією ж проблемою, спробуйте іншу мережу Wi-Fi або використовуйте мобільний інтернет замість Wi-Fi" + "Якщо це не спрацює, увійдіть вручну" + "З\'єднання не безпечне" + "Вхід було скасовано на іншому пристрої." + "Запит на вхід скасовано" + "Вхід був відхилений на іншому пристрої." + "Вхід відхилено" + "Термін входу сплив. Будь ласка, спробуйте ще раз." + "Вхід не було завершено вчасно" + "Ваш інший пристрій не підтримує вхід у %s за допомогою QR-коду. + +Спробуйте ввійти вручну або відскануйте QR-код за допомогою іншого пристрою." + "QR-код не підтримується" + "Постачальник вашого облікового запису не підтримує %1$s." + "%1$s не підтримується" + "Використовуйте QR-код, показаний на іншому пристрої." + "Спробуйте ще раз" + "Неправильний QR-код" + "Вам потрібно дати дозвіл %1$s на використання камери вашого пристрою, щоб продовжити." + "Надайте доступ до камери, щоб сканувати QR-код" + "Сталася несподівана помилка. Будь ласка, спробуйте ще раз." + diff --git a/features/linknewdevice/impl/src/main/res/values-ur/translations.xml b/features/linknewdevice/impl/src/main/res/values-ur/translations.xml new file mode 100644 index 0000000000..54d2c2e401 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-ur/translations.xml @@ -0,0 +1,38 @@ + + + "کیو آر رمز مسح ضوئی کریں" + "اس آلے کے ساتھ کیو آر رمز مسح ضوئی کریں" + "مسح ضوئی کیلئے تیار" + "آپ کا کھاتہ فراہم کنندہ %1$s کا تعاون نہیں کرتا۔" + "%1$s تعاون یافتہ نہیں" + "کر رمز غیر تعاون یافتہ" + "دوسرے آلے پر دخول منسوخ کر دیا گیا تھا۔" + "دخول کی درخواست منسوخ" + "دخول کی میعاد ختم۔ برائے مہربانی دوبارہ کوشش کریں۔" + "دخول وقت پر مکمل نہیں ہوا تھا" + "%1$s منتخب کریں" + "نئے آلے سے محفوظ اتصال نہیں بنایا جا سکا۔ آپ کے موجودہ آلات اب بھی محفوظ ہیں اور آپ کو ان کے بارے میں فکر کرنے کی ضرورت نہیں ہے۔" + "اب کیا؟" + "اگر یہ شبکہ کا مسئلہ تھا تو کیو آر رمز کے ساتھ دوبارہ داخل ہونے کی کوشش کریں۔" + "اگر آپ کو بھی یہی مسئلہ درپیش ہو، تو کوئی دوسرا وائی فائی شبکہ آزمائیں یا وائی فائی کے بجائے اپنے محمول بیانات استعمال کریں۔" + "اگر یہ کام نہ کرے، تو دستی طور پر داخل ہوں" + "اتصال محفوظ نہیں" + "دوسرے آلے پر دخول منسوخ کر دیا گیا تھا۔" + "دخول کی درخواست منسوخ" + "دوسرے آلہ پر دخول کو مسترد کر دیا گیا تھا۔" + "دخول مسترد کیا گیا" + "دخول کی میعاد ختم۔ برائے مہربانی دوبارہ کوشش کریں۔" + "دخول وقت پر مکمل نہیں ہوا تھا" + "آپ کا دوسرا آلہ کیو آر رمز کے ساتھ %s میں دخول کا تعاون نہیں کرتا۔ + +دستی طور پر داخل ہونے کی کوشش کریں ، یا کسی دوسرے آلے سے کیو آر رمز مسح ضوئی کریں۔" + "کر رمز غیر تعاون یافتہ" + "آپ کا کھاتہ فراہم کنندہ %1$s کا تعاون نہیں کرتا۔" + "%1$s تعاون یافتہ نہیں" + "دوسرے آلے پر دکھایا گیا کیو آر رمز استعمال کریں۔" + "دوبارہ کوشش کریں" + "غلط کیو آر رمز" + "جاری رکھنے کے لیے آپ %1$s کو اپنے آلے کا تصویرگر استعمال کرنے کی اجازت دینے کی ضرورت ہے۔" + "کیو آر رمز کو مسح ضوئی کرنے کے لئے تصویرگر تک رسائی کی اجازت دیں" + "ایک غیر متوقع نقص واقع ہوا۔ برائے مہربانی دوبارہ کوشش کریں۔" + diff --git a/features/linknewdevice/impl/src/main/res/values-uz/translations.xml b/features/linknewdevice/impl/src/main/res/values-uz/translations.xml new file mode 100644 index 0000000000..d2f3b7a865 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-uz/translations.xml @@ -0,0 +1,38 @@ + + + "QR kodni skanerlash" + "Bu qurilma bilan QR kodni skanerlang" + "Skanerlashga tayyor" + "Hisob provayderingiz %1$s bilan ishlamaydi." + "%1$s qoʻllab-quvvatlanmaydi" + "QR kod qoʻllab-quvvatlanmaydi" + "Boshqa qurilmadan hisobga kirish bekor qilindi." + "Tizimga kirish soʻrovi bekor qilindi" + "Kirish muddati tugagan. Iltimos, qayta urinib koʻring." + "Kirish oʻz vaqtida tugallanmagan" + "%1$sʼni tanlang" + "Yangi qurilmaga xavfsiz ulanish amalga oshirilmadi. Mavjud qurilmalaringiz hali ham xavfsiz va ular haqida qaygʻurishingiz shart emas." + "Endi nima?" + "Agar bu tarmoq muammosi boʻlsa, QR kod bilan qayta kiring" + "Xuddi shu muammoga duch kelsangiz, boshqa wifi tarmogʻini sinang yoki wifi oʻrniga mobil internetdan foydalaning" + "Agar bunisi ishlamasa, oddiy usulda kiring" + "Ulanish xavfsiz emas" + "Boshqa qurilmadan hisobga kirish bekor qilindi." + "Tizimga kirish soʻrovi bekor qilindi" + "Boshqa qurilmadan hisobga kirish bekor qilindi." + "Tizimga kirish rad etildi" + "Kirish muddati tugagan. Iltimos, qayta urinib koʻring." + "Kirish oʻz vaqtida tugallanmagan" + "Boshqa qurilmangiz %s hisobiga QR kod orqali kirishni qoʻllab-quvvatlamaydi. + +Oddiy usulda kiring yoki boshqa qurilma bilan QR kodni skanerlang." + "QR kod qoʻllab-quvvatlanmaydi" + "Hisob provayderingiz %1$s bilan ishlamaydi." + "%1$s qoʻllab-quvvatlanmaydi" + "Narigi qurilmada koʻrsatilgan QR koddan foydalaning." + "Qayta urinib ko\'ring" + "QR kod notoʻgʻri" + "Davom etish uchun %1$s qurilmangiz kamerasidan foydalanishiga ruxsat berishingiz kerak." + "QR kodni skanerlash uchun kameraga ruxsat bering" + "Kutilmagan xatolik yuz berdi. Qayta urining." + diff --git a/features/linknewdevice/impl/src/main/res/values-zh-rTW/translations.xml b/features/linknewdevice/impl/src/main/res/values-zh-rTW/translations.xml new file mode 100644 index 0000000000..9b3cd5ead5 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-zh-rTW/translations.xml @@ -0,0 +1,38 @@ + + + "掃描 QR code" + "使用此裝置掃描 QR code" + "準備掃描" + "您的帳號提供者不支援 %1$s。" + "不支援 %1$s" + "不支援 QR code" + "已在其他裝置上取消登入。" + "已取消登入請求" + "登入已過期。請再試一次。" + "未及時完成登入" + "選取 %1$s" + "無法與新裝置建立安全連線。您現有的裝置仍然安全,您不必擔心它們。" + "現在怎麼辦?" + "嘗試再次使用 QR code 登入以確認不是網路問題" + "如果遇到相同的問題,請嘗試使用其他 wifi 網路或您的行動數據" + "若無法運作,請手動登入" + "連線不安全" + "已在其他裝置上取消登入。" + "已取消登入請求" + "其他裝置拒絕登入。" + "已拒絕登入" + "登入已過期。請再試一次。" + "未及時完成登入" + "您的其他裝置不支援使用 QR cpde 登入 %s。 + +嘗試手動登入,或是使用其他裝置掃描 QR code。" + "不支援 QR code" + "您的帳號提供者不支援 %1$s。" + "不支援 %1$s" + "使用其他裝置上顯示的 QR code。" + "再試一次" + "錯誤的 QR code" + "您必須授予 %1$s 權限以使用裝置相機才能繼續。" + "允許相機權限以掃描 QR code" + "發生意外錯誤。請再試一次。" + diff --git a/features/linknewdevice/impl/src/main/res/values-zh/translations.xml b/features/linknewdevice/impl/src/main/res/values-zh/translations.xml new file mode 100644 index 0000000000..99359cc695 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values-zh/translations.xml @@ -0,0 +1,38 @@ + + + "扫描二维码" + "使用此设备扫描二维码" + "准备进行扫描" + "账户提供方不支持 %1$s." + "不支持 %1$s." + "不支持二维码" + "登录被另一台设备取消" + "登录请求已取消" + "登录已过期. 请重试." + "登录未及时完成" + "选择 %1$s" + "无法与新设备建立安全连接。您现有的设备仍然安全,无需担心。" + "现在怎么办?" + "如果这是网络问题,请尝试使用二维码再次登录" + "如果遇到同样的问题,请尝试使用不同的 WiFi 网络或使用移动数据代替 WiFi" + "如果不起作用,请手动登录" + "连接不安全" + "登录被另一台设备取消" + "登录请求已取消" + "其它设备未接受请求" + "登录被拒绝" + "登录已过期. 请重试." + "登录未及时完成" + "另一个设备不支持使用二维码登录 %s. + +尝试手动或使用另一个设备扫描二维码." + "不支持二维码" + "账户提供方不支持 %1$s." + "不支持 %1$s." + "使用其他设备上显示的二维码。" + "再试一次" + "二维码错误" + "您需要授予 %1$s 使用设备摄像头的权限才能继续。" + "允许摄像头权限以扫描 QR 码" + "发生了意外错误。请再试一次。" + diff --git a/features/linknewdevice/impl/src/main/res/values/localazy.xml b/features/linknewdevice/impl/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..321b168751 --- /dev/null +++ b/features/linknewdevice/impl/src/main/res/values/localazy.xml @@ -0,0 +1,57 @@ + + + "Scan the QR code" + "Open %1$s on a laptop or desktop computer" + "Scan the QR code with this device" + "Ready to scan" + "Open %1$s on a desktop computer to get the QR code" + "The numbers don’t match" + "Enter 2-digit code" + "This will verify that the connection to your other device is secure." + "Enter the number shown on your other device" + "Your account provider does not support %1$s." + "%1$s not supported" + "Your account provider doesn’t support signing into a new device with a QR code." + "QR code not supported" + "The sign in was cancelled on the other device." + "Sign in request cancelled" + "Sign in expired. Please try again." + "The sign in was not completed in time" + "Open %1$s on the other device" + "Select %1$s" + "“Sign in with QR code”" + "Scan the QR code shown here with the other device" + "Open %1$s on the other device" + "Desktop computer" + "Loading QR code…" + "Mobile device" + "What type of device do you want to link?" + "Please try again and make sure that you’ve entered the 2-digit code correctly. If the numbers still don’t match then contact your account provider." + "The numbers don’t match" + "A secure connection could not be made to the new device. Your existing devices are still safe and you don\'t need to worry about them." + "What now?" + "Try signing in again with a QR code in case this was a network problem" + "If you encounter the same problem, try a different wifi network or use your mobile data instead of wifi" + "If that doesn’t work, sign in manually" + "Connection not secure" + "The sign in was cancelled on the other device." + "Sign in request cancelled" + "The sign in was declined on the other device." + "Sign in declined" + "You don’t need to do anything else." + "Your other device is already signed in" + "Sign in expired. Please try again." + "The sign in was not completed in time" + "Your other device does not support signing in to %s with a QR code. + +Try signing in manually, or scan the QR code with another device." + "QR code not supported" + "Your account provider does not support %1$s." + "%1$s not supported" + "Use the QR code shown on the other device." + "Try again" + "Wrong QR code" + "You need to give permission for %1$s to use your device’s camera in order to continue." + "Allow camera access to scan the QR code" + "An unexpected error occurred. Please try again." + diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/DefaultLinkNewDeviceEntryPointTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/DefaultLinkNewDeviceEntryPointTest.kt new file mode 100644 index 0000000000..2957a89495 --- /dev/null +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/DefaultLinkNewDeviceEntryPointTest.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.testing.junit4.util.MainDispatcherRule +import com.google.common.truth.Truth.assertThat +import io.element.android.features.linknewdevice.api.LinkNewDeviceEntryPoint +import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.tests.testutils.lambda.lambdaError +import io.element.android.tests.testutils.node.TestParentNode +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class DefaultLinkNewDeviceEntryPointTest { + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + + @get:Rule + val mainDispatcherRule = MainDispatcherRule() + + @Test + fun `test node creation`() = runTest { + val entryPoint = DefaultLinkNewDeviceEntryPoint() + val client = FakeMatrixClient() + val parentNode = TestParentNode.create { buildContext, plugins -> + LinkNewDeviceFlowNode( + buildContext = buildContext, + plugins = plugins, + sessionCoroutineScope = backgroundScope, + linkNewMobileHandler = LinkNewMobileHandler(client), + linkNewDesktopHandler = LinkNewDesktopHandler(client), + ) + } + val callback: LinkNewDeviceEntryPoint.Callback = object : LinkNewDeviceEntryPoint.Callback { + override fun onDone() = lambdaError() + } + val result = entryPoint.createNode(parentNode, BuildContext.root(null), callback) + assertThat(result).isInstanceOf(LinkNewDeviceFlowNode::class.java) + } +} diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticePresenterTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticePresenterTest.kt new file mode 100644 index 0000000000..b6b9769b64 --- /dev/null +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticePresenterTest.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.desktop + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.permissions.test.FakePermissionsPresenter +import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory +import io.element.android.tests.testutils.test +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class DesktopNoticePresenterTest { + @Test + fun `present - initial state`() = runTest { + val presenter = createPresenter() + presenter.test { + awaitItem().run { + assertThat(cameraPermissionState.permission).isEqualTo("android.permission.POST_NOTIFICATIONS") + assertThat(canContinue).isFalse() + } + } + } + + @Test + fun `present - Continue with camera permissions can continue`() = runTest { + val permissionsPresenter = FakePermissionsPresenter().apply { setPermissionGranted() } + val permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter) + val presenter = createPresenter(permissionsPresenterFactory = permissionsPresenterFactory) + presenter.test { + awaitItem().eventSink(DesktopNoticeEvent.Continue) + assertThat(awaitItem().canContinue).isTrue() + } + } + + @Test + fun `present - Continue with unknown camera permissions opens permission dialog`() = runTest { + val permissionsPresenter = FakePermissionsPresenter() + val permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter) + val presenter = createPresenter(permissionsPresenterFactory = permissionsPresenterFactory) + presenter.test { + awaitItem().eventSink(DesktopNoticeEvent.Continue) + assertThat(awaitItem().cameraPermissionState.showDialog).isTrue() + } + } +} + +private fun createPresenter( + permissionsPresenterFactory: FakePermissionsPresenterFactory = FakePermissionsPresenterFactory(), +) = DesktopNoticePresenter( + permissionsPresenterFactory = permissionsPresenterFactory, +) diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeViewTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeViewTest.kt new file mode 100644 index 0000000000..ac0a129f49 --- /dev/null +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/desktop/DesktopNoticeViewTest.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.desktop + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.linknewdevice.impl.R +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBack +import io.element.android.tests.testutils.pressBackKey +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class DesktopNoticeViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `on back pressed - calls the expected callback`() { + ensureCalledOnce { callback -> + rule.setView( + state = aDesktopNoticeState(), + onBackClicked = callback, + ) + rule.pressBackKey() + } + } + + @Test + fun `on back button clicked - calls the expected callback`() { + ensureCalledOnce { callback -> + rule.setView( + state = aDesktopNoticeState(), + onBackClicked = callback, + ) + rule.pressBack() + } + } + + @Test + fun `when can continue - calls the expected callback`() { + ensureCalledOnce { callback -> + rule.setView( + state = aDesktopNoticeState(canContinue = true), + onReadyToScanClick = callback, + ) + } + } + + @Test + fun `on submit button clicked - emits the Continue event`() { + val eventRecorder = EventsRecorder() + rule.setView( + state = aDesktopNoticeState(eventSink = eventRecorder), + ) + rule.clickOn(R.string.screen_link_new_device_desktop_submit) + eventRecorder.assertSingle(DesktopNoticeEvent.Continue) + } + + private fun AndroidComposeTestRule.setView( + state: DesktopNoticeState, + onBackClicked: () -> Unit = EnsureNeverCalled(), + onReadyToScanClick: () -> Unit = EnsureNeverCalled(), + ) { + setContent { + DesktopNoticeView( + state = state, + onBackClick = onBackClicked, + onReadyToScanClick = onReadyToScanClick, + ) + } + } +} diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorViewTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorViewTest.kt new file mode 100644 index 0000000000..8f44182dd4 --- /dev/null +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/error/ErrorViewTest.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.error + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBackKey +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ErrorViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `on back pressed - calls the onRetry callback`() { + ensureCalledOnce { callback -> + rule.setErrorView( + onRetry = callback + ) + rule.pressBackKey() + } + } + + @Test + fun `on start over button clicked - calls the expected callback`() { + ensureCalledOnce { callback -> + rule.setErrorView( + onRetry = callback + ) + rule.clickOn(CommonStrings.action_start_over) + } + } + + private fun AndroidComposeTestRule.setErrorView( + onRetry: () -> Unit, + errorScreenType: ErrorScreenType = ErrorScreenType.UnknownError, + ) { + setContent { + ErrorView( + errorScreenType = errorScreenType, + onRetry = onRetry, + ) + } + } +} diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberPresenterTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberPresenterTest.kt new file mode 100644 index 0000000000..fe2e11e95c --- /dev/null +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberPresenterTest.kt @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package io.element.android.features.linknewdevice.impl.screens.number + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.linknewdevice.impl.LinkNewMobileHandler +import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileStep +import io.element.android.libraries.matrix.test.AN_EXCEPTION +import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.linknewdevice.FakeCheckCodeSender +import io.element.android.libraries.matrix.test.linknewdevice.FakeLinkMobileHandler +import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value +import io.element.android.tests.testutils.test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class EnterNumberPresenterTest { + @get:Rule + val warmUpRule = WarmUpRule() + + @Test + fun `present - initial state`() = runTest { + createPresenter().test { + val initialState = awaitItem() + assertThat(initialState.number).isEmpty() + assertThat(initialState.sendingCode.isUninitialized()).isTrue() + } + } + + @Test + fun `present - enter numbers`() = runTest { + createPresenter().test { + val initialState = awaitItem() + assertThat(initialState.number).isEmpty() + initialState.eventSink(EnterNumberEvent.UpdateNumber("12")) + val state2 = awaitItem() + assertThat(state2.number).isEqualTo("12") + // Non numeric characters are ignored + state2.eventSink(EnterNumberEvent.UpdateNumber("1a")) + val state3 = awaitItem() + assertThat(state3.number).isEqualTo("1") + } + } + + @Test + fun `present - continue in wrong state generates an error`() = runTest { + createPresenter().test { + val initialState = awaitItem() + initialState.eventSink(EnterNumberEvent.Continue) + val state2 = awaitItem() + assertThat(state2.sendingCode.isFailure()).isTrue() + } + } + + @Test + fun `present - continue when number is not valid invokes the navigator`() = runTest { + val linkMobileHandler = FakeLinkMobileHandler( + startResult = {}, + ) + val validateResult = lambdaRecorder { false } + val checkCodeSender = FakeCheckCodeSender( + validateResult = validateResult, + ) + val matrixClient = FakeMatrixClient( + sessionCoroutineScope = backgroundScope, + createLinkMobileHandlerResult = { Result.success(linkMobileHandler) } + ) + val linkNewMobileHandler = LinkNewMobileHandler(matrixClient) + linkNewMobileHandler.createAndStartNewHandler() + val navigateToWrongNumberErrorLambda = lambdaRecorder { } + val navigator = FakeEnterNumberNavigator( + navigateToWrongNumberErrorLambda = navigateToWrongNumberErrorLambda, + ) + createPresenter( + navigator = navigator, + linkNewMobileHandler = linkNewMobileHandler, + ).test { + val initialState = awaitItem() + linkMobileHandler.emitStep( + LinkMobileStep.QrScanned(checkCodeSender) + ) + runCurrent() + initialState.eventSink(EnterNumberEvent.UpdateNumber("88")) + skipItems(1) + initialState.eventSink(EnterNumberEvent.Continue) + skipItems(1) + val finalState = awaitItem() + assertThat(finalState.sendingCode.isLoading()).isTrue() + advanceUntilIdle() + validateResult.assertions().isCalledOnce().with(value(88.toUByte())) + navigateToWrongNumberErrorLambda.assertions().isCalledOnce() + } + } + + @Test + fun `present - continue when the number is valid but sending fails`() = runTest { + val linkMobileHandler = FakeLinkMobileHandler( + startResult = {}, + ) + val validateResult = lambdaRecorder { true } + val sendResult = lambdaRecorder> { Result.failure(AN_EXCEPTION) } + val checkCodeSender = FakeCheckCodeSender( + validateResult = validateResult, + sendResult = sendResult, + ) + val matrixClient = FakeMatrixClient( + sessionCoroutineScope = backgroundScope, + createLinkMobileHandlerResult = { Result.success(linkMobileHandler) } + ) + val linkNewMobileHandler = LinkNewMobileHandler(matrixClient) + linkNewMobileHandler.createAndStartNewHandler() + createPresenter( + linkNewMobileHandler = linkNewMobileHandler, + ).test { + val initialState = awaitItem() + linkMobileHandler.emitStep( + LinkMobileStep.QrScanned(checkCodeSender) + ) + runCurrent() + initialState.eventSink(EnterNumberEvent.UpdateNumber("88")) + skipItems(1) + initialState.eventSink(EnterNumberEvent.Continue) + skipItems(1) + val loadingState = awaitItem() + assertThat(loadingState.sendingCode.isLoading()).isTrue() + val finalState = awaitItem() + assertThat(finalState.sendingCode.isFailure()).isTrue() + validateResult.assertions().isCalledOnce().with(value(88.toUByte())) + sendResult.assertions().isCalledOnce().with(value(88.toUByte())) + } + } + + @Test + fun `present - continue when the number is valid and sending is successful`() = runTest { + val linkMobileHandler = FakeLinkMobileHandler( + startResult = {}, + ) + val validateResult = lambdaRecorder { true } + val sendResult = lambdaRecorder> { Result.success(Unit) } + val checkCodeSender = FakeCheckCodeSender( + validateResult = validateResult, + sendResult = sendResult, + ) + val matrixClient = FakeMatrixClient( + sessionCoroutineScope = backgroundScope, + createLinkMobileHandlerResult = { Result.success(linkMobileHandler) } + ) + val linkNewMobileHandler = LinkNewMobileHandler(matrixClient) + linkNewMobileHandler.createAndStartNewHandler() + createPresenter( + linkNewMobileHandler = linkNewMobileHandler, + ).test { + val initialState = awaitItem() + linkMobileHandler.emitStep( + LinkMobileStep.QrScanned(checkCodeSender) + ) + runCurrent() + initialState.eventSink(EnterNumberEvent.UpdateNumber("88")) + skipItems(1) + initialState.eventSink(EnterNumberEvent.Continue) + skipItems(1) + val loadingState = awaitItem() + assertThat(loadingState.sendingCode.isLoading()).isTrue() + expectNoEvents() + advanceUntilIdle() + validateResult.assertions().isCalledOnce().with(value(88.toUByte())) + sendResult.assertions().isCalledOnce().with(value(88.toUByte())) + } + } + + private fun createPresenter( + navigator: EnterNumberNavigator = FakeEnterNumberNavigator(), + linkNewMobileHandler: LinkNewMobileHandler = LinkNewMobileHandler(FakeMatrixClient()), + ) = EnterNumberPresenter( + navigator = navigator, + linkNewMobileHandler = linkNewMobileHandler, + ) +} diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberStateTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberStateTest.kt new file mode 100644 index 0000000000..e7466a1b21 --- /dev/null +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberStateTest.kt @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.number + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.linknewdevice.impl.screens.number.model.Digit +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.test.AN_EXCEPTION +import org.junit.Test + +class EnterNumberStateTest { + @Test + fun `isContinueButtonEnabled is false if number is not complete`() { + val sut = aEnterNumberState( + number = "", + sendingCode = AsyncAction.Uninitialized, + ) + assertThat(sut.copy(number = "1").isContinueButtonEnabled).isFalse() + } + + @Test + fun `isContinueButtonEnabled is true if number is complete`() { + val sut = aEnterNumberState( + number = "12", + sendingCode = AsyncAction.Uninitialized, + ) + assertThat(sut.isContinueButtonEnabled).isTrue() + } + + @Test + fun `isContinueButtonEnabled is false if number is complete and sending is loading`() { + val sut = aEnterNumberState( + number = "12", + sendingCode = AsyncAction.Loading, + ) + assertThat(sut.isContinueButtonEnabled).isFalse() + } + + @Test + fun `isContinueButtonEnabled is true if number is complete and sending is not loading`() { + listOf( + AsyncAction.Uninitialized, + AsyncAction.Failure(AN_EXCEPTION), + AsyncAction.Success(Unit), + ).forEach { action -> + val sut = aEnterNumberState( + number = "12", + sendingCode = action, + ) + assertThat(sut.isContinueButtonEnabled).isTrue() + } + } + + @Test + fun `numberEntry is computed from number - case empty`() { + val sut = aEnterNumberState( + number = "", + ) + assertThat(sut.numberEntry.size).isEqualTo(2) + assertThat(sut.numberEntry.digits).containsExactly( + Digit.Empty, + Digit.Empty, + ) + } + + @Test + fun `numberEntry is computed from number - case half filled`() { + val sut = aEnterNumberState( + number = "1", + ) + assertThat(sut.numberEntry.size).isEqualTo(2) + assertThat(sut.numberEntry.digits).containsExactly( + Digit.Filled('1'), + Digit.Empty, + ) + } + + @Test + fun `numberEntry is computed from number - case filled`() { + val sut = aEnterNumberState( + number = "12", + ) + assertThat(sut.numberEntry.size).isEqualTo(2) + assertThat(sut.numberEntry.digits).containsExactly( + Digit.Filled('1'), + Digit.Filled('2'), + ) + } +} diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberViewTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberViewTest.kt new file mode 100644 index 0000000000..20e1d898dd --- /dev/null +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/EnterNumberViewTest.kt @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.number + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBack +import io.element.android.tests.testutils.pressBackKey +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class EnterNumberViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `on back pressed - calls the expected callback`() { + ensureCalledOnce { callback -> + rule.setView( + state = aEnterNumberState(), + onBackClicked = callback, + ) + rule.pressBackKey() + } + } + + @Test + fun `on back button clicked - calls the expected callback`() { + ensureCalledOnce { callback -> + rule.setView( + state = aEnterNumberState(), + onBackClicked = callback, + ) + rule.pressBack() + } + } + + @Test + fun `on continue button clicked - emits the Continue event`() { + val eventRecorder = EventsRecorder() + rule.setView( + state = aEnterNumberState( + number = "12", + eventSink = eventRecorder, + ), + ) + rule.clickOn(CommonStrings.action_continue) + eventRecorder.assertSingle(EnterNumberEvent.Continue) + } + + @Test + fun `when the number is not complete, continue button is disabled`() { + val eventRecorder = EventsRecorder(expectEvents = false) + rule.setView( + state = aEnterNumberState( + number = "1", + eventSink = eventRecorder, + ), + ) + val continueStr = rule.activity.getString(CommonStrings.action_continue) + rule.onNodeWithText(continueStr).assertIsNotEnabled() + } + + private fun AndroidComposeTestRule.setView( + state: EnterNumberState, + onBackClicked: () -> Unit = EnsureNeverCalled(), + ) { + setContent { + EnterNumberView( + state = state, + onBackClick = onBackClicked, + ) + } + } +} diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/FakeEnterNumberNavigator.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/FakeEnterNumberNavigator.kt new file mode 100644 index 0000000000..a96ab7fe30 --- /dev/null +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/number/FakeEnterNumberNavigator.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.number + +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeEnterNumberNavigator( + private val navigateToWrongNumberErrorLambda: () -> Unit = { lambdaError() }, +) : EnterNumberNavigator { + override fun navigateToWrongNumberError() { + navigateToWrongNumberErrorLambda() + } +} diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/qrcode/ShowQrCodeViewTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/qrcode/ShowQrCodeViewTest.kt new file mode 100644 index 0000000000..c6c89ba818 --- /dev/null +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/qrcode/ShowQrCodeViewTest.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.qrcode + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBackKey +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ShowQrCodeViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `on back pressed - calls the expected callback`() { + ensureCalledOnce { callback -> + rule.setView( + onBackClick = callback + ) + rule.pressBackKey() + } + } + + private fun AndroidComposeTestRule.setView( + onBackClick: () -> Unit = EnsureNeverCalled(), + ) { + setContent { + ShowQrCodeView( + data = "DATA", + onBackClick = onBackClick, + ) + } + } +} diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootPresenterTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootPresenterTest.kt new file mode 100644 index 0000000000..88a68786f3 --- /dev/null +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootPresenterTest.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.root + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.linknewdevice.impl.LinkNewMobileHandler +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.test.AN_EXCEPTION +import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.linknewdevice.FakeLinkMobileHandler +import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.test +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class LinkNewDeviceRootPresenterTest { + @get:Rule + val warmUpRule = WarmUpRule() + + @Test + fun `present - initial state`() = runTest { + val matrixClient = FakeMatrixClient( + canLinkNewDeviceResult = { Result.success(true) }, + ) + createPresenter( + matrixClient = matrixClient, + ).test { + val initialState = awaitItem() + assertThat(initialState.isSupported.isUninitialized()).isTrue() + assertThat(awaitItem().isSupported.dataOrNull()).isTrue() + } + } + + @Test + fun `present - new login device not supported`() = runTest { + val matrixClient = FakeMatrixClient( + canLinkNewDeviceResult = { Result.success(false) }, + ) + createPresenter( + matrixClient = matrixClient, + ).test { + val initialState = awaitItem() + assertThat(initialState.isSupported.isUninitialized()).isTrue() + assertThat(awaitItem().isSupported.dataOrNull()).isFalse() + } + } + + @Test + fun `present - error`() = runTest { + val matrixClient = FakeMatrixClient( + canLinkNewDeviceResult = { Result.failure(AN_EXCEPTION) }, + ) + createPresenter( + matrixClient = matrixClient, + ).test { + val initialState = awaitItem() + assertThat(initialState.isSupported.isUninitialized()).isTrue() + assertThat(awaitItem().isSupported.isFailure()).isTrue() + } + } + + @Test + fun `present - link new mobile device`() = runTest { + val linkMobileHandler = FakeLinkMobileHandler( + startResult = {}, + ) + val matrixClient = FakeMatrixClient( + canLinkNewDeviceResult = { Result.success(true) }, + sessionCoroutineScope = backgroundScope, + createLinkMobileHandlerResult = { Result.success(linkMobileHandler) } + ) + createPresenter( + matrixClient = matrixClient, + ).test { + skipItems(1) + val initialState = awaitItem() + assertThat(initialState.isSupported.dataOrNull()).isTrue() + initialState.eventSink(LinkNewDeviceRootEvent.LinkMobileDevice) + val loadingState = awaitItem() + assertThat(loadingState.qrCodeData.isLoading()).isTrue() + } + } + + private fun createPresenter( + matrixClient: MatrixClient = FakeMatrixClient(), + linkNewMobileHandler: LinkNewMobileHandler = LinkNewMobileHandler(matrixClient), + ) = LinkNewDeviceRootPresenter( + matrixClient = matrixClient, + linkNewMobileHandler = linkNewMobileHandler, + ) +} diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootViewTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootViewTest.kt new file mode 100644 index 0000000000..e352debfb0 --- /dev/null +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/root/LinkNewDeviceRootViewTest.kt @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.root + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.linknewdevice.impl.R +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBackKey +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class LinkNewDeviceRootViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `on back pressed - calls the onRetry callback`() { + val eventRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { callback -> + rule.setLinkNewDeviceRootView( + state = aLinkNewDeviceRootState( + eventSink = eventRecorder, + ), + onBackClick = callback + ) + rule.pressBackKey() + } + } + + @Test + fun `link desktop button clicked - calls the expected callback`() { + val eventRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { callback -> + rule.setLinkNewDeviceRootView( + state = aLinkNewDeviceRootState( + isSupported = AsyncData.Success(true), + eventSink = eventRecorder, + ), + onLinkDesktopDeviceClick = callback, + ) + rule.clickOn(R.string.screen_link_new_device_root_desktop_computer) + } + } + + @Test + fun `link mobile button clicked - emits the expected event`() { + val eventRecorder = EventsRecorder() + rule.setLinkNewDeviceRootView( + state = aLinkNewDeviceRootState( + isSupported = AsyncData.Success(true), + eventSink = eventRecorder, + ) + ) + rule.clickOn(R.string.screen_link_new_device_root_mobile_device) + eventRecorder.assertSingle(LinkNewDeviceRootEvent.LinkMobileDevice) + } + + @Test + fun `not supported - dismiss click - invokes the expected callback`() { + val eventRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { callback -> + rule.setLinkNewDeviceRootView( + state = aLinkNewDeviceRootState( + isSupported = AsyncData.Success(false), + eventSink = eventRecorder, + ), + onBackClick = callback, + ) + rule.clickOn(CommonStrings.action_dismiss) + } + } + + private fun AndroidComposeTestRule.setLinkNewDeviceRootView( + state: LinkNewDeviceRootState = aLinkNewDeviceRootState(), + onBackClick: () -> Unit = EnsureNeverCalled(), + onLinkDesktopDeviceClick: () -> Unit = EnsureNeverCalled(), + ) { + setContent { + LinkNewDeviceRootView( + state = state, + onBackClick = onBackClick, + onLinkDesktopDeviceClick = onLinkDesktopDeviceClick, + ) + } + } +} diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodePresenterTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodePresenterTest.kt new file mode 100644 index 0000000000..50c3ce767b --- /dev/null +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodePresenterTest.kt @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package io.element.android.features.linknewdevice.impl.screens.scan + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.linknewdevice.impl.LinkNewDesktopHandler +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeDecodeException +import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopStep +import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.QR_CODE_DATA_RECIPROCATE +import io.element.android.libraries.matrix.test.linknewdevice.FakeLinkDesktopHandler +import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value +import io.element.android.tests.testutils.test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class ScanQrCodePresenterTest { + @get:Rule + val warmUpRule = WarmUpRule() + + @Test + fun `present - initial state`() = runTest { + val matrixClient = FakeMatrixClient( + createLinkDesktopHandlerResult = { Result.success(FakeLinkDesktopHandler()) } + ) + createPresenter( + matrixClient = matrixClient, + ).test { + val initialState = awaitItem() + assertThat(initialState.scanAction.isLoading()).isTrue() + } + } + + @Test + fun `present - handle scanned event - success`() = runTest { + val handleScannedQrCodeResult = lambdaRecorder { } + val matrixClient = FakeMatrixClient( + sessionCoroutineScope = backgroundScope, + createLinkDesktopHandlerResult = { + Result.success( + FakeLinkDesktopHandler( + handleScannedQrCodeResult = handleScannedQrCodeResult, + ) + ) + } + ) + createPresenter( + matrixClient = matrixClient, + ).test { + val initialState = awaitItem() + assertThat(initialState.scanAction.isLoading()).isTrue() + initialState.eventSink(ScanQrCodeEvent.QrCodeScanned(QR_CODE_DATA_RECIPROCATE)) + val scannedState = awaitItem() + assertThat(scannedState.scanAction.isSuccess()).isTrue() + runCurrent() + handleScannedQrCodeResult.assertions().isCalledOnce().with(value(QR_CODE_DATA_RECIPROCATE)) + } + } + + @Test + fun `present - handle scanned event - failure`() = runTest { + val handleScannedQrCodeResult = lambdaRecorder { } + val handler = FakeLinkDesktopHandler( + handleScannedQrCodeResult = handleScannedQrCodeResult, + ) + val matrixClient = FakeMatrixClient( + sessionCoroutineScope = backgroundScope, + createLinkDesktopHandlerResult = { + Result.success(handler) + } + ) + createPresenter( + matrixClient = matrixClient, + ).test { + val initialState = awaitItem() + assertThat(initialState.scanAction.isLoading()).isTrue() + initialState.eventSink(ScanQrCodeEvent.QrCodeScanned(QR_CODE_DATA_RECIPROCATE)) + val scannedState = awaitItem() + assertThat(scannedState.scanAction.isSuccess()).isTrue() + handler.emitStep(LinkDesktopStep.InvalidQrCode(QrCodeDecodeException.Crypto("Invalid QR Code"))) + skipItems(1) + val errorState = awaitItem() + assertThat(errorState.scanAction.isFailure()).isTrue() + handleScannedQrCodeResult.assertions().isCalledOnce().with(value(QR_CODE_DATA_RECIPROCATE)) + // Reset by trying again + errorState.eventSink(ScanQrCodeEvent.TryAgain) + val resetState = awaitItem() + assertThat(resetState.scanAction.isLoading()).isTrue() + } + } +} + +private fun createPresenter( + matrixClient: MatrixClient, +) = ScanQrCodePresenter( + linkNewDesktopHandler = LinkNewDesktopHandler(matrixClient), +) diff --git a/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeViewTest.kt b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeViewTest.kt new file mode 100644 index 0000000000..fcc3afeb7d --- /dev/null +++ b/features/linknewdevice/impl/src/test/kotlin/io/element/android/features/linknewdevice/impl/screens/scan/ScanQrCodeViewTest.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.linknewdevice.impl.screens.scan + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.test.AN_EXCEPTION +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBackKey +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ScanQrCodeViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `on back pressed - calls the expected callback`() { + val eventRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { callback -> + rule.setView( + state = aScanQrCodeState( + eventSink = eventRecorder, + ), + onBackClick = callback + ) + rule.pressBackKey() + } + } + + @Test + fun `try again button clicked - emits the expected event`() { + val eventRecorder = EventsRecorder() + rule.setView( + state = aScanQrCodeState( + scanAction = AsyncAction.Failure(AN_EXCEPTION), + eventSink = eventRecorder, + ) + ) + rule.clickOn(CommonStrings.action_try_again) + eventRecorder.assertSingle(ScanQrCodeEvent.TryAgain) + } + + private fun AndroidComposeTestRule.setView( + state: ScanQrCodeState = aScanQrCodeState(), + onBackClick: () -> Unit = EnsureNeverCalled(), + ) { + setContent { + ScanQrCodeView( + state = state, + onBackClick = onBackClick, + ) + } + } +} diff --git a/features/linknewdevice/test/build.gradle.kts b/features/linknewdevice/test/build.gradle.kts new file mode 100644 index 0000000000..388612f920 --- /dev/null +++ b/features/linknewdevice/test/build.gradle.kts @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-compose-library") +} + +android { + namespace = "io.element.android.features.linknewdevice.test" +} + +dependencies { + implementation(projects.features.linknewdevice.api) + implementation(projects.tests.testutils) +} diff --git a/features/location/api/build.gradle.kts b/features/location/api/build.gradle.kts index e4d5c4e886..ab85e37594 100644 --- a/features/location/api/build.gradle.kts +++ b/features/location/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/Location.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/Location.kt index 0d720877a1..7593867207 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/Location.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/Location.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/LocationService.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/LocationService.kt index 847bdf3921..22049c9548 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/LocationService.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/LocationService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/SendLocationEntryPoint.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/SendLocationEntryPoint.kt index 1528e6cb14..48816b2bb4 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/SendLocationEntryPoint.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/SendLocationEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,8 +19,9 @@ import io.element.android.libraries.matrix.api.timeline.Timeline * Allows a user to share a location message within a room. */ interface SendLocationEntryPoint : FeatureEntryPoint { - fun builder(timelineMode: Timeline.Mode): Builder - interface Builder { - fun build(parentNode: Node, buildContext: BuildContext): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + timelineMode: Timeline.Mode, + ): Node } diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/ShowLocationEntryPoint.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/ShowLocationEntryPoint.kt index 7ae28a1e92..03693a7d68 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/ShowLocationEntryPoint.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/ShowLocationEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,14 @@ import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.architecture.NodeInputs interface ShowLocationEntryPoint : FeatureEntryPoint { - data class Inputs(val location: Location, val description: String?) : NodeInputs + data class Inputs( + val location: Location, + val description: String?, + ) : NodeInputs - fun createNode(parentNode: Node, buildContext: BuildContext, inputs: Inputs): Node + fun createNode( + parentNode: Node, + buildContext: BuildContext, + inputs: Inputs, + ): Node } 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 382a65ae47..c58cadd66b 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt index eb8d0ea1b7..839cda0237 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt index db75b5e30a..d119dc7eb2 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 40112cea4f..48c78efc3c 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapPlaceholder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapPlaceholder.kt index 6ea4e9f9de..81b80c8dc3 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapPlaceholder.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapPlaceholder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapUrlBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapUrlBuilder.kt index 533ecf8d2d..6636976828 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapUrlBuilder.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapUrlBuilder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/TileServerStyleUriBuilder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/TileServerStyleUriBuilder.kt index 051b2448d4..17b620eec9 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/TileServerStyleUriBuilder.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/TileServerStyleUriBuilder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/api/src/test/kotlin/io/element/android/features/location/api/LocationKtTest.kt b/features/location/api/src/test/kotlin/io/element/android/features/location/api/LocationKtTest.kt index 6718f6614f..d1ad40176e 100644 --- a/features/location/api/src/test/kotlin/io/element/android/features/location/api/LocationKtTest.kt +++ b/features/location/api/src/test/kotlin/io/element/android/features/location/api/LocationKtTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilderTest.kt b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilderTest.kt index ba44426a70..65c0acd88d 100644 --- a/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilderTest.kt +++ b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerStaticMapUrlBuilderTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt index dc53765836..d5c6521258 100644 --- a/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt +++ b/features/location/api/src/test/kotlin/io/element/android/features/location/api/internal/MapTilerTileServerStyleUriBuilderTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/build.gradle.kts b/features/location/impl/build.gradle.kts index 43f6bba0d0..bd41c7ecdf 100644 --- a/features/location/impl/build.gradle.kts +++ b/features/location/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/main/AndroidManifest.xml b/features/location/impl/src/main/AndroidManifest.xml index 662651e570..ae728c09e1 100644 --- a/features/location/impl/src/main/AndroidManifest.xml +++ b/features/location/impl/src/main/AndroidManifest.xml @@ -1,7 +1,8 @@ diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/DefaultLocationService.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/DefaultLocationService.kt index 5be8e7c093..9f2c3fa70b 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/DefaultLocationService.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/DefaultLocationService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,12 +10,10 @@ package io.element.android.features.location.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.location.api.BuildConfig import io.element.android.features.location.api.LocationService @ContributesBinding(AppScope::class) -@Inject class DefaultLocationService : LocationService { override fun isServiceAvailable(): Boolean { return BuildConfig.MAPTILER_API_KEY.isNotEmpty() diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/MapDefaults.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/MapDefaults.kt index 153adad343..d01f3e7dd2 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/MapDefaults.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/MapDefaults.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/PermissionDeniedDialog.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/PermissionDeniedDialog.kt index 5b925e64ae..6817f579e5 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/PermissionDeniedDialog.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/PermissionDeniedDialog.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/PermissionRationaleDialog.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/PermissionRationaleDialog.kt index 8b5d3f06a3..7aef07e32b 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/PermissionRationaleDialog.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/PermissionRationaleDialog.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActions.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActions.kt index c879635052..0284c25a0a 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActions.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,7 +15,6 @@ import androidx.annotation.VisibleForTesting import androidx.core.net.toUri import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.location.api.Location import io.element.android.libraries.androidutils.system.openAppSettingsPage import io.element.android.libraries.core.extensions.runCatchingExceptions @@ -23,7 +23,6 @@ import timber.log.Timber import java.util.Locale @ContributesBinding(AppScope::class) -@Inject class AndroidLocationActions( @ApplicationContext private val context: Context ) : LocationActions { diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/actions/LocationActions.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/actions/LocationActions.kt index d4edeb8abb..cd9efbd261 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/actions/LocationActions.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/actions/LocationActions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/DefaultPermissionsPresenter.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/DefaultPermissionsPresenter.kt index 0ef520aadb..1aa2e1269d 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/DefaultPermissionsPresenter.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/DefaultPermissionsPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -33,7 +34,7 @@ class DefaultPermissionsPresenter( override fun present(): PermissionsState { val multiplePermissionsState = rememberMultiplePermissionsState(permissions = permissions) - fun handleEvents(event: PermissionsEvents) { + fun handleEvent(event: PermissionsEvents) { when (event) { PermissionsEvents.RequestPermissions -> multiplePermissionsState.launchMultiplePermissionRequest() } @@ -46,7 +47,7 @@ class DefaultPermissionsPresenter( else -> PermissionsState.Permissions.NoneGranted }, shouldShowRationale = multiplePermissionsState.shouldShowRationale, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsEvents.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsEvents.kt index 951742aee4..a4b5ef1228 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsEvents.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsPresenter.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsPresenter.kt index 82e54ba977..e7062264c9 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsPresenter.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsState.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsState.kt index 1be342297b..91191280d0 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsState.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/permissions/PermissionsState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/ui/LocationFloatingActionButton.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/ui/LocationFloatingActionButton.kt index 7b189a6e1f..773544375f 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/ui/LocationFloatingActionButton.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/common/ui/LocationFloatingActionButton.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/DefaultSendLocationEntryPoint.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/DefaultSendLocationEntryPoint.kt index c42be68bf1..56399f7e9d 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/DefaultSendLocationEntryPoint.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/DefaultSendLocationEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,24 +12,20 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.location.api.SendLocationEntryPoint import io.element.android.libraries.architecture.createNode import io.element.android.libraries.matrix.api.timeline.Timeline @ContributesBinding(AppScope::class) -@Inject class DefaultSendLocationEntryPoint : SendLocationEntryPoint { - override fun builder(timelineMode: Timeline.Mode): SendLocationEntryPoint.Builder { - return Builder(timelineMode) - } - - class Builder(private val timelineMode: Timeline.Mode) : SendLocationEntryPoint.Builder { - override fun build(parentNode: Node, buildContext: BuildContext): Node { - return parentNode.createNode( - buildContext = buildContext, - plugins = listOf(SendLocationNode.Inputs(timelineMode)) - ) - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + timelineMode: Timeline.Mode, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf(SendLocationNode.Inputs(timelineMode)) + ) } } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationEvents.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationEvents.kt index d8ffb3eff4..0d266eefc7 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationEvents.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationNode.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationNode.kt index 8fff96e7b6..2184b52b44 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationNode.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 9914920ac3..b753820e55 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -78,7 +79,7 @@ class SendLocationPresenter( } } - fun handleEvents(event: SendLocationEvents) { + fun handleEvent(event: SendLocationEvents) { when (event) { is SendLocationEvents.SendLocation -> scope.launch { sendLocation(event, mode) @@ -103,7 +104,7 @@ class SendLocationPresenter( mode = mode, hasLocationPermission = permissionsState.isAnyGranted, appName = appName, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationState.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationState.kt index fc2f1e91a8..4ca84c47a5 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationState.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationStateProvider.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationStateProvider.kt index 858098dee9..238201cbec 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationStateProvider.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 d6882ced98..452a4aa8ae 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/DefaultShowLocationEntryPoint.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/DefaultShowLocationEntryPoint.kt index d226a01ede..7d1f5f1ef4 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/DefaultShowLocationEntryPoint.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/DefaultShowLocationEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,14 +12,16 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.location.api.ShowLocationEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultShowLocationEntryPoint : ShowLocationEntryPoint { - override fun createNode(parentNode: Node, buildContext: BuildContext, inputs: ShowLocationEntryPoint.Inputs): Node { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + inputs: ShowLocationEntryPoint.Inputs, + ): Node { return parentNode.createNode(buildContext, listOf(inputs)) } } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationEvents.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationEvents.kt index a1464aeaf9..12f368fa11 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationEvents.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationNode.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationNode.kt index d5977fa426..86d7741752 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationNode.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt index 7929843571..3dcccef886 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -56,7 +57,7 @@ class ShowLocationPresenter( } } - fun handleEvents(event: ShowLocationEvents) { + fun handleEvent(event: ShowLocationEvents) { when (event) { ShowLocationEvents.Share -> locationActions.share(location, description) is ShowLocationEvents.TrackMyLocation -> { @@ -86,7 +87,7 @@ class ShowLocationPresenter( hasLocationPermission = permissionsState.isAnyGranted, isTrackMyLocation = isTrackMyLocation, appName = appName, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } } diff --git a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationState.kt b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationState.kt index cee52c126b..96635d6df8 100644 --- a/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationState.kt +++ b/features/location/impl/src/main/kotlin/io/element/android/features/location/impl/show/ShowLocationState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 98b700356b..7d03a1ebb2 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 2a6063e9f3..6bc9e27bc6 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/DefaultLocationServiceTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/DefaultLocationServiceTest.kt index fd687dd938..b0e899730b 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/DefaultLocationServiceTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/DefaultLocationServiceTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/PermissionsStateFactory.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/PermissionsStateFactory.kt index 01f4f522f6..a4352444dc 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/PermissionsStateFactory.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/PermissionsStateFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActionsTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActionsTest.kt index 3d8ab97504..f82635e562 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActionsTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActionsTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/FakeLocationActions.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/FakeLocationActions.kt index 488408eaf2..94dc972213 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/FakeLocationActions.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/FakeLocationActions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/permissions/FakePermissionsPresenter.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/permissions/FakePermissionsPresenter.kt index 7351dd71f4..94d909a7ef 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/permissions/FakePermissionsPresenter.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/permissions/FakePermissionsPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -19,7 +20,7 @@ class FakePermissionsPresenter : PermissionsPresenter { private var state = PermissionsState( permissions = PermissionsState.Permissions.NoneGranted, shouldShowRationale = false, - eventSink = ::handleEvent + eventSink = ::handleEvent, ) set(value) { field = value.copy(eventSink = ::handleEvent) diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/DefaultSendLocationEntryPointTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/DefaultSendLocationEntryPointTest.kt index 9be79c7092..a90afd5a9d 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/DefaultSendLocationEntryPointTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/DefaultSendLocationEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -47,8 +48,11 @@ class DefaultSendLocationEntryPointTest { ) } val timelineMode = Timeline.Mode.Live - val result = entryPoint.builder(timelineMode) - .build(parentNode, BuildContext.root(null)) + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + timelineMode = timelineMode, + ) assertThat(result).isInstanceOf(SendLocationNode::class.java) assertThat(result.plugins).contains(SendLocationNode.Inputs(timelineMode)) } 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 4847942d67..23ab3847cd 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/DefaultShowLocationEntryPointTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/DefaultShowLocationEntryPointTest.kt index d31ef0c0e4..a49b887a42 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/DefaultShowLocationEntryPointTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/DefaultShowLocationEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -48,8 +49,8 @@ class DefaultShowLocationEntryPointTest { description = "My location", ) val result = entryPoint.createNode( - parentNode, - BuildContext.root(null), + parentNode = parentNode, + buildContext = BuildContext.root(null), inputs = inputs, ) assertThat(result).isInstanceOf(ShowLocationNode::class.java) 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 cc53badbb2..df22863c7c 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationViewTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationViewTest.kt index 2125fff4db..2245360bb2 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationViewTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/show/ShowLocationViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/test/build.gradle.kts b/features/location/test/build.gradle.kts index 024ae2c303..f84e8ba772 100644 --- a/features/location/test/build.gradle.kts +++ b/features/location/test/build.gradle.kts @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,5 +15,8 @@ android { } dependencies { - implementation(projects.features.location.api) + api(projects.features.location.api) + implementation(projects.libraries.matrix.api) + implementation(libs.appyx.core) + implementation(projects.tests.testutils) } diff --git a/features/location/test/src/main/kotlin/io/element/android/features/location/test/FakeLocationService.kt b/features/location/test/src/main/kotlin/io/element/android/features/location/test/FakeLocationService.kt index 69a3331ffb..f961a81c9b 100644 --- a/features/location/test/src/main/kotlin/io/element/android/features/location/test/FakeLocationService.kt +++ b/features/location/test/src/main/kotlin/io/element/android/features/location/test/FakeLocationService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/location/test/src/main/kotlin/io/element/android/features/location/test/FakeSendLocationEntryPoint.kt b/features/location/test/src/main/kotlin/io/element/android/features/location/test/FakeSendLocationEntryPoint.kt new file mode 100644 index 0000000000..2a1741e6c8 --- /dev/null +++ b/features/location/test/src/main/kotlin/io/element/android/features/location/test/FakeSendLocationEntryPoint.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.location.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.location.api.SendLocationEntryPoint +import io.element.android.libraries.matrix.api.timeline.Timeline +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeSendLocationEntryPoint : SendLocationEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + timelineMode: Timeline.Mode, + ): Node = lambdaError() +} diff --git a/features/location/test/src/main/kotlin/io/element/android/features/location/test/FakeShowLocationEntryPoint.kt b/features/location/test/src/main/kotlin/io/element/android/features/location/test/FakeShowLocationEntryPoint.kt new file mode 100644 index 0000000000..b6a94091d9 --- /dev/null +++ b/features/location/test/src/main/kotlin/io/element/android/features/location/test/FakeShowLocationEntryPoint.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.location.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.location.api.ShowLocationEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeShowLocationEntryPoint : ShowLocationEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + inputs: ShowLocationEntryPoint.Inputs, + ): Node = lambdaError() +} diff --git a/features/lockscreen/api/build.gradle.kts b/features/lockscreen/api/build.gradle.kts index 4419b7a9fa..a09c1a4d40 100644 --- a/features/lockscreen/api/build.gradle.kts +++ b/features/lockscreen/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenEntryPoint.kt b/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenEntryPoint.kt index e9ba05ba74..aa891ada4f 100644 --- a/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenEntryPoint.kt +++ b/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,13 +16,14 @@ import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint interface LockScreenEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext, navTarget: Target): NodeBuilder - fun pinUnlockIntent(context: Context): Intent + fun createNode( + parentNode: Node, + buildContext: BuildContext, + navTarget: Target, + callback: Callback, + ): Node - interface NodeBuilder { - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } + fun pinUnlockIntent(context: Context): Intent interface Callback : Plugin { fun onSetupDone() diff --git a/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenLockState.kt b/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenLockState.kt index 5650122ee0..1a63ebc640 100644 --- a/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenLockState.kt +++ b/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenLockState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenService.kt b/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenService.kt index 4919442e28..bd644e69e9 100644 --- a/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenService.kt +++ b/features/lockscreen/api/src/main/kotlin/io/element/android/features/lockscreen/api/LockScreenService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -35,12 +36,6 @@ interface LockScreenService { fun isPinSetup(): Flow } -/** - * Check if the app is currently locked. - */ -val LockScreenService.isLocked: Boolean - get() = lockState.value == LockScreenLockState.Locked - /** * Makes sure the secure flag is set on the activity if the pin is setup. * @param activity the activity to set the flag on. diff --git a/features/lockscreen/impl/build.gradle.kts b/features/lockscreen/impl/build.gradle.kts index 998b763804..63aec590de 100644 --- a/features/lockscreen/impl/build.gradle.kts +++ b/features/lockscreen/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/AndroidManifest.xml b/features/lockscreen/impl/src/main/AndroidManifest.xml index 2b4ccb4296..13b1262f27 100644 --- a/features/lockscreen/impl/src/main/AndroidManifest.xml +++ b/features/lockscreen/impl/src/main/AndroidManifest.xml @@ -1,7 +1,8 @@ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenEntryPoint.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenEntryPoint.kt index 1155a6fdcd..ec8fdc6d8b 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenEntryPoint.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,34 +14,30 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.lockscreen.api.LockScreenEntryPoint import io.element.android.features.lockscreen.impl.unlock.activity.PinUnlockActivity import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultLockScreenEntryPoint : LockScreenEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext, navTarget: LockScreenEntryPoint.Target): LockScreenEntryPoint.NodeBuilder { - val callbacks = mutableListOf() - - return object : LockScreenEntryPoint.NodeBuilder { - override fun callback(callback: LockScreenEntryPoint.Callback): LockScreenEntryPoint.NodeBuilder { - callbacks += callback - return this - } - - override fun build(): Node { - val inputs = LockScreenFlowNode.Inputs( + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + navTarget: LockScreenEntryPoint.Target, + callback: LockScreenEntryPoint.Callback, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf( + LockScreenFlowNode.Inputs( when (navTarget) { LockScreenEntryPoint.Target.Setup -> LockScreenFlowNode.NavTarget.Setup LockScreenEntryPoint.Target.Settings -> LockScreenFlowNode.NavTarget.Settings } - ) - val plugins = listOf(inputs) + callbacks - return parentNode.createNode(buildContext, plugins) - } - } + ), + callback, + ) + ) } override fun pinUnlockIntent(context: Context): Intent { diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt index c1a97dadad..7ac42feda6 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.features.lockscreen.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.features.lockscreen.api.LockScreenLockState import io.element.android.features.lockscreen.api.LockScreenService @@ -35,7 +35,6 @@ import kotlin.time.Duration @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DefaultLockScreenService( private val lockScreenConfig: LockScreenConfig, private val lockScreenStore: LockScreenStore, @@ -75,15 +74,14 @@ class DefaultLockScreenService( } /** - * Makes sure to delete the pin code when the session is deleted. + * Makes sure to delete the pin code when the last session is deleted. */ private fun observeSessionsState() { sessionObserver.addListener(object : SessionListener { - override suspend fun onSessionCreated(userId: String) = Unit - - override suspend fun onSessionDeleted(userId: String) { - // TODO handle multi session at some point - pinCodeManager.deletePinCode() + override suspend fun onSessionDeleted(userId: String, wasLastSession: Boolean) { + if (wasLastSession) { + pinCodeManager.deletePinCode() + } } }) } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenConfig.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenConfig.kt index 923eb32c76..c246390a63 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenConfig.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenFlowNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenFlowNode.kt index 94e2556ea2..2a84ddc0aa 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenFlowNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/LockScreenFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricAuthenticator.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricAuthenticator.kt index 7358cf248e..a96c713ff2 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricAuthenticator.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricAuthenticator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricAuthenticatorManager.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricAuthenticatorManager.kt index 887334812e..9917845725 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricAuthenticatorManager.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricAuthenticatorManager.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricUnlockError.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricUnlockError.kt index 91617b270a..941256f8b4 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricUnlockError.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/BiometricUnlockError.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricAuthenticatorManager.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricAuthenticatorManager.kt index b6b3c4115f..8bb044fd06 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricAuthenticatorManager.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricAuthenticatorManager.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -24,7 +25,6 @@ import androidx.fragment.app.FragmentActivity import androidx.lifecycle.compose.LocalLifecycleOwner import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.features.lockscreen.impl.LockScreenConfig import io.element.android.features.lockscreen.impl.R @@ -42,7 +42,6 @@ private const val SECRET_KEY_ALIAS = "elementx.SECRET_KEY_ALIAS_BIOMETRIC" @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) -@Inject class DefaultBiometricAuthenticatorManager( @ApplicationContext private val context: Context, private val lockScreenStore: LockScreenStore, diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricUnlockCallback.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricUnlockCallback.kt index 5e83ac9688..9a7a0abd09 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricUnlockCallback.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricUnlockCallback.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/components/PinEntryTextField.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/components/PinEntryTextField.kt index ed5bb2e4ac..439c6aeaa7 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/components/PinEntryTextField.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/components/PinEntryTextField.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManager.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManager.kt index 824db5339e..091432044a 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManager.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManager.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.features.lockscreen.impl.pin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.features.lockscreen.impl.storage.LockScreenStore import io.element.android.libraries.cryptography.api.EncryptionDecryptionService @@ -22,7 +22,6 @@ private const val SECRET_KEY_ALIAS = "elementx.SECRET_KEY_ALIAS_PIN_CODE" @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) -@Inject class DefaultPinCodeManager( private val secretKeyRepository: SecretKeyRepository, private val encryptionDecryptionService: EncryptionDecryptionService, @@ -72,7 +71,7 @@ class DefaultPinCodeManager( lockScreenStore.onWrongPin() } } - } catch (failure: Throwable) { + } catch (_: Throwable) { false } } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManagerCallback.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManagerCallback.kt index 9daadaa5d7..c9c8b7bf4e 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManagerCallback.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManagerCallback.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/PinCodeManager.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/PinCodeManager.kt index c36a976f25..9282f3e7df 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/PinCodeManager.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/PinCodeManager.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinDigit.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinDigit.kt index 0ca285321c..64200d0da3 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinDigit.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinDigit.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntry.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntry.kt index e9ee9a9903..f9cb2afcfe 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntry.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntry.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsEvents.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsEvents.kt index 19f729c94c..2d62427e02 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsEvents.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt index 01aeaabe89..0bd26729d5 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -110,7 +111,7 @@ class LockScreenSettingsFlowNode( } NavTarget.Settings -> { val callback = object : LockScreenSettingsNode.Callback { - override fun onChangePinClick() { + override fun navigateToSetupPin() { backstack.push(NavTarget.SetupPin) } } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsNode.kt index 5e27b815bc..e66bc13691 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,10 +13,10 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.SessionScope @ContributesNode(SessionScope::class) @@ -26,12 +27,10 @@ class LockScreenSettingsNode( private val presenter: LockScreenSettingsPresenter, ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun onChangePinClick() + fun navigateToSetupPin() } - private fun onChangePinClick() { - plugins().forEach { it.onChangePinClick() } - } + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { @@ -39,7 +38,7 @@ class LockScreenSettingsNode( LockScreenSettingsView( state = state, onBackClick = this::navigateUp, - onChangePinClick = this::onChangePinClick, + onChangePinClick = callback::navigateToSetupPin, modifier = modifier, ) } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt index 6f238c964b..589794bde0 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -50,7 +51,7 @@ class LockScreenSettingsPresenter( val biometricUnlock = biometricAuthenticatorManager.rememberConfirmBiometricAuthenticator() - fun handleEvents(event: LockScreenSettingsEvents) { + fun handleEvent(event: LockScreenSettingsEvents) { when (event) { LockScreenSettingsEvents.CancelRemovePin -> showRemovePinConfirmation = false LockScreenSettingsEvents.ConfirmRemovePin -> { @@ -82,7 +83,7 @@ class LockScreenSettingsPresenter( isBiometricEnabled = isBiometricEnabled, showRemovePinConfirmation = showRemovePinConfirmation, showToggleBiometric = biometricAuthenticatorManager.isDeviceSecured, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsState.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsState.kt index f49f2c2b26..a69d633508 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsState.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsStateProvider.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsStateProvider.kt index 5cf7068735..43f20c1c52 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsStateProvider.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsView.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsView.kt index a843525d00..fe5f20da0d 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsView.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/LockScreenSetupFlowNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/LockScreenSetupFlowNode.kt index 263cc8ea54..6f47395a26 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/LockScreenSetupFlowNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/LockScreenSetupFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,7 +15,6 @@ import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.newRoot import dev.zacsweers.metro.Assisted @@ -27,6 +27,7 @@ import io.element.android.features.lockscreen.impl.setup.biometric.SetupBiometri import io.element.android.features.lockscreen.impl.setup.pin.SetupPinNode import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope import kotlinx.parcelize.Parcelize @@ -50,9 +51,7 @@ class LockScreenSetupFlowNode( fun onSetupDone() } - private fun onSetupDone() { - plugins().forEach { it.onSetupDone() } - } + private val callback: Callback = callback() sealed interface NavTarget : Parcelable { @Parcelize @@ -67,7 +66,7 @@ class LockScreenSetupFlowNode( if (biometricAuthenticatorManager.hasAvailableAuthenticator) { backstack.newRoot(NavTarget.Biometric) } else { - onSetupDone() + callback.onSetupDone() } } } @@ -91,7 +90,7 @@ class LockScreenSetupFlowNode( NavTarget.Biometric -> { val callback = object : SetupBiometricNode.Callback { override fun onBiometricSetupDone() { - onSetupDone() + callback.onSetupDone() } } createNode(buildContext, plugins = listOf(callback)) diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricEvents.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricEvents.kt index 964d6b3d38..ab8b18642e 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricEvents.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricNode.kt index 64da1a321c..c74b9cd77f 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,10 +14,10 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.SessionScope @ContributesNode(SessionScope::class) @@ -30,16 +31,14 @@ class SetupBiometricNode( fun onBiometricSetupDone() } - private fun onSetupDone() { - plugins().forEach { it.onBiometricSetupDone() } - } + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { val state = presenter.present() LaunchedEffect(state.isBiometricSetupDone) { if (state.isBiometricSetupDone) { - onSetupDone() + callback.onBiometricSetupDone() } } SetupBiometricView( diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenter.kt index f1183d8541..3af2a28851 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenter.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -34,7 +35,7 @@ class SetupBiometricPresenter( val coroutineScope = rememberCoroutineScope() val biometricUnlock = biometricAuthenticatorManager.rememberConfirmBiometricAuthenticator() - fun handleEvents(event: SetupBiometricEvents) { + fun handleEvent(event: SetupBiometricEvents) { when (event) { SetupBiometricEvents.AllowBiometric -> coroutineScope.launch { biometricUnlock.setup() @@ -52,7 +53,7 @@ class SetupBiometricPresenter( return SetupBiometricState( isBiometricSetupDone = isBiometricSetupDone, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricState.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricState.kt index afe73beee0..2843c028d1 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricState.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricStateProvider.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricStateProvider.kt index 6c30fc7172..d725d2de97 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricStateProvider.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricView.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricView.kt index 761d48ec38..35b1ec76c0 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricView.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinEvents.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinEvents.kt index a111beb984..276a94b2fc 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinEvents.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinNode.kt index aceb955b88..2f86ca5ec7 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt index 36065484c7..ac5b5bd1cc 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -73,7 +74,7 @@ class SetupPinPresenter( } } - fun handleEvents(event: SetupPinEvents) { + fun handleEvent(event: SetupPinEvents) { when (event) { is SetupPinEvents.OnPinEntryChanged -> { // Use the fromConfirmationStep flag from ui to avoid race condition. @@ -106,7 +107,7 @@ class SetupPinPresenter( isConfirmationStep = isConfirmationStep, setupPinFailure = setupPinFailure, appName = buildMeta.applicationName, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinState.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinState.kt index ee9801b384..2d5124d440 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinState.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinStateProvider.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinStateProvider.kt index 2c4e08c130..f50643e367 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinStateProvider.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinView.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinView.kt index 81bfa4c1a4..5f2320db32 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinView.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/PinValidator.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/PinValidator.kt index a12872fab1..c84d892fbc 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/PinValidator.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/PinValidator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/SetupPinFailure.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/SetupPinFailure.kt index cedefa3f19..94e4aa5fb2 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/SetupPinFailure.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/setup/pin/validation/SetupPinFailure.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/EncryptedPinCodeStorage.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/EncryptedPinCodeStorage.kt index d1ec05f643..c4558812de 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/EncryptedPinCodeStorage.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/EncryptedPinCodeStorage.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/LockScreenStore.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/LockScreenStore.kt index e4321f7358..d48a30e815 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/LockScreenStore.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/LockScreenStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/PreferencesLockScreenStore.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/PreferencesLockScreenStore.kt index dbe51d3222..6b99d90592 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/PreferencesLockScreenStore.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/storage/PreferencesLockScreenStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,7 +15,6 @@ import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.lockscreen.impl.LockScreenConfig import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory import kotlinx.coroutines.flow.Flow @@ -22,7 +22,6 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map @ContributesBinding(AppScope::class) -@Inject class PreferencesLockScreenStore( preferenceDataStoreFactory: PreferenceDataStoreFactory, private val lockScreenConfig: LockScreenConfig, diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockEvents.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockEvents.kt index e72d05fa27..bd9043859f 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockEvents.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockHelper.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockHelper.kt index 59bccff9be..2a475814c9 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockHelper.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockHelper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockNode.kt index fba460f6ee..459e165e00 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,10 +14,10 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.SessionScope @ContributesNode(SessionScope::class) @@ -30,18 +31,14 @@ class PinUnlockNode( fun onUnlock() } - private fun onUnlock() { - plugins().forEach { - it.onUnlock() - } - } + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { val state = presenter.present() LaunchedEffect(state.isUnlocked) { if (state.isUnlocked) { - onUnlock() + callback.onUnlock() } } PinUnlockView( diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt index fc2e61d404..5429320fc7 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -94,7 +95,7 @@ class PinUnlockPresenter( isUnlocked.value = true } - fun handleEvents(event: PinUnlockEvents) { + fun handleEvent(event: PinUnlockEvents) { when (event) { is PinUnlockEvents.OnPinKeypadPressed -> { pinEntryState.value = pinEntry.process(event.pinKeypadModel) @@ -129,7 +130,7 @@ class PinUnlockPresenter( showBiometricUnlock = biometricUnlock.isActive, biometricUnlockResult = biometricUnlockResult, isUnlocked = isUnlocked.value, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockState.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockState.kt index dd24fbefd7..2bbcbe335c 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockState.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateProvider.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateProvider.kt index 54647eaad8..2beb8babe3 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateProvider.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockView.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockView.kt index baa856307c..659f8c2966 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockView.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/activity/PinUnlockActivity.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/activity/PinUnlockActivity.kt index a494bca8c6..6209c19be2 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/activity/PinUnlockActivity.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/activity/PinUnlockActivity.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,8 +15,12 @@ import androidx.activity.OnBackPressedCallback import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.lifecycle.lifecycleScope import dev.zacsweers.metro.Inject +import io.element.android.compound.colors.SemanticColorsLightDark import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.lockscreen.api.LockScreenLockState import io.element.android.features.lockscreen.api.LockScreenService @@ -46,9 +51,13 @@ class PinUnlockActivity : AppCompatActivity() { super.onCreate(savedInstanceState) bindings().inject(this) setContent { + val colors by remember { + enterpriseService.semanticColorsFlow(sessionId = null) + }.collectAsState(SemanticColorsLightDark.default) ElementThemeApp( appPreferencesStore = appPreferencesStore, - enterpriseService = enterpriseService, + compoundLight = colors.light, + compoundDark = colors.dark, buildMeta = buildMeta, ) { val state = presenter.present() diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/di/PinUnlockBindings.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/di/PinUnlockBindings.kt index 8ddb898caa..c8b65afbe0 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/di/PinUnlockBindings.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/di/PinUnlockBindings.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt index 820b557423..093ad2f2b4 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypad.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -20,8 +21,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.Backspace import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -39,6 +38,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.times import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.text.toSp @@ -205,7 +205,8 @@ private fun PinKeypadBackButton( onClick = onClick, ) { Icon( - imageVector = Icons.AutoMirrored.Filled.Backspace, + modifier = Modifier.size(28.dp), + imageVector = CompoundIcons.BackspaceSolid(), contentDescription = stringResource(CommonStrings.a11y_delete), ) } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypadModel.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypadModel.kt index 9f54e6bd5e..36c783951e 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypadModel.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypadModel.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/main/res/values-hr/translations.xml b/features/lockscreen/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..1a81bcc6cb --- /dev/null +++ b/features/lockscreen/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,40 @@ + + + "biometrijska provjera autentičnosti" + "biometrijsko otključavanje" + "Otključavanje biometrijom" + "Potvrdi biometriju" + "Zaboravili ste PIN?" + "Promijeni PIN kod" + "Omogući biometrijsko otključavanje" + "Ukloni PIN" + "Jeste li sigurni da želite ukloniti PIN?" + "Želite li ukloniti PIN?" + "Dopusti %1$s" + "Radije bih upotrijebio/la PIN" + "Uštedite si malo vremena i iskoristite %1$s kako biste svaki put otključali aplikaciju" + "Odaberite PIN" + "Potvrdite PIN" + "Zaključajte %1$s kako biste dodatno osigurali svoje razgovore. + +Odaberite nešto nezaboravno. Ako zaboravite ovaj PIN, bit ćete odjavljeni iz aplikacije." + "Ovo ne možete iz sigurnosnih razloga odabrati kao svoj PIN kod" + "Odaberite drugi PIN" + "Unesite dvaput isti PIN" + "PIN-ovi se ne podudaraju" + "Morat ćete se ponovno prijaviti i izraditi novi PIN da biste mogli nastaviti" + "Odjavit ćete se" + + "Imate %1$d pokušaj otključavanja" + "Imate %1$d pokušaja otključavanja" + "Imate %1$d pokušaja otključavanja" + + + "Pogrešan PIN. Imate još %1$d pokušaj" + "Pogrešan PIN. Imate još %1$d pokušaja" + "Pogrešan PIN. Imate još %1$d pokušaja" + + "Upotrijebi biometriju" + "Upotrijebi PIN" + "Odjavljivanje…" + diff --git a/features/lockscreen/impl/src/main/res/values-ru/translations.xml b/features/lockscreen/impl/src/main/res/values-ru/translations.xml index eae4799f93..3879c46597 100644 --- a/features/lockscreen/impl/src/main/res/values-ru/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-ru/translations.xml @@ -6,7 +6,7 @@ "Подтвердить биометрические данные" "Забыли PIN-код?" "Изменить PIN-код" - "Разрешить биометрическую разблокировку" + "Разрешить разблокировку по биометрии" "Удалить PIN-код" "Вы действительно хотите удалить PIN-код?" "Удалить PIN-код?" diff --git a/features/lockscreen/impl/src/main/res/values-uz/translations.xml b/features/lockscreen/impl/src/main/res/values-uz/translations.xml index 9981cf3bce..8cd0a57b94 100644 --- a/features/lockscreen/impl/src/main/res/values-uz/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-uz/translations.xml @@ -3,6 +3,7 @@ "biometrik autentifikatsiya" "biometrik qulf ochish" "Biometrik bilan qulfni oching" + "Biometrikni tasdiqlang" "PIN kodni unutdingizmi?" "PIN kodni o\'zgartirish" "Biometrik qulfni ochishga ruxsat bering" diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenEntryPointIntentTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenEntryPointIntentTest.kt index 995140b87b..e99223735f 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenEntryPointIntentTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenEntryPointIntentTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenEntryPointTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenEntryPointTest.kt index 822d275063..533a7be414 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenEntryPointTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -37,9 +38,12 @@ class DefaultLockScreenEntryPointTest { override fun onSetupDone() = lambdaError() } val navTarget = LockScreenEntryPoint.Target.Setup - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null), navTarget) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + navTarget = navTarget, + callback = callback, + ) assertThat(result).isInstanceOf(LockScreenFlowNode::class.java) assertThat(result.plugins).contains(LockScreenFlowNode.Inputs(LockScreenFlowNode.NavTarget.Setup)) assertThat(result.plugins).contains(callback) @@ -58,9 +62,12 @@ class DefaultLockScreenEntryPointTest { override fun onSetupDone() = lambdaError() } val navTarget = LockScreenEntryPoint.Target.Settings - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null), navTarget) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + navTarget = navTarget, + callback = callback, + ) assertThat(result).isInstanceOf(LockScreenFlowNode::class.java) assertThat(result.plugins).contains(LockScreenFlowNode.Inputs(LockScreenFlowNode.NavTarget.Settings)) assertThat(result.plugins).contains(callback) diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenServiceTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenServiceTest.kt new file mode 100644 index 0000000000..9082f20a55 --- /dev/null +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/DefaultLockScreenServiceTest.kt @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.lockscreen.impl + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.features.lockscreen.impl.biometric.BiometricAuthenticatorManager +import io.element.android.features.lockscreen.impl.biometric.FakeBiometricAuthenticatorManager +import io.element.android.features.lockscreen.impl.fixtures.aLockScreenConfig +import io.element.android.features.lockscreen.impl.pin.PinCodeManager +import io.element.android.features.lockscreen.impl.pin.createDefaultPinCodeManager +import io.element.android.features.lockscreen.impl.pin.storage.InMemoryLockScreenStore +import io.element.android.features.lockscreen.impl.storage.LockScreenStore +import io.element.android.libraries.sessionstorage.api.observer.SessionObserver +import io.element.android.libraries.sessionstorage.test.observer.FakeSessionObserver +import io.element.android.services.appnavstate.api.AppForegroundStateService +import io.element.android.services.appnavstate.test.FakeAppForegroundStateService +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class DefaultLockScreenServiceTest { + @Test + fun `when the pin is not mandatory and no pin is configured isSetupRequired emits false`() = runTest { + val sut = createDefaultLockScreenService( + lockScreenConfig = aLockScreenConfig(isPinMandatory = false) + ) + sut.isSetupRequired().test { + assertThat(awaitItem()).isFalse() + } + } + + @Test + fun `when the pin is mandatory, isSetupRequired emits true`() = runTest { + val lockScreenStore = InMemoryLockScreenStore() + val sut = createDefaultLockScreenService( + lockScreenConfig = aLockScreenConfig(isPinMandatory = true), + lockScreenStore = lockScreenStore, + ) + sut.isSetupRequired().test { + assertThat(awaitItem()).isTrue() + // When the user configures the pin code, the setup is not required anymore + lockScreenStore.saveEncryptedPinCode("encryptedCode") + assertThat(awaitItem()).isFalse() + // Users deletes the pin code + lockScreenStore.deleteEncryptedPinCode() + assertThat(awaitItem()).isTrue() + } + } + + @Test + fun `when the last session is deleted, the pin code is removed`() = runTest { + val sessionObserver = FakeSessionObserver() + val lockScreenStore = InMemoryLockScreenStore() + val sut = createDefaultLockScreenService( + lockScreenConfig = aLockScreenConfig(isPinMandatory = true), + lockScreenStore = lockScreenStore, + sessionObserver = sessionObserver, + ) + sut.isPinSetup().test { + assertThat(awaitItem()).isFalse() + // When the user configure the pin code, the setup is not required anymore + lockScreenStore.saveEncryptedPinCode("encryptedCode") + assertThat(awaitItem()).isTrue() + sessionObserver.onSessionDeleted("userId", wasLastSession = false) + expectNoEvents() + sessionObserver.onSessionDeleted("userId", wasLastSession = true) + assertThat(awaitItem()).isFalse() + } + } +} + +private fun TestScope.createDefaultLockScreenService( + lockScreenConfig: LockScreenConfig = aLockScreenConfig(), + lockScreenStore: LockScreenStore = InMemoryLockScreenStore(), + pinCodeManager: PinCodeManager = createDefaultPinCodeManager( + lockScreenStore = lockScreenStore, + ), + sessionObserver: SessionObserver = FakeSessionObserver(), + appForegroundStateService: AppForegroundStateService = FakeAppForegroundStateService(), + biometricAuthenticatorManager: BiometricAuthenticatorManager = FakeBiometricAuthenticatorManager(), +) = DefaultLockScreenService( + lockScreenConfig = lockScreenConfig, + lockScreenStore = lockScreenStore, + pinCodeManager = pinCodeManager, + coroutineScope = backgroundScope, + sessionObserver = sessionObserver, + appForegroundStateService = appForegroundStateService, + biometricAuthenticatorManager = biometricAuthenticatorManager, +) diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricAuthenticator.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricAuthenticator.kt index 5e35f22e05..073bdc799d 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricAuthenticator.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricAuthenticator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricAuthenticatorManager.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricAuthenticatorManager.kt index 6e98abd80c..9e9b892582 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricAuthenticatorManager.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/biometric/FakeBiometricAuthenticatorManager.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/fixtures/LockScreenConfig.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/fixtures/LockScreenConfig.kt index 675102140d..23fccb6584 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/fixtures/LockScreenConfig.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/fixtures/LockScreenConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/fixtures/PinCodeManager.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/fixtures/PinCodeManager.kt index 5f983daace..89bd760a5f 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/fixtures/PinCodeManager.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/fixtures/PinCodeManager.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManagerTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManagerTest.kt index ed942c45b9..dbc9c18c90 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManagerTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/DefaultPinCodeManagerTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,19 +11,18 @@ package io.element.android.features.lockscreen.impl.pin import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.lockscreen.impl.pin.storage.InMemoryLockScreenStore +import io.element.android.features.lockscreen.impl.storage.LockScreenStore +import io.element.android.libraries.cryptography.api.EncryptionDecryptionService +import io.element.android.libraries.cryptography.api.SecretKeyRepository import io.element.android.libraries.cryptography.impl.AESEncryptionDecryptionService import io.element.android.libraries.cryptography.test.SimpleSecretKeyRepository import kotlinx.coroutines.test.runTest import org.junit.Test class DefaultPinCodeManagerTest { - private val lockScreenStore = InMemoryLockScreenStore() - private val secretKeyRepository = SimpleSecretKeyRepository() - private val encryptionDecryptionService = AESEncryptionDecryptionService() - private val pinCodeManager = DefaultPinCodeManager(secretKeyRepository, encryptionDecryptionService, lockScreenStore) - @Test fun `given a pin code when create and delete assert no pin code left`() = runTest { + val pinCodeManager = createDefaultPinCodeManager() pinCodeManager.hasPinCode().test { assertThat(awaitItem()).isFalse() pinCodeManager.createPinCode("1234") @@ -34,6 +34,7 @@ class DefaultPinCodeManagerTest { @Test fun `given a pin code when create and verify with the same pin succeed`() = runTest { + val pinCodeManager = createDefaultPinCodeManager() val pinCode = "1234" pinCodeManager.createPinCode(pinCode) assertThat(pinCodeManager.verifyPinCode(pinCode)).isTrue() @@ -41,7 +42,18 @@ class DefaultPinCodeManagerTest { @Test fun `given a pin code when create and verify with a different pin fails`() = runTest { + val pinCodeManager = createDefaultPinCodeManager() pinCodeManager.createPinCode("1234") assertThat(pinCodeManager.verifyPinCode("1235")).isFalse() } } + +fun createDefaultPinCodeManager( + lockScreenStore: LockScreenStore = InMemoryLockScreenStore(), + secretKeyRepository: SecretKeyRepository = SimpleSecretKeyRepository(), + encryptionDecryptionService: EncryptionDecryptionService = AESEncryptionDecryptionService(), +) = DefaultPinCodeManager( + lockScreenStore = lockScreenStore, + secretKeyRepository = secretKeyRepository, + encryptionDecryptionService = encryptionDecryptionService, +) diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntryAssertions.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntryAssertions.kt index b704e24e0d..9a863a0478 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntryAssertions.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntryAssertions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntryTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntryTest.kt index 2c59f843bc..8c2e5ba4bc 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntryTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/model/PinEntryTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/storage/InMemoryLockScreenStore.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/storage/InMemoryLockScreenStore.kt index a4a197b533..61acf71cdd 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/storage/InMemoryLockScreenStore.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/pin/storage/InMemoryLockScreenStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenterTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenterTest.kt index 45af2682e1..ef3e94f27f 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenterTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenterTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenterTest.kt index 7efb6619f1..3f87c1dccf 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenterTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/biometric/SetupBiometricPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt index 1d1b381fef..6a1d32e879 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/setup/pin/SetupPinPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt index 5fdf1342ea..f5bfb11818 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateTest.kt index 389ebf83c7..34244b3374 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/PinUnlockStateTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypadTest.kt b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypadTest.kt index e51b007312..1ecb79bd67 100644 --- a/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypadTest.kt +++ b/features/lockscreen/impl/src/test/kotlin/io/element/android/features/lockscreen/impl/unlock/keypad/PinKeypadTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/lockscreen/test/build.gradle.kts b/features/lockscreen/test/build.gradle.kts index 80f46e82c6..5c314dbb87 100644 --- a/features/lockscreen/test/build.gradle.kts +++ b/features/lockscreen/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,6 +15,8 @@ android { } dependencies { - implementation(libs.coroutines.core) api(projects.features.lockscreen.api) + implementation(libs.coroutines.core) + implementation(projects.libraries.architecture) + implementation(projects.tests.testutils) } diff --git a/features/lockscreen/test/src/main/kotlin/io/element/android/features/lockscreen/test/FakeLockScreenEntryPoint.kt b/features/lockscreen/test/src/main/kotlin/io/element/android/features/lockscreen/test/FakeLockScreenEntryPoint.kt new file mode 100644 index 0000000000..1bfcbaf45b --- /dev/null +++ b/features/lockscreen/test/src/main/kotlin/io/element/android/features/lockscreen/test/FakeLockScreenEntryPoint.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.lockscreen.test + +import android.content.Context +import android.content.Intent +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.lockscreen.api.LockScreenEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeLockScreenEntryPoint : LockScreenEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + navTarget: LockScreenEntryPoint.Target, + callback: LockScreenEntryPoint.Callback, + ): Node = lambdaError() + + override fun pinUnlockIntent(context: Context): Intent = lambdaError() +} diff --git a/features/lockscreen/test/src/main/kotlin/io/element/android/features/lockscreen/test/FakeLockScreenService.kt b/features/lockscreen/test/src/main/kotlin/io/element/android/features/lockscreen/test/FakeLockScreenService.kt index 7d2fd60c16..a75b3b1614 100644 --- a/features/lockscreen/test/src/main/kotlin/io/element/android/features/lockscreen/test/FakeLockScreenService.kt +++ b/features/lockscreen/test/src/main/kotlin/io/element/android/features/lockscreen/test/FakeLockScreenService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/api/build.gradle.kts b/features/login/api/build.gradle.kts index 869e7ad25a..2a4e3ecb7c 100644 --- a/features/login/api/build.gradle.kts +++ b/features/login/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt index b3ef99ee19..830a63b8f1 100644 --- a/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt +++ b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -19,14 +20,14 @@ interface LoginEntryPoint : FeatureEntryPoint { ) interface Callback : Plugin { - fun onReportProblem() + fun navigateToBugReport() + fun onDone() } - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - - interface NodeBuilder { - fun params(params: Params): NodeBuilder - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: Params, + callback: Callback, + ): Node } diff --git a/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginIntentResolver.kt b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginIntentResolver.kt index 67eb284944..79cfc35fbd 100644 --- a/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginIntentResolver.kt +++ b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginIntentResolver.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginParams.kt b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginParams.kt index 84dcac40a6..2a83a06915 100644 --- a/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginParams.kt +++ b/features/login/api/src/main/kotlin/io/element/android/features/login/api/LoginParams.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/api/src/main/kotlin/io/element/android/features/login/api/accesscontrol/AccountProviderAccessControl.kt b/features/login/api/src/main/kotlin/io/element/android/features/login/api/accesscontrol/AccountProviderAccessControl.kt index 3b182fd4df..fc804c14ff 100644 --- a/features/login/api/src/main/kotlin/io/element/android/features/login/api/accesscontrol/AccountProviderAccessControl.kt +++ b/features/login/api/src/main/kotlin/io/element/android/features/login/api/accesscontrol/AccountProviderAccessControl.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/build.gradle.kts b/features/login/impl/build.gradle.kts index 690fe13578..071408b2f5 100644 --- a/features/login/impl/build.gradle.kts +++ b/features/login/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/AndroidManifest.xml b/features/login/impl/src/main/AndroidManifest.xml index 44281caee1..453cf05132 100644 --- a/features/login/impl/src/main/AndroidManifest.xml +++ b/features/login/impl/src/main/AndroidManifest.xml @@ -1,7 +1,8 @@ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt index 7da2bdf758..1f0fe44f35 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,36 +10,28 @@ package io.element.android.features.login.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.login.api.LoginEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultLoginEntryPoint : LoginEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): LoginEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : LoginEntryPoint.NodeBuilder { - override fun params(params: LoginEntryPoint.Params): LoginEntryPoint.NodeBuilder { - plugins += LoginFlowNode.Params( + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: LoginEntryPoint.Params, + callback: LoginEntryPoint.Callback, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf( + LoginFlowNode.Params( accountProvider = params.accountProvider, loginHint = params.loginHint, - ) - return this - } - - override fun callback(callback: LoginEntryPoint.Callback): LoginEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + ), + callback, + ) + ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginIntentResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginIntentResolver.kt index 4851a9ab7c..26f0f1737e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginIntentResolver.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/DefaultLoginIntentResolver.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,12 +11,10 @@ package io.element.android.features.login.impl import androidx.core.net.toUri import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.login.api.LoginIntentResolver import io.element.android.features.login.api.LoginParams @ContributesBinding(AppScope::class) -@Inject class DefaultLoginIntentResolver : LoginIntentResolver { override fun parse(uriString: String): LoginParams? { val uri = uriString.toUri() diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index 4b83190f5d..a19bb12d86 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,7 +19,6 @@ import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.push import com.bumble.appyx.navmodel.backstack.operation.singleTop @@ -41,11 +41,14 @@ import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTa import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.di.annotations.AppCoroutineScope import io.element.android.libraries.matrix.api.auth.OidcDetails import io.element.android.libraries.oidc.api.OidcAction import io.element.android.libraries.oidc.api.OidcActionFlow +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize @@ -57,6 +60,8 @@ class LoginFlowNode( @Assisted plugins: List, private val accountProviderDataSource: AccountProviderDataSource, private val oidcActionFlow: OidcActionFlow, + @AppCoroutineScope + private val appCoroutineScope: CoroutineScope, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.OnBoarding, @@ -70,6 +75,7 @@ class LoginFlowNode( val loginHint: String?, ) : NodeInputs + private val callback: LoginEntryPoint.Callback = callback() private var activity: Activity? = null private var darkTheme: Boolean = false @@ -126,13 +132,13 @@ class LoginFlowNode( return when (navTarget) { NavTarget.OnBoarding -> { val callback = object : OnBoardingNode.Callback { - override fun onSignUp() { + override fun navigateToSignUpFlow() { backstack.push( NavTarget.ConfirmAccountProvider(isAccountCreation = true) ) } - override fun onSignIn(mustChooseAccountProvider: Boolean) { + override fun navigateToSignInFlow(mustChooseAccountProvider: Boolean) { backstack.push( if (mustChooseAccountProvider) { NavTarget.ChooseAccountProvider @@ -142,25 +148,29 @@ class LoginFlowNode( ) } - override fun onSignInWithQrCode() { + override fun navigateToQrCode() { backstack.push(NavTarget.QrCode) } - override fun onReportProblem() { - plugins().forEach { it.onReportProblem() } + override fun navigateToBugReport() { + callback.navigateToBugReport() } - override fun onOidcDetails(oidcDetails: OidcDetails) { + override fun navigateToOidc(oidcDetails: OidcDetails) { navigateToMas(oidcDetails) } - override fun onCreateAccountContinue(url: String) { + override fun navigateToCreateAccount(url: String) { backstack.push(NavTarget.CreateAccount(url)) } - override fun onLoginPasswordNeeded() { + override fun navigateToLoginPassword() { backstack.push(NavTarget.LoginPassword) } + + override fun onDone() { + callback.onDone() + } } val params = inputs() val inputs = OnBoardingNode.Params( @@ -171,15 +181,15 @@ class LoginFlowNode( } NavTarget.ChooseAccountProvider -> { val callback = object : ChooseAccountProviderNode.Callback { - override fun onOidcDetails(oidcDetails: OidcDetails) { + override fun navigateToOidc(oidcDetails: OidcDetails) { navigateToMas(oidcDetails) } - override fun onCreateAccountContinue(url: String) { + override fun navigateToCreateAccount(url: String) { backstack.push(NavTarget.CreateAccount(url)) } - override fun onLoginPasswordNeeded() { + override fun navigateToLoginPassword() { backstack.push(NavTarget.LoginPassword) } } @@ -193,19 +203,19 @@ class LoginFlowNode( isAccountCreation = navTarget.isAccountCreation, ) val callback = object : ConfirmAccountProviderNode.Callback { - override fun onOidcDetails(oidcDetails: OidcDetails) { + override fun navigateToOidc(oidcDetails: OidcDetails) { navigateToMas(oidcDetails) } - override fun onCreateAccountContinue(url: String) { + override fun navigateToCreateAccount(url: String) { backstack.push(NavTarget.CreateAccount(url)) } - override fun onLoginPasswordNeeded() { + override fun navigateToLoginPassword() { backstack.push(NavTarget.LoginPassword) } - override fun onChangeAccountProvider() { + override fun navigateToChangeAccountProvider() { backstack.push(NavTarget.ChangeAccountProvider) } } @@ -221,7 +231,7 @@ class LoginFlowNode( backstack.singleTop(confirmAccountProvider) } - override fun onOtherClick() { + override fun navigateToSearchAccountProvider() { backstack.push(NavTarget.SearchAccountProvider) } } @@ -267,7 +277,9 @@ class LoginFlowNode( DisposableEffect(Unit) { onDispose { activity = null - accountProviderDataSource.reset() + appCoroutineScope.launch { + accountProviderDataSource.reset() + } } } BackstackView() diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt index fb739008a7..db56047e15 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accesscontrol/DefaultAccountProviderAccessControl.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.features.login.impl.accesscontrol import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.login.api.accesscontrol.AccountProviderAccessControl import io.element.android.features.login.impl.changeserver.AccountProviderAccessException @@ -17,7 +17,6 @@ import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.wellknown.api.WellknownRetriever @ContributesBinding(AppScope::class) -@Inject class DefaultAccountProviderAccessControl( private val enterpriseService: EnterpriseService, private val wellknownRetriever: WellknownRetriever, @@ -41,7 +40,7 @@ class DefaultAccountProviderAccessControl( // Ensure that Element Pro is not required for this account provider val wellKnown = wellknownRetriever.getElementWellKnown( baseUrl = accountProviderUrl.ensureProtocol(), - ) + ).dataOrNull() if (wellKnown?.enforceElementPro == true) { throw AccountProviderAccessException.NeedElementProException( unauthorisedAccountProviderTitle = title, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProvider.kt index 0fcef6bedf..7ca467da7d 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,5 +14,4 @@ data class AccountProvider( val subtitle: String? = null, val isPublic: Boolean = false, val isMatrixOrg: Boolean = false, - val isValid: Boolean = false, ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt index b14dd75b10..931959d4ea 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -21,28 +22,34 @@ import kotlinx.coroutines.flow.asStateFlow class AccountProviderDataSource( enterpriseService: EnterpriseService, ) { - private val defaultAccountProvider = - (enterpriseService.defaultHomeserverList().firstOrNull { it != EnterpriseService.ANY_ACCOUNT_PROVIDER } ?: AuthenticationConfig.MATRIX_ORG_URL) - .let { url -> - AccountProvider( - url = url, - subtitle = null, - isPublic = url == AuthenticationConfig.MATRIX_ORG_URL, - isMatrixOrg = url == AuthenticationConfig.MATRIX_ORG_URL, - ) - } - - private val accountProvider: MutableStateFlow = MutableStateFlow( - defaultAccountProvider + private val defaultAccountProvider = createAccountProvider( + url = enterpriseService.defaultHomeserverList() + .firstOrNull { it != EnterpriseService.ANY_ACCOUNT_PROVIDER } + ?: AuthenticationConfig.MATRIX_ORG_URL ) + private val accountProvider: MutableStateFlow = MutableStateFlow(defaultAccountProvider) + val flow: StateFlow = accountProvider.asStateFlow() - fun reset() { - accountProvider.tryEmit(defaultAccountProvider) + suspend fun reset() { + accountProvider.emit(defaultAccountProvider) } - fun userSelection(data: AccountProvider) { - accountProvider.tryEmit(data) + suspend fun setUrl(url: String) { + setAccountProvider(createAccountProvider(url)) + } + + suspend fun setAccountProvider(data: AccountProvider) { + accountProvider.emit(data) + } + + private fun createAccountProvider(url: String): AccountProvider { + return AccountProvider( + url = url, + subtitle = null, + isPublic = url == AuthenticationConfig.MATRIX_ORG_URL, + isMatrixOrg = url == AuthenticationConfig.MATRIX_ORG_URL, + ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderOtherView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderOtherView.kt index 92fda4cf14..fd5c7b8019 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderOtherView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderOtherView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderProvider.kt index a5f0fd7d3b..745939233f 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,7 +16,7 @@ open class AccountProviderProvider : PreviewParameterProvider { get() = sequenceOf( anAccountProvider(), anAccountProvider().copy(subtitle = null), - anAccountProvider().copy(subtitle = null, title = "invalid", isValid = false), + anAccountProvider().copy(subtitle = null, title = "invalid"), anAccountProvider().copy(subtitle = null, title = "Other", isPublic = false, isMatrixOrg = false), // Add other state here ) @@ -26,11 +27,9 @@ fun anAccountProvider( subtitle: String? = "Matrix.org is an open network for secure, decentralized communication.", isPublic: Boolean = true, isMatrixOrg: Boolean = true, - isValid: Boolean = true, ) = AccountProvider( url = url, subtitle = subtitle, isPublic = isPublic, isMatrixOrg = isMatrixOrg, - isValid = isValid, ) 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 b61f0283a7..5130bf1ad8 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/AccountProviderAccessException.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/AccountProviderAccessException.kt index 5c48f346fe..88ec3bfe8e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/AccountProviderAccessException.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/AccountProviderAccessException.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvents.kt index 231fe04ba0..27e6128dd0 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt index 4df9eb12d5..4a4fb3ca41 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -38,7 +39,7 @@ class ChangeServerPresenter( mutableStateOf(AsyncData.Uninitialized) } - fun handleEvents(event: ChangeServerEvents) { + fun handleEvent(event: ChangeServerEvents) { when (event) { is ChangeServerEvents.ChangeServer -> localCoroutineScope.changeServer(event.accountProvider, changeServerAction) ChangeServerEvents.ClearError -> changeServerAction.value = AsyncData.Uninitialized @@ -47,7 +48,7 @@ class ChangeServerPresenter( return ChangeServerState( changeServerAction = changeServerAction.value, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } @@ -60,11 +61,12 @@ class ChangeServerPresenter( title = data.title, accountProviderUrl = data.url, ) - authenticationService.setHomeserver(data.url).map { - authenticationService.getHomeserverDetails().value!! - // Valid, remember user choice - accountProviderDataSource.userSelection(data) - }.getOrThrow() + val details = authenticationService.setHomeserver(data.url).getOrThrow() + if (!details.isSupported) { + throw ChangeServerError.UnsupportedServer + } + // Homeserver is valid, remember user choice + accountProviderDataSource.setAccountProvider(data) }.runCatchingUpdatingState(changeServerAction, errorTransform = ChangeServerError::from) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerState.kt index fb5b79474e..eea3ad78cd 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt index a97ff2dda1..1a94bb5631 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,13 +11,12 @@ package io.element.android.features.login.impl.changeserver import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.ui.strings.CommonStrings open class ChangeServerStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aChangeServerState(), - aChangeServerState(changeServerAction = AsyncData.Failure(ChangeServerError.Error(CommonStrings.error_unknown))), + aChangeServerState(changeServerAction = AsyncData.Failure(ChangeServerError.Error(null))), aChangeServerState(changeServerAction = AsyncData.Failure(ChangeServerError.SlidingSyncAlert)), aChangeServerState( changeServerAction = AsyncData.Failure( @@ -34,6 +34,11 @@ open class ChangeServerStateProvider : PreviewParameterProvider { when (val error = state.changeServerAction.error as? ChangeServerError) { + ChangeServerError.InvalidServer -> + ErrorDialog( + modifier = modifier, + content = stringResource(R.string.screen_change_server_error_invalid_homeserver), + onSubmit = { + eventSink.invoke(ChangeServerEvents.ClearError) + } + ) + ChangeServerError.UnsupportedServer -> + ErrorDialog( + modifier = modifier, + content = stringResource(R.string.screen_login_error_unsupported_authentication), + onSubmit = { + eventSink.invoke(ChangeServerEvents.ClearError) + } + ) is ChangeServerError.Error -> { ErrorDialog( modifier = modifier, - content = error.message(), + content = error.messageStr ?: stringResource(CommonStrings.error_unknown), onSubmit = { eventSink.invoke(ChangeServerEvents.ClearError) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/LoginModule.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/LoginModule.kt index 40bb58a96e..4523e6f45e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/LoginModule.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/LoginModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginBindings.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginBindings.kt index c3c189f2c8..050dc0c8c6 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginBindings.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginBindings.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginGraph.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginGraph.kt index 12400f97ec..6500820629 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginGraph.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginGraph.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginScope.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginScope.kt index c4aaf4f1b3..a3538c8cd3 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginScope.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/di/QrCodeLoginScope.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/dialogs/SlidingSyncNotSupportedDialog.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/dialogs/SlidingSyncNotSupportedDialog.kt index be526ccb0d..60ffd2af87 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/dialogs/SlidingSyncNotSupportedDialog.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/dialogs/SlidingSyncNotSupportedDialog.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt index 5079e2a715..2f4af14237 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerError.kt @@ -1,30 +1,20 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.login.impl.error -import androidx.annotation.StringRes -import androidx.compose.runtime.Composable -import androidx.compose.runtime.ReadOnlyComposable -import androidx.compose.ui.res.stringResource -import io.element.android.features.login.impl.R import io.element.android.features.login.impl.changeserver.AccountProviderAccessException import io.element.android.libraries.matrix.api.auth.AuthenticationException -import io.element.android.libraries.ui.strings.CommonStrings sealed class ChangeServerError : Exception() { data class Error( - @StringRes val messageId: Int? = null, val messageStr: String? = null, - ) : ChangeServerError() { - @Composable - @ReadOnlyComposable - fun message(): String = messageStr ?: stringResource(messageId ?: CommonStrings.error_unknown) - } + ) : ChangeServerError() data class NeedElementPro( val unauthorisedAccountProviderTitle: String, @@ -37,11 +27,23 @@ sealed class ChangeServerError : Exception() { ) : ChangeServerError() data object SlidingSyncAlert : ChangeServerError() + data object InvalidServer : ChangeServerError() + data object UnsupportedServer : ChangeServerError() companion object { fun from(error: Throwable): ChangeServerError = when (error) { - is AuthenticationException.SlidingSyncVersion -> SlidingSyncAlert - is AuthenticationException.Oidc -> Error(messageStr = error.message) + is ChangeServerError -> error + is AuthenticationException -> { + when (error) { + is AuthenticationException.SlidingSyncVersion -> SlidingSyncAlert + is AuthenticationException.InvalidServerName, + is AuthenticationException.ServerUnreachable -> InvalidServer + // AccountAlreadyLoggedIn error should not happen at this point + is AuthenticationException.AccountAlreadyLoggedIn -> Error(messageStr = error.message) + is AuthenticationException.Generic -> Error(messageStr = error.message) + is AuthenticationException.Oidc -> Error(messageStr = error.message) + } + } is AccountProviderAccessException.NeedElementProException -> NeedElementPro( unauthorisedAccountProviderTitle = error.unauthorisedAccountProviderTitle, applicationId = error.applicationId, @@ -50,7 +52,7 @@ sealed class ChangeServerError : Exception() { unauthorisedAccountProviderTitle = error.unauthorisedAccountProviderTitle, authorisedAccountProviderTitles = error.authorisedAccountProviderTitles, ) - else -> Error(messageId = R.string.screen_change_server_error_invalid_homeserver) + else -> Error(messageStr = error.message) } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerErrorProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerErrorProvider.kt index 333347851a..59fb42fe0c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerErrorProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ChangeServerErrorProvider.kt @@ -1,21 +1,19 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.login.impl.error import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.login.impl.R class ChangeServerErrorProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - ChangeServerError.Error( - messageId = R.string.screen_change_server_error_invalid_homeserver, - ), + ChangeServerError.InvalidServer, ChangeServerError.Error( messageStr = "An error description", ), @@ -28,5 +26,6 @@ class ChangeServerErrorProvider : PreviewParameterProvider { authorisedAccountProviderTitles = listOf("provider.org", "provider.io"), ), ChangeServerError.SlidingSyncAlert, + ChangeServerError.UnsupportedServer, ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ErrorFormatter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ErrorFormatter.kt index 16bdf9b5e6..66ca0d9752 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ErrorFormatter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/error/ErrorFormatter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginHelper.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginHelper.kt index 82ee87c372..a62919e705 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginHelper.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginHelper.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -25,8 +26,6 @@ import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.OidcPrompt import io.element.android.libraries.oidc.api.OidcAction import io.element.android.libraries.oidc.api.OidcActionFlow -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch /** * This class is responsible for managing the login flow, including handling OIDC actions and @@ -58,15 +57,13 @@ class LoginHelper( loginModeState.value = AsyncData.Uninitialized } - fun submit( - coroutineScope: CoroutineScope, + suspend fun submit( isAccountCreation: Boolean, homeserverUrl: String, loginHint: String?, - ) = coroutineScope.launch { + ) { suspend { - authenticationService.setHomeserver(homeserverUrl).map { - val matrixHomeServerDetails = authenticationService.getHomeserverDetails().value!! + authenticationService.setHomeserver(homeserverUrl).map { matrixHomeServerDetails -> if (matrixHomeServerDetails.supportsOidcLogin) { // Retrieve the details right now val oidcPrompt = if (isAccountCreation) OidcPrompt.Create else OidcPrompt.Login diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginMode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginMode.kt index 571b018297..08e604ef20 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginMode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginMode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeView.kt index c3fe5eac47..f88e34bf4a 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -41,9 +42,20 @@ fun LoginModeView( when (val error = loginMode.error) { is ChangeServerError -> { when (error) { + ChangeServerError.InvalidServer -> + ErrorDialog( + content = stringResource(R.string.screen_change_server_error_invalid_homeserver), + onSubmit = onClearError, + ) + is ChangeServerError.UnsupportedServer -> { + ErrorDialog( + content = stringResource(R.string.screen_login_error_unsupported_authentication), + onSubmit = onClearError, + ) + } is ChangeServerError.Error -> { ErrorDialog( - content = error.message(), + content = error.messageStr ?: stringResource(CommonStrings.error_unknown), onSubmit = onClearError, ) } @@ -91,7 +103,7 @@ fun LoginModeView( } is AuthenticationException.AccountAlreadyLoggedIn -> { ErrorDialog( - content = stringResource(CommonStrings.error_account_already_logged_in, error.message.orEmpty()), + content = stringResource(CommonStrings.error_account_already_logged_in, error.userId), onSubmit = onClearError, ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeViewErrorProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeViewErrorProvider.kt index dd0a7f353c..513d0a8a04 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeViewErrorProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeViewErrorProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManager.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManager.kt index 42bada93a0..a25d810c25 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManager.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/DefaultQrCodeLoginManager.kt @@ -1,14 +1,14 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.login.impl.qrcode import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.features.login.impl.di.QrCodeLoginScope import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService @@ -21,7 +21,6 @@ import kotlinx.coroutines.flow.StateFlow @SingleIn(QrCodeLoginScope::class) @ContributesBinding(QrCodeLoginScope::class) -@Inject class DefaultQrCodeLoginManager( private val authenticationService: MatrixAuthenticationService, ) : QrCodeLoginManager { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt index 22dcb9da9c..7dad95741b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -147,11 +148,11 @@ class QrCodeLoginFlowNode( return when (navTarget) { is NavTarget.Initial -> { val callback = object : QrCodeIntroNode.Callback { - override fun onCancelClicked() { + override fun cancel() { navigateUp() } - override fun onContinue() { + override fun navigateToQrCodeScan() { backstack.push(NavTarget.QrCodeScan) } } @@ -159,11 +160,11 @@ class QrCodeLoginFlowNode( } is NavTarget.QrCodeScan -> { val callback = object : QrCodeScanNode.Callback { - override fun onScannedCode(qrCodeLoginData: MatrixQrCodeLoginData) { + override fun handleScannedCode(qrCodeLoginData: MatrixQrCodeLoginData) { lifecycleScope.startAuthentication(qrCodeLoginData) } - override fun onCancelClicked() { + override fun cancel() { backstack.pop() } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt index f1ae224f56..5f75403443 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/qrcode/QrCodeLoginManager.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverData.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverData.kt index 0b4b088938..d9994deb9a 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverData.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverData.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,6 +11,4 @@ package io.element.android.features.login.impl.resolver data class HomeserverData( // The computed homeserver url, for which a wellknown file has been retrieved, or just a valid Url val homeserverUrl: String, - // True if a wellknown file has been found and is valid. If false, it means that the [homeserverUrl] is valid - val isWellknownValid: Boolean, ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt index 5612a56d5e..1c4ef8cd09 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/HomeserverResolver.kt @@ -1,26 +1,24 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.login.impl.resolver import dev.zacsweers.metro.Inject -import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.parallelMap -import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.core.uri.isValidUrl -import io.element.android.libraries.wellknown.api.WellKnown -import io.element.android.libraries.wellknown.api.WellknownRetriever +import io.element.android.libraries.matrix.api.auth.HomeServerLoginCompatibilityChecker import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeout +import timber.log.Timber import java.util.Collections /** @@ -29,7 +27,7 @@ import java.util.Collections @Inject class HomeserverResolver( private val dispatchers: CoroutineDispatchers, - private val wellknownRetriever: WellknownRetriever, + private val homeServerLoginCompatibilityChecker: HomeServerLoginCompatibilityChecker, ) { fun resolve(userInput: String): Flow> = flow { val flowContext = currentCoroutineContext() @@ -41,36 +39,24 @@ class HomeserverResolver( // Run all the requests in parallel withContext(dispatchers.io) { list.parallelMap { url -> - val wellKnown = tryOrNull { - withTimeout(5000) { - wellknownRetriever.getWellKnown(url) - } - } - val isValid = wellKnown?.isValid().orFalse() + val isValid = homeServerLoginCompatibilityChecker.check(url) + .onFailure { Timber.w(it, "Failed to check compatibility with homeserver $url") } + .getOrNull() + ?: return@parallelMap + + // Emit the list as soon as possible if (isValid) { - // Emit the list as soon as possible - currentList.add( - HomeserverData( - homeserverUrl = url, - isWellknownValid = true, - ) - ) + currentList.add(HomeserverData(homeserverUrl = url)) withContext(flowContext) { emit(currentList.toList()) } } } } - // If list is empty, and the user has entered an URL, do not block the user. - if (currentList.isEmpty() && trimmedUserInput.isValidUrl()) { - emit( - listOf( - HomeserverData( - homeserverUrl = trimmedUserInput, - isWellknownValid = false, - ) - ) - ) + // If list is empty, and candidateBase is a valid an URL, do not block the user. + // A unsupported homeserver / homeserver not found error will be displayed if the user continues + if (currentList.isEmpty() && candidateBase.isValidUrl()) { + emit(listOf(HomeserverData(homeserverUrl = candidateBase))) } } @@ -88,7 +74,3 @@ class HomeserverResolver( } } } - -private fun WellKnown.isValid(): Boolean { - return homeServer?.baseURL?.isNotBlank().orFalse() -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderNode.kt index a0587211c0..018a25dc89 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,12 +14,12 @@ import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.login.impl.util.openLearnMorePage +import io.element.android.libraries.architecture.callback @ContributesNode(AppScope::class) @AssistedInject @@ -29,16 +30,10 @@ class ChangeAccountProviderNode( ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { fun onDone() - fun onOtherClick() + fun navigateToSearchAccountProvider() } - private fun onDone() { - plugins().forEach { it.onDone() } - } - - private fun onOtherClick() { - plugins().forEach { it.onOtherClick() } - } + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { @@ -49,8 +44,8 @@ class ChangeAccountProviderNode( modifier = modifier, onBackClick = ::navigateUp, onLearnMoreClick = { openLearnMorePage(context) }, - onSuccess = ::onDone, - onOtherProviderClick = ::onOtherClick, + onSuccess = callback::onDone, + onOtherProviderClick = callback::navigateToSearchAccountProvider, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenter.kt index 3c725106c8..41605352ef 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,6 +18,7 @@ import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.changeserver.ChangeServerState import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.uri.ensureProtocol +import kotlinx.collections.immutable.toImmutableList @Inject class ChangeAccountProviderPresenter( @@ -36,9 +38,9 @@ class ChangeAccountProviderPresenter( subtitle = null, isPublic = url == AuthenticationConfig.MATRIX_ORG_URL, isMatrixOrg = url == AuthenticationConfig.MATRIX_ORG_URL, - isValid = true, ) } + .toImmutableList() } val canSearchForAccountProviders = remember { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderState.kt index e8e1f21cd8..3068a1cb57 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,10 +10,10 @@ package io.element.android.features.login.impl.screens.changeaccountprovider import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.changeserver.ChangeServerState +import kotlinx.collections.immutable.ImmutableList -// Do not use default value, so no member get forgotten in the presenters. data class ChangeAccountProviderState( - val accountProviders: List, + val accountProviders: ImmutableList, val canSearchForAccountProviders: Boolean, val changeServerState: ChangeServerState, ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderStateProvider.kt index 435eee7f89..5cec1961a4 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,6 +13,7 @@ import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.accountprovider.anAccountProvider import io.element.android.features.login.impl.changeserver.ChangeServerState import io.element.android.features.login.impl.changeserver.aChangeServerState +import kotlinx.collections.immutable.toImmutableList open class ChangeAccountProviderStateProvider : PreviewParameterProvider { override val values: Sequence @@ -29,7 +31,7 @@ fun aChangeAccountProviderState( canSearchForAccountProviders: Boolean = true, changeServerState: ChangeServerState = aChangeServerState(), ) = ChangeAccountProviderState( - accountProviders = accountProviders, + accountProviders = accountProviders.toImmutableList(), canSearchForAccountProviders = canSearchForAccountProviders, changeServerState = changeServerState, ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt index c4542dfb0d..4f6c87ac73 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderEvents.kt index 76c86d18c2..f60cc3e9da 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderNode.kt index 128d235c93..5dc6ebbd6b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderNode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,12 +14,12 @@ import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.login.impl.util.openLearnMorePage +import io.element.android.libraries.architecture.callback import io.element.android.libraries.matrix.api.auth.OidcDetails @ContributesNode(AppScope::class) @@ -29,22 +30,12 @@ class ChooseAccountProviderNode( private val presenter: ChooseAccountProviderPresenter, ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun onLoginPasswordNeeded() - fun onOidcDetails(oidcDetails: OidcDetails) - fun onCreateAccountContinue(url: String) + fun navigateToLoginPassword() + fun navigateToOidc(oidcDetails: OidcDetails) + fun navigateToCreateAccount(url: String) } - private fun onOidcDetails(oidcDetails: OidcDetails) { - plugins().forEach { it.onOidcDetails(oidcDetails) } - } - - private fun onLoginPasswordNeeded() { - plugins().forEach { it.onLoginPasswordNeeded() } - } - - private fun onCreateAccountContinue(url: String) { - plugins().forEach { it.onCreateAccountContinue(url) } - } + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { @@ -54,10 +45,10 @@ class ChooseAccountProviderNode( state = state, modifier = modifier, onBackClick = ::navigateUp, - onOidcDetails = ::onOidcDetails, - onNeedLoginPassword = ::onLoginPasswordNeeded, + onOidcDetails = callback::navigateToOidc, + onNeedLoginPassword = callback::navigateToLoginPassword, onLearnMoreClick = { openLearnMorePage(context) }, - onCreateAccountContinue = ::onCreateAccountContinue, + onCreateAccountContinue = callback::navigateToCreateAccount, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenter.kt index d259454f18..87010a4a30 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderPresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -21,6 +22,8 @@ import io.element.android.features.login.impl.login.LoginHelper import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.uri.ensureProtocol +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.launch @Inject class ChooseAccountProviderPresenter( @@ -36,10 +39,9 @@ class ChooseAccountProviderPresenter( fun handleEvent(event: ChooseAccountProviderEvents) { when (event) { - ChooseAccountProviderEvents.Continue -> { + ChooseAccountProviderEvents.Continue -> localCoroutineScope.launch { selectedAccountProvider?.let { loginHelper.submit( - coroutineScope = localCoroutineScope, isAccountCreation = false, homeserverUrl = it.url, loginHint = null, @@ -66,9 +68,9 @@ class ChooseAccountProviderPresenter( subtitle = null, isPublic = url == AuthenticationConfig.MATRIX_ORG_URL, isMatrixOrg = url == AuthenticationConfig.MATRIX_ORG_URL, - isValid = true, ) } + .toImmutableList() } return ChooseAccountProviderState( diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderState.kt index 3591334047..e34fbccb41 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,10 +11,10 @@ package io.element.android.features.login.impl.screens.chooseaccountprovider import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.login.LoginMode import io.element.android.libraries.architecture.AsyncData +import kotlinx.collections.immutable.ImmutableList -// Do not use default value, so no member get forgotten in the presenters. data class ChooseAccountProviderState( - val accountProviders: List, + val accountProviders: ImmutableList, val selectedAccountProvider: AccountProvider?, val loginMode: AsyncData, val eventSink: (ChooseAccountProviderEvents) -> Unit, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderStateProvider.kt index b921fee330..93bd665011 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,6 +13,7 @@ import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.accountprovider.anAccountProvider import io.element.android.features.login.impl.login.LoginMode import io.element.android.libraries.architecture.AsyncData +import kotlinx.collections.immutable.toImmutableList open class ChooseAccountProviderStateProvider : PreviewParameterProvider { private val server1 = anAccountProvider( @@ -70,7 +72,7 @@ fun aChooseAccountProviderState( loginMode: AsyncData = AsyncData.Uninitialized, eventSink: (ChooseAccountProviderEvents) -> Unit = {}, ) = ChooseAccountProviderState( - accountProviders = accountProviders, + accountProviders = accountProviders.toImmutableList(), selectedAccountProvider = selectedAccountProvider, loginMode = loginMode, eventSink = eventSink, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt index 760e39ce85..cdb80304a7 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderEvents.kt index 8fe5a6c814..d66606b22c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt index 0d50d21a18..e3643afbf2 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,13 +14,13 @@ import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.login.impl.util.openLearnMorePage import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs import io.element.android.libraries.matrix.api.auth.OidcDetails @@ -42,27 +43,13 @@ class ConfirmAccountProviderNode( ) interface Callback : Plugin { - fun onLoginPasswordNeeded() - fun onOidcDetails(oidcDetails: OidcDetails) - fun onCreateAccountContinue(url: String) - fun onChangeAccountProvider() + fun navigateToLoginPassword() + fun navigateToOidc(oidcDetails: OidcDetails) + fun navigateToCreateAccount(url: String) + fun navigateToChangeAccountProvider() } - private fun onOidcDetails(data: OidcDetails) { - plugins().forEach { it.onOidcDetails(data) } - } - - private fun onLoginPasswordNeeded() { - plugins().forEach { it.onLoginPasswordNeeded() } - } - - private fun onCreateAccountContinue(url: String) { - plugins().forEach { it.onCreateAccountContinue(url) } - } - - private fun onChangeAccountProvider() { - plugins().forEach { it.onChangeAccountProvider() } - } + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { @@ -71,10 +58,10 @@ class ConfirmAccountProviderNode( ConfirmAccountProviderView( state = state, modifier = modifier, - onOidcDetails = ::onOidcDetails, - onNeedLoginPassword = ::onLoginPasswordNeeded, - onCreateAccountContinue = ::onCreateAccountContinue, - onChange = ::onChangeAccountProvider, + onOidcDetails = callback::navigateToOidc, + onNeedLoginPassword = callback::navigateToLoginPassword, + onCreateAccountContinue = callback::navigateToCreateAccount, + onChange = callback::navigateToChangeAccountProvider, onLearnMoreClick = { openLearnMorePage(context) }, ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt index d485755afb..c38da7b11c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,6 +18,7 @@ import dev.zacsweers.metro.AssistedInject import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.login.LoginHelper import io.element.android.libraries.architecture.Presenter +import kotlinx.coroutines.launch @AssistedInject class ConfirmAccountProviderPresenter( @@ -40,11 +42,10 @@ class ConfirmAccountProviderPresenter( val loginMode by loginHelper.collectLoginMode() - fun handleEvents(event: ConfirmAccountProviderEvents) { + fun handleEvent(event: ConfirmAccountProviderEvents) { when (event) { - ConfirmAccountProviderEvents.Continue -> { + ConfirmAccountProviderEvents.Continue -> localCoroutineScope.launch { loginHelper.submit( - coroutineScope = localCoroutineScope, isAccountCreation = params.isAccountCreation, homeserverUrl = accountProvider.url, loginHint = null, @@ -58,7 +59,7 @@ class ConfirmAccountProviderPresenter( accountProvider = accountProvider, isAccountCreation = params.isAccountCreation, loginMode = loginMode, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt index d0da9ab85e..b29b610b3e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,6 @@ import io.element.android.features.login.impl.accountprovider.AccountProvider import io.element.android.features.login.impl.login.LoginMode import io.element.android.libraries.architecture.AsyncData -// Do not use default value, so no member get forgotten in the presenters. data class ConfirmAccountProviderState( val accountProvider: AccountProvider, val isAccountCreation: Boolean, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStateProvider.kt index 90d34571e9..f3a48a86b4 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt index 8ecfbb5628..a175ab556d 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/AccountCreationNotSupported.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/AccountCreationNotSupported.kt index 019a0d4e1f..f91eea25ce 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/AccountCreationNotSupported.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/AccountCreationNotSupported.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountEvents.kt index e16c6d8f9a..a51557347e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountNode.kt index 44e4bde551..e1972e5a0f 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountPresenter.kt index a3aec0eb81..bf7af111f4 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,24 +19,18 @@ import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedInject import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.extensions.flatMap import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.meta.BuildMeta -import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.core.SessionId import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch -import kotlinx.coroutines.withTimeout -import kotlin.time.Duration.Companion.seconds @AssistedInject class CreateAccountPresenter( @Assisted private val url: String, private val authenticationService: MatrixAuthenticationService, - private val clientProvider: MatrixClientProvider, private val messageParser: MessageParser, private val buildMeta: BuildMeta, ) : Presenter { @@ -50,7 +45,7 @@ class CreateAccountPresenter( val pageProgress: MutableState = remember { mutableIntStateOf(0) } val createAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } - fun handleEvents(event: CreateAccountEvents) { + fun handleEvent(event: CreateAccountEvents) { when (event) { is CreateAccountEvents.SetPageProgress -> { pageProgress.value = event.progress @@ -68,7 +63,7 @@ class CreateAccountPresenter( pageProgress = pageProgress.value, isDebugBuild = buildMeta.isDebuggable, createAction = createAction.value, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } @@ -79,12 +74,6 @@ class CreateAccountPresenter( }.flatMap { externalSession -> authenticationService.importCreatedSession(externalSession) }.onSuccess { sessionId -> - tryOrNull { - // Wait until the session is verified - val client = clientProvider.getOrRestore(sessionId).getOrThrow() - val sessionVerificationService = client.sessionVerificationService - withTimeout(10.seconds) { sessionVerificationService.sessionVerifiedStatus.first { it.isVerified() } } - } loggedInState.value = AsyncAction.Success(sessionId) }.onFailure { failure -> loggedInState.value = AsyncAction.Failure(failure) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountState.kt index 13ffd11ed5..de7efe573e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountStateProvider.kt index e2e7ac3251..976ceae70d 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountView.kt index 2ad96edf75..051188627a 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MessageParser.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MessageParser.kt index 8450aef1d3..d7954373bc 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MessageParser.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MessageParser.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,10 +10,9 @@ package io.element.android.features.login.impl.screens.createaccount import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource +import io.element.android.libraries.androidutils.json.JsonProvider import io.element.android.libraries.matrix.api.auth.external.ExternalSession -import kotlinx.serialization.json.Json interface MessageParser { /** @@ -23,13 +23,12 @@ interface MessageParser { } @ContributesBinding(AppScope::class) -@Inject class DefaultMessageParser( private val accountProviderDataSource: AccountProviderDataSource, + private val json: JsonProvider, ) : MessageParser { override fun parse(message: String): ExternalSession { - val parser = Json { ignoreUnknownKeys = true } - val response = parser.decodeFromString(MobileRegistrationResponse.serializer(), message) + val response = json().decodeFromString(MobileRegistrationResponse.serializer(), message) val userId = response.userId ?: error("No user ID in response") val homeServer = response.homeServer ?: accountProviderDataSource.flow.value.url val accessToken = response.accessToken ?: error("No access token in response") @@ -40,7 +39,6 @@ class DefaultMessageParser( accessToken = accessToken, deviceId = deviceId, refreshToken = null, - slidingSyncProxy = null ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MobileRegistrationResponse.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MobileRegistrationResponse.kt index 96f07585d6..5e78b1bd37 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MobileRegistrationResponse.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MobileRegistrationResponse.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/WebViewMessageInterceptor.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/WebViewMessageInterceptor.kt index 02e872090e..20d503522b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/WebViewMessageInterceptor.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/WebViewMessageInterceptor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordEvents.kt index e5fb57347e..25a003b531 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordNode.kt index ce3d2a84fa..c6ce16141d 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt index 80a5711d52..b1ddc6e5b8 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -41,7 +42,7 @@ class LoginPasswordPresenter( } val accountProvider by accountProviderDataSource.flow.collectAsState() - fun handleEvents(event: LoginPasswordEvents) { + fun handleEvent(event: LoginPasswordEvents) { when (event) { is LoginPasswordEvents.SetLogin -> updateFormState(formState) { copy(login = event.login) @@ -60,7 +61,7 @@ class LoginPasswordPresenter( accountProvider = accountProvider, formState = formState.value, loginAction = loginAction.value, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } 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 18bac19633..d8adc7351f 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordStateProvider.kt index af6c07146e..2183790365 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt index 5cb0c23d9c..d3641ea749 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingEvents.kt index 5e0cc054fc..6101b21ded 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingLogoResIdProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingLogoResIdProvider.kt index 73ac06bbe2..a1a0266f34 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingLogoResIdProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingLogoResIdProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,6 @@ import android.annotation.SuppressLint import android.content.Context import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.annotations.ApplicationContext fun interface OnBoardingLogoResIdProvider { @@ -19,7 +19,6 @@ fun interface OnBoardingLogoResIdProvider { } @ContributesBinding(AppScope::class) -@Inject class DefaultOnBoardingLogoResIdProvider( @ApplicationContext private val context: Context, ) : OnBoardingLogoResIdProvider { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingNode.kt index 3652a3df8d..1ded677c13 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,13 +14,13 @@ import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.login.impl.util.openLearnMorePage import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs import io.element.android.libraries.matrix.api.auth.OidcDetails @@ -34,13 +35,14 @@ class OnBoardingNode( plugins = plugins ) { interface Callback : Plugin { - fun onSignUp() - fun onSignIn(mustChooseAccountProvider: Boolean) - fun onSignInWithQrCode() - fun onReportProblem() - fun onLoginPasswordNeeded() - fun onOidcDetails(oidcDetails: OidcDetails) - fun onCreateAccountContinue(url: String) + fun navigateToSignUpFlow() + fun navigateToSignInFlow(mustChooseAccountProvider: Boolean) + fun navigateToQrCode() + fun navigateToBugReport() + fun navigateToLoginPassword() + fun navigateToOidc(oidcDetails: OidcDetails) + fun navigateToCreateAccount(url: String) + fun onDone() } data class Params( @@ -48,40 +50,13 @@ class OnBoardingNode( val loginHint: String?, ) : NodeInputs + private val callback: Callback = callback() private val params = inputs() private val presenter = presenterFactory.create( params = params, ) - private fun onSignIn(mustChooseAccountProvider: Boolean) { - plugins().forEach { it.onSignIn(mustChooseAccountProvider) } - } - - private fun onSignUp() { - plugins().forEach { it.onSignUp() } - } - - private fun onSignInWithQrCode() { - plugins().forEach { it.onSignInWithQrCode() } - } - - private fun onReportProblem() { - plugins().forEach { it.onReportProblem() } - } - - private fun onOidcDetails(data: OidcDetails) { - plugins().forEach { it.onOidcDetails(data) } - } - - private fun onLoginPasswordNeeded() { - plugins().forEach { it.onLoginPasswordNeeded() } - } - - private fun onCreateAccountContinue(url: String) { - plugins().forEach { it.onCreateAccountContinue(url) } - } - @Composable override fun View(modifier: Modifier) { val state = presenter.present() @@ -89,15 +64,15 @@ class OnBoardingNode( OnBoardingView( state = state, modifier = modifier, - onSignIn = ::onSignIn, - onCreateAccount = ::onSignUp, - onSignInWithQrCode = ::onSignInWithQrCode, - onReportProblem = ::onReportProblem, - onOidcDetails = ::onOidcDetails, - onNeedLoginPassword = ::onLoginPasswordNeeded, + onSignIn = callback::navigateToSignInFlow, + onCreateAccount = callback::navigateToSignUpFlow, + onSignInWithQrCode = callback::navigateToQrCode, + onReportProblem = callback::navigateToBugReport, + onOidcDetails = callback::navigateToOidc, + onNeedLoginPassword = callback::navigateToLoginPassword, onLearnMoreClick = { openLearnMorePage(context) }, - onCreateAccountContinue = ::onCreateAccountContinue, - onBackClick = ::navigateUp, + onCreateAccountContinue = callback::navigateToCreateAccount, + onBackClick = callback::onDone, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt index e7e20aa70d..4d83c45a44 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -23,12 +24,14 @@ import io.element.android.appconfig.OnBoardingConfig import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.api.canConnectToAnyHomeserver import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl +import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.features.login.impl.login.LoginHelper import io.element.android.features.rageshake.api.RageshakeFeatureAvailability import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.ui.utils.MultipleTapToUnlock +import kotlinx.coroutines.launch @AssistedInject class OnBoardingPresenter( @@ -40,6 +43,7 @@ class OnBoardingPresenter( private val loginHelper: LoginHelper, private val onBoardingLogoResIdProvider: OnBoardingLogoResIdProvider, private val sessionStore: SessionStore, + private val accountProviderDataSource: AccountProviderDataSource, ) : Presenter { @AssistedFactory interface Factory { @@ -90,19 +94,22 @@ class OnBoardingPresenter( } val isAddingAccount by produceState(initialValue = false) { // We are adding an account if there is at least one session already stored - value = sessionStore.getAllSessions().isNotEmpty() + value = sessionStore.numberOfSessions() > 0 } val loginMode by loginHelper.collectLoginMode() fun handleEvent(event: OnBoardingEvents) { when (event) { - is OnBoardingEvents.OnSignIn -> loginHelper.submit( - coroutineScope = localCoroutineScope, - isAccountCreation = false, - homeserverUrl = event.defaultAccountProvider, - loginHint = params.loginHint?.takeIf { forcedAccountProvider == null }, - ) + is OnBoardingEvents.OnSignIn -> localCoroutineScope.launch { + // Ensure that the current account provider is set + accountProviderDataSource.setUrl(event.defaultAccountProvider) + loginHelper.submit( + isAccountCreation = false, + homeserverUrl = event.defaultAccountProvider, + loginHint = params.loginHint?.takeIf { forcedAccountProvider == null }, + ) + } OnBoardingEvents.ClearError -> loginHelper.clearError() OnBoardingEvents.OnVersionClick -> { if (canReportBug) { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingState.kt index ae5bb79eb5..db6c3573f9 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingStateProvider.kt index 2eb9bfb301..d7db27ca0b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt index fbc4dc6d09..977c6de71c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt index 8b8d5b2dd5..b3b5ee8031 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,11 +13,11 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.login.impl.di.QrCodeLoginScope +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs @ContributesNode(QrCodeLoginScope::class) @@ -29,17 +30,14 @@ class QrCodeConfirmationNode( fun onCancel() } + private val callback: Callback = callback() private val step = inputs() - private fun onCancel() { - plugins().forEach { it.onCancel() } - } - @Composable override fun View(modifier: Modifier) { QrCodeConfirmationView( step = step, - onCancel = ::onCancel, + onCancel = callback::onCancel, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationStep.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationStep.kt index c285a5631e..6aacebe6b4 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationStep.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationStep.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationStepProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationStepProvider.kt index ea0e180284..17864f68a1 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationStepProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationStepProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt index 99bb11662e..8de93bcb0f 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/confirmation/QrCodeConfirmationView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt index 7b46c2e45c..4dc1e48e51 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,12 +13,12 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.login.impl.di.QrCodeLoginScope import io.element.android.features.login.impl.qrcode.QrCodeErrorScreenType +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs import io.element.android.libraries.core.meta.BuildMeta @@ -32,10 +33,7 @@ class QrCodeErrorNode( fun onRetry() } - private fun onRetry() { - plugins().forEach { it.onRetry() } - } - + private val callback: Callback = callback() private val qrCodeErrorScreenType = inputs() @Composable @@ -44,7 +42,7 @@ class QrCodeErrorNode( modifier = modifier, errorScreenType = qrCodeErrorScreenType, appName = buildMeta.productionApplicationName, - onRetry = ::onRetry, + onRetry = callback::onRetry, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt index 6f3731bd75..d2ec6ce192 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/error/QrCodeErrorView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroEvents.kt index f567569d80..11abd63683 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroNode.kt index c86dc6096a..5ff5f09d53 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,11 +13,11 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.login.impl.di.QrCodeLoginScope +import io.element.android.libraries.architecture.callback @ContributesNode(QrCodeLoginScope::class) @AssistedInject @@ -26,25 +27,19 @@ class QrCodeIntroNode( private val presenter: QrCodeIntroPresenter, ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun onCancelClicked() - fun onContinue() + fun cancel() + fun navigateToQrCodeScan() } - private fun onCancelClicked() { - plugins().forEach { it.onCancelClicked() } - } - - private fun onContinue() { - plugins().forEach { it.onContinue() } - } + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { val state = presenter.present() QrCodeIntroView( state = state, - onBackClick = ::onCancelClicked, - onContinue = ::onContinue, + onBackClick = callback::cancel, + onContinue = callback::navigateToQrCodeScan, modifier = modifier ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt index b90e2a6aeb..13888fe23f 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,7 +18,7 @@ import androidx.compose.runtime.setValue import dev.zacsweers.metro.Inject import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.meta.BuildMeta -import io.element.android.libraries.permissions.api.PermissionsEvents +import io.element.android.libraries.permissions.api.PermissionsEvent import io.element.android.libraries.permissions.api.PermissionsPresenter @Inject @@ -39,13 +40,13 @@ class QrCodeIntroPresenter( } } - fun handleEvents(event: QrCodeIntroEvents) { + fun handleEvent(event: QrCodeIntroEvents) { when (event) { QrCodeIntroEvents.Continue -> if (cameraPermissionState.permissionGranted) { canContinue = true } else { pendingPermissionRequest = true - cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions) + cameraPermissionState.eventSink(PermissionsEvent.RequestPermissions) } } } @@ -55,7 +56,7 @@ class QrCodeIntroPresenter( desktopAppName = buildMeta.desktopApplicationName, cameraPermissionState = cameraPermissionState, canContinue = canContinue, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroState.kt index e2f878603f..d20a3f58d9 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroStateProvider.kt index fa3faa50a0..22c7fd67f6 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt index 3125c0fcca..767dc49999 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/intro/QrCodeIntroView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanEvents.kt index 574604903e..f5804aceb0 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt index f6b52522b5..987221eb94 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,11 +13,11 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.login.impl.di.QrCodeLoginScope +import io.element.android.libraries.architecture.callback import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData @ContributesNode(QrCodeLoginScope::class) @@ -27,25 +28,19 @@ class QrCodeScanNode( private val presenter: QrCodeScanPresenter, ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun onScannedCode(qrCodeLoginData: MatrixQrCodeLoginData) - fun onCancelClicked() + fun handleScannedCode(qrCodeLoginData: MatrixQrCodeLoginData) + fun cancel() } - private fun onQrCodeDataReady(qrCodeLoginData: MatrixQrCodeLoginData) { - plugins().forEach { it.onScannedCode(qrCodeLoginData) } - } - - private fun onCancelClicked() { - plugins().forEach { it.onCancelClicked() } - } + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { val state = presenter.present() QrCodeScanView( state = state, - onQrCodeDataReady = ::onQrCodeDataReady, - onBackClick = ::onCancelClicked, + onQrCodeDataReady = callback::handleScannedCode, + onBackClick = callback::cancel, modifier = modifier ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenter.kt index ed612f2ac3..2f93d5b214 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -53,7 +54,7 @@ class QrCodeScanPresenter( authenticationAction.value = AsyncAction.Failure(it) } - fun handleEvents(event: QrCodeScanEvents) { + fun handleEvent(event: QrCodeScanEvents) { when (event) { QrCodeScanEvents.TryAgain -> { isScanning = true @@ -69,7 +70,7 @@ class QrCodeScanPresenter( return QrCodeScanState( isScanning = isScanning, authenticationAction = authenticationAction.value, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanState.kt index fb72767a3d..0e64bfc6a6 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanStateProvider.kt index dd0c09344e..0d467efb71 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanView.kt index c4892e9c3e..6fa3d1b0c7 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/qrcode/scan/QrCodeScanView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -105,7 +106,7 @@ private fun Content( QrCodeCameraView( modifier = Modifier.fillMaxSize(), onScanQrCode = { state.eventSink.invoke(QrCodeScanEvents.QrCodeScanned(it)) }, - renderPreview = state.isScanning, + isScanning = state.isScanning, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderEvents.kt index 4fafe494bb..8816de3946 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderEvents.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderNode.kt index dc6084032e..ddbcc4c847 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,12 +14,12 @@ import androidx.compose.ui.platform.LocalContext import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.login.impl.util.openLearnMorePage +import io.element.android.libraries.architecture.callback @ContributesNode(AppScope::class) @AssistedInject @@ -31,9 +32,7 @@ class SearchAccountProviderNode( fun onDone() } - private fun onDone() { - plugins().forEach { it.onDone() } - } + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { @@ -44,7 +43,7 @@ class SearchAccountProviderNode( modifier = modifier, onBackClick = ::navigateUp, onLearnMoreClick = { openLearnMorePage(context) }, - onSuccess = ::onDone, + onSuccess = callback::onDone, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenter.kt index 0aa06ca632..2efe4501c6 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -45,7 +46,7 @@ class SearchAccountProviderPresenter( onUserInput(userInput, data) } - fun handleEvents(event: SearchAccountProviderEvents) { + fun handleEvent(event: SearchAccountProviderEvents) { when (event) { is SearchAccountProviderEvents.UserInput -> { userInput = event.input @@ -57,14 +58,14 @@ class SearchAccountProviderPresenter( userInput = userInput, userInputResult = data.value, changeServerState = changeServerState, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } private fun CoroutineScope.onUserInput(userInput: String, data: MutableState>>) = launch { data.value = AsyncData.Uninitialized // Debounce - delay(300) + delay(500) data.value = AsyncData.Loading() homeserverResolver.resolve(userInput).collect { data.value = AsyncData.Success(it) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderState.kt index 4fb0a08141..dc02a997a6 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,6 @@ import io.element.android.features.login.impl.changeserver.ChangeServerState import io.element.android.features.login.impl.resolver.HomeserverData import io.element.android.libraries.architecture.AsyncData -// Do not use default value, so no member get forgotten in the presenters. data class SearchAccountProviderState( val userInput: String, val userInputResult: AsyncData>, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderStateProvider.kt index fb0e0f5c5a..2a8512c38a 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -34,18 +35,14 @@ fun aSearchAccountProviderState( fun aHomeserverDataList(): List { return listOf( - aHomeserverData(isWellknownValid = true), - aHomeserverData(homeserverUrl = "https://no.sliding.sync", isWellknownValid = true), - aHomeserverData(homeserverUrl = "https://invalid", isWellknownValid = false), + aHomeserverData(homeserverUrl = AuthenticationConfig.MATRIX_ORG_URL), + aHomeserverData(homeserverUrl = "https://no.sliding.sync"), + aHomeserverData(homeserverUrl = "https://invalid"), ) } fun aHomeserverData( homeserverUrl: String = AuthenticationConfig.MATRIX_ORG_URL, - isWellknownValid: Boolean = true, ): HomeserverData { - return HomeserverData( - homeserverUrl = homeserverUrl, - isWellknownValid = isWellknownValid, - ) + return HomeserverData(homeserverUrl = homeserverUrl) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt index 13bfd9e38e..55c2c28f5b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -192,7 +193,6 @@ private fun HomeserverData.toAccountProvider(): AccountProvider { // There is no need to know for other servers right now isPublic = isMatrixOrg, isMatrixOrg = isMatrixOrg, - isValid = isWellknownValid, ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/Util.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/Util.kt index 36ee64c600..06fbf12f4d 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/Util.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/util/Util.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/web/WebClientUrlForAuthenticationRetriever.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/web/WebClientUrlForAuthenticationRetriever.kt index f72af823f2..df08cf468b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/web/WebClientUrlForAuthenticationRetriever.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/web/WebClientUrlForAuthenticationRetriever.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.features.login.impl.web import androidx.core.net.toUri import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.appconfig.AuthenticationConfig import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported import io.element.android.libraries.wellknown.api.WellknownRetriever @@ -21,7 +21,6 @@ interface WebClientUrlForAuthenticationRetriever { } @ContributesBinding(AppScope::class) -@Inject class DefaultWebClientUrlForAuthenticationRetriever( private val wellknownRetriever: WellknownRetriever, ) : WebClientUrlForAuthenticationRetriever { @@ -30,7 +29,7 @@ class DefaultWebClientUrlForAuthenticationRetriever( Timber.w("Temporary account creation flow is only supported on matrix.org") throw AccountCreationNotSupported() } - val wellknown = wellknownRetriever.getElementWellKnown(homeServerUrl) + val wellknown = wellknownRetriever.getElementWellKnown(homeServerUrl).dataOrNull() ?: throw AccountCreationNotSupported() val registrationHelperUrl = wellknown.registrationHelperUrl return if (registrationHelperUrl != null) { diff --git a/features/login/impl/src/main/res/raw/keep.xml b/features/login/impl/src/main/res/raw/keep.xml index 478b6d4016..b013056445 100644 --- a/features/login/impl/src/main/res/raw/keep.xml +++ b/features/login/impl/src/main/res/raw/keep.xml @@ -1,7 +1,8 @@ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPoint.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPoint.kt index b27bcab3b8..0293a400a8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPoint.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,36 +10,20 @@ package io.element.android.features.messages.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.messages.api.MessagesEntryPoint -import io.element.android.libraries.architecture.NodeFactoriesBindings -import io.element.android.libraries.architecture.bindings +import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope @ContributesBinding(SessionScope::class) -@Inject class DefaultMessagesEntryPoint : MessagesEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): MessagesEntryPoint.NodeBuilder { - val nodeFactories = parentNode.bindings().nodeFactories() - val plugins = ArrayList() - - return object : MessagesEntryPoint.NodeBuilder { - override fun params(params: MessagesEntryPoint.Params): MessagesEntryPoint.NodeBuilder { - plugins += MessagesEntryPoint.Params(params.initialTarget) - return this - } - - override fun callback(callback: MessagesEntryPoint.Callback): MessagesEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return nodeFactories[MessagesFlowNode::class]!!.create(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: MessagesEntryPoint.Params, + callback: MessagesEntryPoint.Callback, + ): Node { + return parentNode.createNode(buildContext, listOf(params, callback)) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt index 2e035b6299..2419d76038 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,6 +19,7 @@ sealed interface MessagesEvents { data class InviteDialogDismissed(val action: InviteDialogAction) : MessagesEvents data class OnUserClicked(val user: MatrixUser) : MessagesEvents data object Dismiss : MessagesEvents + data object MarkAsFullyReadAndExit : MessagesEvents } enum class InviteDialogAction { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt index 82aafcf545..f0b88e1956 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,8 +17,8 @@ import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject @@ -25,6 +26,7 @@ import im.vector.app.features.analytics.plan.Interaction import io.element.android.annotations.ContributesNode import io.element.android.features.call.api.CallType import io.element.android.features.call.api.ElementCallEntryPoint +import io.element.android.features.forward.api.ForwardEntryPoint import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint import io.element.android.features.location.api.Location import io.element.android.features.location.api.LocationService @@ -33,8 +35,7 @@ import io.element.android.features.location.api.ShowLocationEntryPoint import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewNode -import io.element.android.features.messages.impl.forward.ForwardMessagesNode -import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider +import io.element.android.features.messages.impl.pinned.DefaultPinnedEventsTimelineProvider import io.element.android.features.messages.impl.pinned.list.PinnedMessagesListNode import io.element.android.features.messages.impl.report.ReportMessageNode import io.element.android.features.messages.impl.threads.ThreadedMessagesNode @@ -53,6 +54,7 @@ import io.element.android.features.poll.api.create.CreatePollEntryPoint import io.element.android.features.poll.api.create.CreatePollMode import io.element.android.libraries.architecture.BackstackWithOverlayBox import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.overlay.Overlay import io.element.android.libraries.architecture.overlay.operation.hide @@ -86,10 +88,12 @@ import io.element.android.libraries.textcomposer.mentions.MentionSpanUpdater import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.withContext import kotlinx.parcelize.Parcelize +import kotlin.time.Duration.Companion.milliseconds @ContributesNode(RoomScope::class) @AssistedInject @@ -103,6 +107,7 @@ class MessagesFlowNode( private val createPollEntryPoint: CreatePollEntryPoint, private val elementCallEntryPoint: ElementCallEntryPoint, private val mediaViewerEntryPoint: MediaViewerEntryPoint, + private val forwardEntryPoint: ForwardEntryPoint, private val analyticsService: AnalyticsService, private val locationService: LocationService, private val room: BaseRoom, @@ -110,7 +115,7 @@ class MessagesFlowNode( private val roomNamesCache: RoomNamesCache, private val mentionSpanUpdater: MentionSpanUpdater, private val mentionSpanTheme: MentionSpanTheme, - private val pinnedEventsTimelineProvider: PinnedEventsTimelineProvider, + private val pinnedEventsTimelineProvider: DefaultPinnedEventsTimelineProvider, private val timelineController: TimelineController, private val knockRequestsListEntryPoint: KnockRequestsListEntryPoint, private val dateFormatter: DateFormatter, @@ -124,8 +129,8 @@ class MessagesFlowNode( savedStateMap = buildContext.savedStateMap, ), buildContext = buildContext, - plugins = plugins -) { + plugins = plugins, +), MessagesEntryPoint.NodeProxy { sealed interface NavTarget : Parcelable { @Parcelize data class Messages(val focusedEventId: EventId?) : NavTarget @@ -149,7 +154,10 @@ class MessagesFlowNode( data class EventDebugInfo(val eventId: EventId?, val debugInfo: TimelineItemDebugInfo) : NavTarget @Parcelize - data class ForwardEvent(val eventId: EventId, val fromPinnedEvents: Boolean) : NavTarget + data class ForwardEvent( + val eventId: EventId, + val fromPinnedEvents: Boolean, + ) : NavTarget @Parcelize data class ReportMessage(val eventId: EventId, val senderId: UserId) : NavTarget @@ -170,10 +178,10 @@ class MessagesFlowNode( data object KnockRequestsList : NavTarget @Parcelize - data class OpenThread(val threadRootId: ThreadId, val focusedEventId: EventId?) : NavTarget + data class Thread(val threadRootId: ThreadId, val focusedEventId: EventId?) : NavTarget } - private val callbacks = plugins() + private val callback: MessagesEntryPoint.Callback = callback() override fun onBuilt() { super.onBuilt() @@ -211,18 +219,18 @@ class MessagesFlowNode( return when (navTarget) { is NavTarget.Messages -> { val callback = object : MessagesNode.Callback { - override fun onRoomDetailsClick() { - callbacks.forEach { it.onRoomDetailsClick() } + override fun navigateToRoomDetails() { + callback.navigateToRoomDetails() } - override fun onEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event): Boolean { + override fun handleEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event): Boolean { return processEventClick( timelineMode = timelineMode, event = event, ) } - override fun onPreviewAttachments(attachments: ImmutableList, inReplyToEventId: EventId?) { + override fun navigateToPreviewAttachments(attachments: ImmutableList, inReplyToEventId: EventId?) { backstack.push( NavTarget.AttachmentPreview( attachment = attachments.first(), @@ -232,39 +240,39 @@ class MessagesFlowNode( ) } - override fun onUserDataClick(userId: UserId) { - callbacks.forEach { it.onUserDataClick(userId) } + override fun navigateToRoomMemberDetails(userId: UserId) { + callback.navigateToRoomMemberDetails(userId) } - override fun onPermalinkClick(data: PermalinkData) { - callbacks.forEach { it.onPermalinkClick(data, pushToBackstack = true) } + override fun handlePermalinkClick(data: PermalinkData) { + callback.handlePermalinkClick(data, pushToBackstack = true) } - override fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { + override fun navigateToEventDebugInfo(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { backstack.push(NavTarget.EventDebugInfo(eventId, debugInfo)) } - override fun onForwardEventClick(eventId: EventId) { + override fun forwardEvent(eventId: EventId) { backstack.push(NavTarget.ForwardEvent(eventId, fromPinnedEvents = false)) } - override fun onReportMessage(eventId: EventId, senderId: UserId) { + override fun navigateToReportMessage(eventId: EventId, senderId: UserId) { backstack.push(NavTarget.ReportMessage(eventId, senderId)) } - override fun onSendLocationClick() { + override fun navigateToSendLocation() { backstack.push(NavTarget.SendLocation(Timeline.Mode.Live)) } - override fun onCreatePollClick() { + override fun navigateToCreatePoll() { backstack.push(NavTarget.CreatePoll(Timeline.Mode.Live)) } - override fun onEditPollClick(eventId: EventId) { + override fun navigateToEditPoll(eventId: EventId) { backstack.push(NavTarget.EditPoll(Timeline.Mode.Live, eventId)) } - override fun onJoinCallClick(roomId: RoomId) { + override fun navigateToRoomCall(roomId: RoomId) { val callType = CallType.RoomCall( sessionId = sessionId, roomId = roomId, @@ -273,16 +281,16 @@ class MessagesFlowNode( elementCallEntryPoint.startCall(callType) } - override fun onViewAllPinnedEvents() { + override fun navigateToPinnedMessagesList() { backstack.push(NavTarget.PinnedMessagesList) } - override fun onViewKnockRequests() { + override fun navigateToKnockRequestsList() { backstack.push(NavTarget.KnockRequestsList) } - override fun onOpenThread(threadRootId: ThreadId, focusedEventId: EventId?) { - backstack.push(NavTarget.OpenThread(threadRootId, focusedEventId)) + override fun navigateToThread(threadRootId: ThreadId, focusedEventId: EventId?) { + backstack.push(NavTarget.Thread(threadRootId, focusedEventId)) } } val inputs = MessagesNode.Inputs(focusedEventId = navTarget.focusedEventId) @@ -302,14 +310,21 @@ class MessagesFlowNode( overlay.hide() } - override fun onViewInTimeline(eventId: EventId) { - viewInTimeline(eventId) + override fun viewInTimeline(eventId: EventId) { + this@MessagesFlowNode.viewInTimeline(eventId) + } + + override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) { + // Need to go to the parent because of the overlay + callback.forwardEvent(eventId, fromPinnedEvents) } } - mediaViewerEntryPoint.nodeBuilder(this, buildContext) - .params(params) - .callback(callback) - .build() + mediaViewerEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = params, + callback = callback + ) } is NavTarget.AttachmentPreview -> { val inputs = AttachmentsPreviewNode.Inputs( @@ -321,7 +336,11 @@ class MessagesFlowNode( } is NavTarget.LocationViewer -> { val inputs = ShowLocationEntryPoint.Inputs(navTarget.location, navTarget.description) - showLocationEntryPoint.createNode(this, buildContext, inputs) + showLocationEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + inputs = inputs, + ) } is NavTarget.EventDebugInfo -> { val inputs = EventDebugInfoNode.Inputs(navTarget.eventId, navTarget.debugInfo) @@ -333,69 +352,79 @@ class MessagesFlowNode( } else { timelineController } - val inputs = ForwardMessagesNode.Inputs(navTarget.eventId, timelineProvider) - val callback = object : ForwardMessagesNode.Callback { - override fun onForwardedToSingleRoom(roomId: RoomId) { - callbacks.forEach { it.onForwardedToSingleRoom(roomId) } + val params = ForwardEntryPoint.Params(navTarget.eventId, timelineProvider) + val callback = object : ForwardEntryPoint.Callback { + override fun onDone(roomIds: List) { + backstack.pop() + roomIds.singleOrNull()?.let { roomId -> + callback.navigateToRoom(roomId) + } } } - createNode(buildContext, listOf(inputs, callback)) + forwardEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = params, + callback = callback, + ) } is NavTarget.ReportMessage -> { val inputs = ReportMessageNode.Inputs(navTarget.eventId, navTarget.senderId) createNode(buildContext, listOf(inputs)) } is NavTarget.SendLocation -> { - sendLocationEntryPoint - .builder(navTarget.timelineMode) - .build(this, buildContext) + sendLocationEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + timelineMode = navTarget.timelineMode, + ) } is NavTarget.CreatePoll -> { - createPollEntryPoint.nodeBuilder(this, buildContext) - .params( - CreatePollEntryPoint.Params( - timelineMode = navTarget.timelineMode, - mode = CreatePollMode.NewPoll - ) - ) - .build() + createPollEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = CreatePollEntryPoint.Params( + timelineMode = navTarget.timelineMode, + mode = CreatePollMode.NewPoll + ), + ) } is NavTarget.EditPoll -> { - createPollEntryPoint.nodeBuilder(this, buildContext) - .params( - CreatePollEntryPoint.Params( - timelineMode = navTarget.timelineMode, - mode = CreatePollMode.EditPoll(eventId = navTarget.eventId) - ) - ) - .build() + createPollEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = CreatePollEntryPoint.Params( + timelineMode = navTarget.timelineMode, + mode = CreatePollMode.EditPoll(eventId = navTarget.eventId) + ), + ) } NavTarget.PinnedMessagesList -> { val callback = object : PinnedMessagesListNode.Callback { - override fun onEventClick(event: TimelineItem.Event) { + override fun handleEventClick(event: TimelineItem.Event) { processEventClick( timelineMode = Timeline.Mode.PinnedEvents, event = event, ) } - override fun onUserDataClick(userId: UserId) { - callbacks.forEach { it.onUserDataClick(userId) } + override fun navigateToRoomMemberDetails(userId: UserId) { + callback.navigateToRoomMemberDetails(userId) } - override fun onViewInTimelineClick(eventId: EventId) { - viewInTimeline(eventId) + override fun viewInTimeline(eventId: EventId) { + this@MessagesFlowNode.viewInTimeline(eventId) } - override fun onRoomPermalinkClick(data: PermalinkData.RoomLink) { - callbacks.forEach { it.onPermalinkClick(data, pushToBackstack = !room.matches(data.roomIdOrAlias)) } + override fun handlePermalinkClick(data: PermalinkData.RoomLink) { + callback.handlePermalinkClick(data, pushToBackstack = !room.matches(data.roomIdOrAlias)) } - override fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { + override fun navigateToEventDebugInfo(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { backstack.push(NavTarget.EventDebugInfo(eventId, debugInfo)) } - override fun onForwardEventClick(eventId: EventId) { + override fun handleForwardEventClick(eventId: EventId) { backstack.push(NavTarget.ForwardEvent(eventId = eventId, fromPinnedEvents = true)) } } @@ -404,20 +433,20 @@ class MessagesFlowNode( NavTarget.KnockRequestsList -> { knockRequestsListEntryPoint.createNode(this, buildContext) } - is NavTarget.OpenThread -> { + is NavTarget.Thread -> { val inputs = ThreadedMessagesNode.Inputs( threadRootEventId = navTarget.threadRootId, focusedEventId = navTarget.focusedEventId, ) val callback = object : ThreadedMessagesNode.Callback { - override fun onEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event): Boolean { + override fun handleEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event): Boolean { return processEventClick( timelineMode = timelineMode, event = event, ) } - override fun onPreviewAttachments(attachments: ImmutableList, inReplyToEventId: EventId?) { + override fun navigateToPreviewAttachments(attachments: ImmutableList, inReplyToEventId: EventId?) { backstack.push( NavTarget.AttachmentPreview( attachment = attachments.first(), @@ -427,39 +456,39 @@ class MessagesFlowNode( ) } - override fun onUserDataClick(userId: UserId) { - callbacks.forEach { it.onUserDataClick(userId) } + override fun navigateToRoomMemberDetails(userId: UserId) { + callback.navigateToRoomMemberDetails(userId) } - override fun onPermalinkClick(data: PermalinkData) { - callbacks.forEach { it.onPermalinkClick(data, pushToBackstack = true) } + override fun handlePermalinkClick(data: PermalinkData) { + callback.handlePermalinkClick(data, pushToBackstack = true) } - override fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { + override fun navigateToEventDebugInfo(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { backstack.push(NavTarget.EventDebugInfo(eventId, debugInfo)) } - override fun onForwardEventClick(eventId: EventId) { + override fun handleForwardEventClick(eventId: EventId) { backstack.push(NavTarget.ForwardEvent(eventId, fromPinnedEvents = false)) } - override fun onReportMessage(eventId: EventId, senderId: UserId) { + override fun navigateToReportMessage(eventId: EventId, senderId: UserId) { backstack.push(NavTarget.ReportMessage(eventId, senderId)) } - override fun onSendLocationClick() { + override fun navigateToSendLocation() { backstack.push(NavTarget.SendLocation(Timeline.Mode.Thread(navTarget.threadRootId))) } - override fun onCreatePollClick() { + override fun navigateToCreatePoll() { backstack.push(NavTarget.CreatePoll(Timeline.Mode.Thread(navTarget.threadRootId))) } - override fun onEditPollClick(eventId: EventId) { + override fun navigateToEditPoll(eventId: EventId) { backstack.push(NavTarget.EditPoll(Timeline.Mode.Thread(navTarget.threadRootId), eventId)) } - override fun onJoinCallClick(roomId: RoomId) { + override fun navigateToRoomCall(roomId: RoomId) { val callType = CallType.RoomCall( sessionId = sessionId, roomId = roomId, @@ -468,8 +497,8 @@ class MessagesFlowNode( elementCallEntryPoint.startCall(callType) } - override fun onOpenThread(threadRootId: ThreadId, focusedEventId: EventId?) { - backstack.push(NavTarget.OpenThread(threadRootId, focusedEventId)) + override fun navigateToThread(threadRootId: ThreadId, focusedEventId: EventId?) { + backstack.push(NavTarget.Thread(threadRootId, focusedEventId)) } } createNode(buildContext, listOf(inputs, callback)) @@ -482,7 +511,7 @@ class MessagesFlowNode( roomIdOrAlias = room.roomId.toRoomIdOrAlias(), eventId = eventId, ) - callbacks.forEach { it.onPermalinkClick(permalinkData, pushToBackstack = false) } + callback.handlePermalinkClick(permalinkData, pushToBackstack = false) } private fun processEventClick( @@ -583,6 +612,16 @@ class MessagesFlowNode( ) } + override suspend fun attachThread(threadId: ThreadId, focusedEventId: EventId?) { + // Wait until we have the UI for the main timeline attached + waitForChildAttached() + // Give some time for the items in the main timeline to be received, otherwise loading the focused thread root id won't work + // (look at TimelineItemIndexer and firstProcessLatch for more info) + delay(10.milliseconds) + // Then push the new threads screen on top + backstack.push(NavTarget.Thread(threadId, focusedEventId)) + } + @Composable override fun View(modifier: Modifier) { mentionSpanTheme.updateStyles() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNavigator.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNavigator.kt index fc417fc029..2ec5c0bcbf 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNavigator.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNavigator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,11 +17,12 @@ import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugIn import kotlinx.collections.immutable.ImmutableList interface MessagesNavigator { - fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) - fun onForwardEventClick(eventId: EventId) - fun onReportContentClick(eventId: EventId, senderId: UserId) - fun onEditPollClick(eventId: EventId) - fun onPreviewAttachment(attachments: ImmutableList, inReplyToEventId: EventId?) - fun onNavigateToRoom(roomId: RoomId, eventId: EventId?, serverNames: List) - fun onOpenThread(threadRootId: ThreadId, focusedEventId: EventId?) + fun navigateToEventDebugInfo(eventId: EventId?, debugInfo: TimelineItemDebugInfo) + fun forwardEvent(eventId: EventId) + fun navigateToReportMessage(eventId: EventId, senderId: UserId) + fun navigateToEditPoll(eventId: EventId) + fun navigateToPreviewAttachments(attachments: ImmutableList, inReplyToEventId: EventId?) + fun navigateToRoom(roomId: RoomId, eventId: EventId?, serverNames: List) + fun navigateToThread(threadRootId: ThreadId, focusedEventId: EventId?) + fun close() } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index d4283d19d5..0692a98745 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,6 +10,7 @@ package io.element.android.features.messages.impl import android.app.Activity import android.content.Context +import androidx.activity.compose.BackHandler import androidx.activity.compose.LocalActivity import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -23,7 +25,6 @@ import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode @@ -32,7 +33,7 @@ import io.element.android.features.knockrequests.api.banner.KnockRequestsBannerR import io.element.android.features.messages.impl.actionlist.ActionListPresenter import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor import io.element.android.features.messages.impl.attachments.Attachment -import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents +import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter import io.element.android.features.messages.impl.timeline.TimelineController import io.element.android.features.messages.impl.timeline.TimelineEvents @@ -47,8 +48,8 @@ import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTa import io.element.android.libraries.androidutils.system.openUrlInExternalApp import io.element.android.libraries.androidutils.system.toast import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs -import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.designsystem.utils.OnLifecycleEvent import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.annotations.ApplicationContext @@ -67,7 +68,9 @@ import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo import io.element.android.libraries.mediaplayer.api.MediaPlayer import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.LoadMessagesUi import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.api.finishLongRunningTransaction import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope @@ -92,13 +95,12 @@ class MessagesNode( private val knockRequestsBannerRenderer: KnockRequestsBannerRenderer, private val roomMemberModerationRenderer: RoomMemberModerationRenderer, ) : Node(buildContext, plugins = plugins), MessagesNavigator { - private val callbacks = plugins() - data class Inputs( val focusedEventId: EventId?, ) : NodeInputs private val inputs = inputs() + private val callback: Callback = callback() private val timelineController = TimelineController(room, room.liveTimeline) private val presenter = presenterFactory.create( @@ -113,21 +115,21 @@ class MessagesNode( ) interface Callback : Plugin { - fun onEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event): Boolean - fun onPreviewAttachments(attachments: ImmutableList, inReplyToEventId: EventId?) - fun onUserDataClick(userId: UserId) - fun onPermalinkClick(data: PermalinkData) - fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) - fun onForwardEventClick(eventId: EventId) - fun onReportMessage(eventId: EventId, senderId: UserId) - fun onSendLocationClick() - fun onCreatePollClick() - fun onEditPollClick(eventId: EventId) - fun onJoinCallClick(roomId: RoomId) - fun onOpenThread(threadRootId: ThreadId, focusedEventId: EventId?) - fun onRoomDetailsClick() - fun onViewAllPinnedEvents() - fun onViewKnockRequests() + fun handleEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event): Boolean + fun navigateToPreviewAttachments(attachments: ImmutableList, inReplyToEventId: EventId?) + fun navigateToRoomMemberDetails(userId: UserId) + fun handlePermalinkClick(data: PermalinkData) + fun navigateToEventDebugInfo(eventId: EventId?, debugInfo: TimelineItemDebugInfo) + fun forwardEvent(eventId: EventId) + fun navigateToReportMessage(eventId: EventId, senderId: UserId) + fun navigateToSendLocation() + fun navigateToCreatePoll() + fun navigateToEditPoll(eventId: EventId) + fun navigateToRoomCall(roomId: RoomId) + fun navigateToThread(threadRootId: ThreadId, focusedEventId: EventId?) + fun navigateToRoomDetails() + fun navigateToPinnedMessagesList() + fun navigateToKnockRequestsList() } override fun onBuilt() { @@ -136,38 +138,15 @@ class MessagesNode( onCreate = { sessionCoroutineScope.launch { analyticsService.capture(room.toAnalyticsViewRoom()) } }, + onResume = { + analyticsService.finishLongRunningTransaction(LoadMessagesUi) + }, onDestroy = { mediaPlayer.close() } ) } - private fun onRoomDetailsClick() { - callbacks.forEach { it.onRoomDetailsClick() } - } - - private fun onViewAllPinnedMessagesClick() { - callbacks.forEach { it.onViewAllPinnedEvents() } - } - - private fun onViewKnockRequestsClick() { - callbacks.forEach { it.onViewKnockRequests() } - } - - private fun onEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event): Boolean { - // Note: cannot use `callbacks.all { it.onEventClick(event) }` because: - // - if callbacks is empty, it will return true and we want to return false. - // - if a callback returns false, the other callback will not be invoked. - return callbacks.takeIf { it.isNotEmpty() } - ?.map { it.onEventClick(timelineMode, event) } - ?.all { it } - .orFalse() - } - - private fun onUserDataClick(userId: UserId) { - callbacks.forEach { it.onUserDataClick(userId) } - } - private fun onLinkClick( activity: Activity, darkTheme: Boolean, @@ -179,7 +158,7 @@ class MessagesNode( is PermalinkData.UserLink -> { // Open the room member profile, it will fallback to // the user profile if the user is not in the room - callbacks.forEach { it.onUserDataClick(permalink.userId) } + callback.navigateToRoomMemberDetails(permalink.userId) } is PermalinkData.RoomLink -> { handleRoomLinkClick(permalink, eventSink) @@ -210,59 +189,49 @@ class MessagesNode( displaySameRoomToast() } } else { - callbacks.forEach { it.onPermalinkClick(roomLink) } + callback.handlePermalinkClick(roomLink) } } - override fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { - callbacks.forEach { it.onShowEventDebugInfoClick(eventId, debugInfo) } + override fun navigateToEventDebugInfo(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { + callback.navigateToEventDebugInfo(eventId, debugInfo) } - override fun onForwardEventClick(eventId: EventId) { - callbacks.forEach { it.onForwardEventClick(eventId) } + override fun forwardEvent(eventId: EventId) { + callback.forwardEvent(eventId) } - override fun onReportContentClick(eventId: EventId, senderId: UserId) { - callbacks.forEach { it.onReportMessage(eventId, senderId) } + override fun navigateToReportMessage(eventId: EventId, senderId: UserId) { + callback.navigateToReportMessage(eventId, senderId) } - override fun onEditPollClick(eventId: EventId) { - callbacks.forEach { it.onEditPollClick(eventId) } + override fun navigateToEditPoll(eventId: EventId) { + callback.navigateToEditPoll(eventId) } - override fun onPreviewAttachment(attachments: ImmutableList, inReplyToEventId: EventId?) { - callbacks.forEach { it.onPreviewAttachments(attachments, inReplyToEventId) } + override fun navigateToPreviewAttachments(attachments: ImmutableList, inReplyToEventId: EventId?) { + callback.navigateToPreviewAttachments(attachments, inReplyToEventId) } - override fun onNavigateToRoom(roomId: RoomId, eventId: EventId?, serverNames: List) { + override fun navigateToRoom(roomId: RoomId, eventId: EventId?, serverNames: List) { if (roomId == room.roomId) { displaySameRoomToast() } else { val permalinkData = PermalinkData.RoomLink(roomId.toRoomIdOrAlias(), eventId, viaParameters = serverNames.toImmutableList()) - callbacks.forEach { it.onPermalinkClick(permalinkData) } + callback.handlePermalinkClick(permalinkData) } } - override fun onOpenThread(threadRootId: ThreadId, focusedEventId: EventId?) { - callbacks.forEach { it.onOpenThread(threadRootId, focusedEventId) } - } - - private fun onSendLocationClick() { - callbacks.forEach { it.onSendLocationClick() } - } - - private fun onCreatePollClick() { - callbacks.forEach { it.onCreatePollClick() } - } - - private fun onJoinCallClick() { - callbacks.forEach { it.onJoinCallClick(room.roomId) } + override fun navigateToThread(threadRootId: ThreadId, focusedEventId: EventId?) { + callback.navigateToThread(threadRootId, focusedEventId) } private fun displaySameRoomToast() { context.toast(CommonStrings.screen_room_permalink_same_room_android) } + override fun close() = navigateUp() + @Composable override fun View(modifier: Modifier) { val activity = requireNotNull(LocalActivity.current) @@ -271,29 +240,34 @@ class MessagesNode( LocalTimelineItemPresenterFactories provides timelineItemPresenterFactories, ) { val state = presenter.present() + + BackHandler { + state.eventSink(MessagesEvents.MarkAsFullyReadAndExit) + } + OnLifecycleEvent { _, event -> when (event) { - Lifecycle.Event.ON_PAUSE -> state.composerState.eventSink(MessageComposerEvents.SaveDraft) + Lifecycle.Event.ON_PAUSE -> state.composerState.eventSink(MessageComposerEvent.SaveDraft) else -> Unit } } MessagesView( state = state, - onBackClick = this::navigateUp, - onRoomDetailsClick = this::onRoomDetailsClick, + onBackClick = { state.eventSink(MessagesEvents.MarkAsFullyReadAndExit) }, + onRoomDetailsClick = callback::navigateToRoomDetails, onEventContentClick = { isLive, event -> if (isLive) { - onEventClick(timelineController.mainTimelineMode(), event) + callback.handleEventClick(timelineController.mainTimelineMode(), event) } else { val detachedTimelineMode = timelineController.detachedTimelineMode() if (detachedTimelineMode != null) { - onEventClick(detachedTimelineMode, event) + callback.handleEventClick(detachedTimelineMode, event) } else { false } } }, - onUserDataClick = this::onUserDataClick, + onUserDataClick = callback::navigateToRoomMemberDetails, onLinkClick = { url, customTab -> onLinkClick( activity = activity, @@ -303,15 +277,15 @@ class MessagesNode( customTab = customTab, ) }, - onSendLocationClick = this::onSendLocationClick, - onCreatePollClick = this::onCreatePollClick, - onJoinCallClick = this::onJoinCallClick, - onViewAllPinnedMessagesClick = this::onViewAllPinnedMessagesClick, + onSendLocationClick = callback::navigateToSendLocation, + onCreatePollClick = callback::navigateToCreatePoll, + onJoinCallClick = { callback.navigateToRoomCall(room.roomId) }, + onViewAllPinnedMessagesClick = callback::navigateToPinnedMessagesList, modifier = modifier, knockRequestsBannerView = { knockRequestsBannerRenderer.View( modifier = Modifier, - onViewRequestsClick = this::onViewKnockRequestsClick + onViewRequestsClick = callback::navigateToKnockRequestsList, ) }, ) @@ -319,7 +293,7 @@ class MessagesNode( state = state.roomMemberModerationState, onSelectAction = { action, target -> when (action) { - is ModerationAction.DisplayProfile -> onUserDataClick(target.userId) + is ModerationAction.DisplayProfile -> callback.navigateToRoomMemberDetails(target.userId) else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target)) } }, @@ -329,12 +303,11 @@ class MessagesNode( var focusedEventId by rememberSaveable { mutableStateOf(inputs.focusedEventId) } - LaunchedEffect(Unit) { - focusedEventId?.also { eventId -> - state.timelineState.eventSink(TimelineEvents.FocusOnEvent(eventId)) + LaunchedEffect(focusedEventId) { + if (focusedEventId != null) { + state.timelineState.eventSink(TimelineEvents.FocusOnEvent(focusedEventId!!)) + focusedEventId = null } - // Reset the focused event id to null to avoid refocusing when restoring node. - focusedEventId = null } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 5bf92ef6f6..8a5f3cf423 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,12 +12,10 @@ import android.os.Build import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState -import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable @@ -31,11 +30,13 @@ import io.element.android.features.messages.api.timeline.HtmlConverterProvider import io.element.android.features.messages.impl.actionlist.ActionListEvents import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction +import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState import io.element.android.features.messages.impl.link.LinkState -import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents +import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent import io.element.android.features.messages.impl.messagecomposer.MessageComposerState import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState +import io.element.android.features.messages.impl.timeline.MarkAsFullyRead import io.element.android.features.messages.impl.timeline.TimelineController import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineState @@ -65,27 +66,23 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState +import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.core.toThreadId import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.matrix.api.permalink.PermalinkParser -import io.element.android.libraries.matrix.api.recentemojis.AddRecentEmoji import io.element.android.libraries.matrix.api.room.JoinedRoom -import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.RoomInfo import io.element.android.libraries.matrix.api.room.RoomMembersState import io.element.android.libraries.matrix.api.room.isDm -import io.element.android.libraries.matrix.api.room.powerlevels.canPinUnpin -import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther -import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn -import io.element.android.libraries.matrix.api.room.powerlevels.canSendMessage -import io.element.android.libraries.matrix.api.sync.SyncService +import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId import io.element.android.libraries.matrix.ui.messages.reply.map import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.matrix.ui.room.getDirectRoomMember +import io.element.android.libraries.recentemojis.api.AddRecentEmoji import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.analytics.api.AnalyticsService @@ -94,6 +91,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber +import java.util.concurrent.atomic.AtomicBoolean @AssistedInject class MessagesPresenter( @@ -104,6 +102,7 @@ class MessagesPresenter( @Assisted private val timelinePresenter: Presenter, private val timelineProtectionPresenter: Presenter, private val identityChangeStatePresenter: Presenter, + private val historyVisibleStatePresenter: Presenter, private val linkPresenter: Presenter, @Assisted private val actionListPresenter: Presenter, private val customReactionPresenter: Presenter, @@ -112,7 +111,6 @@ class MessagesPresenter( private val pinnedMessagesBannerPresenter: Presenter, private val roomCallStatePresenter: Presenter, private val roomMemberModerationPresenter: Presenter, - private val syncService: SyncService, private val snackbarDispatcher: SnackbarDispatcher, private val dispatchers: CoroutineDispatchers, private val clipboardHelper: ClipboardHelper, @@ -124,6 +122,8 @@ class MessagesPresenter( private val encryptionService: EncryptionService, private val featureFlagService: FeatureFlagService, private val addRecentEmoji: AddRecentEmoji, + private val markAsFullyRead: MarkAsFullyRead, + @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, ) : Presenter { @AssistedFactory interface Factory { @@ -140,10 +140,13 @@ class MessagesPresenter( timelineMode = timelineController.mainTimelineMode() ) + private val markingAsReadAndExiting = AtomicBoolean(false) + @Composable override fun present(): MessagesState { htmlConverterProvider.Update() + val coroutineScope = rememberCoroutineScope() val roomInfo by room.roomInfoFlow.collectAsState() val localCoroutineScope = rememberCoroutineScope() val composerState = composerPresenter.present() @@ -151,6 +154,7 @@ class MessagesPresenter( val timelineState = timelinePresenter.present() val timelineProtectionState = timelineProtectionPresenter.present() val identityChangeState = identityChangeStatePresenter.present() + val historyVisibleState = historyVisibleStatePresenter.present() val actionListState = actionListPresenter.present() val linkState = linkPresenter.present() val customReactionState = customReactionPresenter.present() @@ -160,7 +164,9 @@ class MessagesPresenter( val roomCallState = roomCallStatePresenter.present() val roomMemberModerationState = roomMemberModerationPresenter.present() - val userEventPermissions by userEventPermissions(roomInfo) + val userEventPermissions by room.permissionsAsState(UserEventPermissions.DEFAULT) { perms -> + perms.userEventPermissions() + } val roomAvatar by remember { derivedStateOf { roomInfo.avatarData() } @@ -193,7 +199,6 @@ class MessagesPresenter( showReinvitePrompt = !hasDismissedInviteDialog && composerHasFocus && roomInfo.isDm && roomInfo.activeMembersCount == 1L } } - val isOnline by syncService.isOnline.collectAsState() val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() @@ -216,7 +221,7 @@ class MessagesPresenter( onPauseOrDispose {} } - fun handleEvents(event: MessagesEvents) { + fun handleEvent(event: MessagesEvents) { when (event) { is MessagesEvents.HandleAction -> { localCoroutineScope.handleTimelineAction( @@ -242,6 +247,22 @@ class MessagesPresenter( is MessagesEvents.OnUserClicked -> { roomMemberModerationState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(event.user)) } + is MessagesEvents.MarkAsFullyReadAndExit -> coroutineScope.launch { + if (!markingAsReadAndExiting.getAndSet(true)) { + val latestEventId = room.liveTimeline.getLatestEventId().getOrElse { + Timber.w(it, "Failed to get latest event id to mark as fully read") + navigator.close() + return@launch + } + latestEventId?.let { eventId -> + sessionCoroutineScope.launch { + markAsFullyRead(room.roomId, eventId) + } + } + navigator.close() + markingAsReadAndExiting.set(false) + } + } } } @@ -250,50 +271,32 @@ class MessagesPresenter( roomName = roomInfo.name, roomAvatar = roomAvatar, heroes = heroes, - composerState = composerState, userEventPermissions = userEventPermissions, + composerState = composerState, voiceMessageComposerState = voiceMessageComposerState, timelineState = timelineState, timelineProtectionState = timelineProtectionState, identityChangeState = identityChangeState, + historyVisibleState = historyVisibleState, linkState = linkState, actionListState = actionListState, customReactionState = customReactionState, reactionSummaryState = reactionSummaryState, readReceiptBottomSheetState = readReceiptBottomSheetState, - hasNetworkConnection = isOnline, snackbarMessage = snackbarMessage, - showReinvitePrompt = showReinvitePrompt, inviteProgress = inviteProgress.value, + showReinvitePrompt = showReinvitePrompt, enableTextFormatting = MessageComposerConfig.ENABLE_RICH_TEXT_EDITING, - appName = buildMeta.applicationName, roomCallState = roomCallState, + appName = buildMeta.applicationName, pinnedMessagesBannerState = pinnedMessagesBannerState, dmUserVerificationState = dmUserVerificationState, roomMemberModerationState = roomMemberModerationState, successorRoom = roomInfo.successorRoom, - eventSink = { handleEvents(it) } + eventSink = ::handleEvent, ) } - @Composable - private fun userEventPermissions(roomInfo: RoomInfo): State { - val key = if (roomInfo.privilegedCreatorRole && roomInfo.creators.contains(room.sessionId)) { - Long.MAX_VALUE - } else { - roomInfo.roomPowerLevels?.hashCode() ?: 0L - } - return produceState(UserEventPermissions.DEFAULT, key1 = key) { - value = UserEventPermissions( - canSendMessage = room.canSendMessage(type = MessageEventType.RoomMessage).getOrElse { true }, - canSendReaction = room.canSendMessage(type = MessageEventType.Reaction).getOrElse { true }, - canRedactOwn = room.canRedactOwn().getOrElse { false }, - canRedactOther = room.canRedactOther().getOrElse { false }, - canPinUnpin = room.canPinUnpin().getOrElse { false }, - ) - } - } - private fun RoomInfo.avatarData(): AvatarData { return AvatarData( id = id.value, @@ -336,7 +339,7 @@ class MessagesPresenter( is TimelineItemThreadInfo.ThreadResponse -> targetEvent.threadInfo.threadRootId is TimelineItemThreadInfo.ThreadRoot, null -> targetEvent.eventId?.toThreadId() } ?: return@launch - navigator.onOpenThread(threadId, null) + navigator.navigateToThread(threadId, null) } else { handleActionReply(targetEvent, composerState, timelineProtectionState) } @@ -444,7 +447,7 @@ class MessagesPresenter( when (targetEvent.content) { is TimelineItemPollContent -> { if (targetEvent.eventId == null) return - navigator.onEditPollClick(targetEvent.eventId) + navigator.navigateToEditPoll(targetEvent.eventId) } else -> { val composerMode = MessageComposerMode.Edit( @@ -458,7 +461,7 @@ class MessagesPresenter( }.orEmpty(), ) composerState.eventSink( - MessageComposerEvents.SetMode(composerMode) + MessageComposerEvent.SetMode(composerMode) ) } } @@ -473,7 +476,7 @@ class MessagesPresenter( content = "", ) composerState.eventSink( - MessageComposerEvents.SetMode(composerMode) + MessageComposerEvent.SetMode(composerMode) ) } @@ -486,7 +489,7 @@ class MessagesPresenter( content = (targetEvent.content as? TimelineItemEventContentWithAttachment)?.caption.orEmpty(), ) composerState.eventSink( - MessageComposerEvents.SetMode(composerMode) + MessageComposerEvent.SetMode(composerMode) ) } @@ -503,23 +506,23 @@ class MessagesPresenter( hideImage = timelineProtectionState.hideMediaContent(targetEvent.eventId), ) composerState.eventSink( - MessageComposerEvents.SetMode(composerMode) + MessageComposerEvent.SetMode(composerMode) ) } } private fun handleShowDebugInfoAction(event: TimelineItem.Event) { - navigator.onShowEventDebugInfoClick(event.eventId, event.debugInfo) + navigator.navigateToEventDebugInfo(event.eventId, event.debugInfo) } private fun handleForwardAction(event: TimelineItem.Event) { if (event.eventId == null) return - navigator.onForwardEventClick(event.eventId) + navigator.forwardEvent(event.eventId) } private fun handleReportAction(event: TimelineItem.Event) { if (event.eventId == null) return - navigator.onReportContentClick(event.eventId, event.senderId) + navigator.navigateToReportMessage(event.eventId, event.senderId) } private fun handleEndPollAction( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt index 3d05da5a3c..b9d86a6597 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt @@ -1,15 +1,16 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl -import androidx.compose.runtime.Immutable import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerState import io.element.android.features.messages.impl.actionlist.ActionListState +import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState import io.element.android.features.messages.impl.link.LinkState import io.element.android.features.messages.impl.messagecomposer.MessageComposerState @@ -29,7 +30,6 @@ import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom import kotlinx.collections.immutable.ImmutableList -@Immutable data class MessagesState( val roomId: RoomId, val roomName: String?, @@ -41,12 +41,12 @@ data class MessagesState( val timelineState: TimelineState, val timelineProtectionState: TimelineProtectionState, val identityChangeState: IdentityChangeState, + val historyVisibleState: HistoryVisibleState, val linkState: LinkState, val actionListState: ActionListState, val customReactionState: CustomReactionState, val reactionSummaryState: ReactionSummaryState, val readReceiptBottomSheetState: ReadReceiptBottomSheetState, - val hasNetworkConnection: Boolean, val snackbarMessage: SnackbarMessage?, val inviteProgress: AsyncData, val showReinvitePrompt: Boolean, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt index 54b9dc659e..cec9f68b45 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,10 @@ import io.element.android.features.messages.api.timeline.voicemessages.composer. import io.element.android.features.messages.api.timeline.voicemessages.composer.aVoiceMessagePreviewState import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.anActionListState +import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState +import io.element.android.features.messages.impl.crypto.historyvisible.aHistoryVisibleState import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState +import io.element.android.features.messages.impl.crypto.identity.aRoomMemberIdentityStateChange import io.element.android.features.messages.impl.crypto.identity.anIdentityChangeState import io.element.android.features.messages.impl.link.LinkState import io.element.android.features.messages.impl.link.aLinkState @@ -37,6 +41,7 @@ import io.element.android.features.messages.impl.timeline.protection.aTimelinePr import io.element.android.features.roomcall.api.RoomCallState import io.element.android.features.roomcall.api.aStandByCallState import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.avatar.AvatarData @@ -47,6 +52,7 @@ import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.textcomposer.model.MessageComposerMode +import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown import io.element.android.libraries.textcomposer.model.aTextEditorStateRich import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -56,7 +62,6 @@ open class MessagesStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aMessagesState(), - aMessagesState(hasNetworkConnection = false), aMessagesState(composerState = aMessageComposerState(showAttachmentSourcePicker = true)), aMessagesState(userEventPermissions = aUserEventPermissions(canSendMessage = false)), aMessagesState(showReinvitePrompt = true), @@ -83,6 +88,19 @@ open class MessagesStateProvider : PreviewParameterProvider { timelineItems = aTimelineItemList(aTimelineItemTextContent()), ) ), + aMessagesState( + composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()), + identityChangeState = anIdentityChangeState(listOf(aRoomMemberIdentityStateChange())) + ), + aMessagesState( + composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()), + historyVisibleState = aHistoryVisibleState(showAlert = true) + ), + aMessagesState( + composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()), + identityChangeState = anIdentityChangeState(listOf(aRoomMemberIdentityStateChange())), + historyVisibleState = aHistoryVisibleState(showAlert = true) + ) ) } @@ -103,12 +121,12 @@ fun aMessagesState( ), timelineProtectionState: TimelineProtectionState = aTimelineProtectionState(), identityChangeState: IdentityChangeState = anIdentityChangeState(), + historyVisibleState: HistoryVisibleState = aHistoryVisibleState(), linkState: LinkState = aLinkState(), readReceiptBottomSheetState: ReadReceiptBottomSheetState = aReadReceiptBottomSheetState(), actionListState: ActionListState = anActionListState(), customReactionState: CustomReactionState = aCustomReactionState(), reactionSummaryState: ReactionSummaryState = aReactionSummaryState(), - hasNetworkConnection: Boolean = true, showReinvitePrompt: Boolean = false, roomCallState: RoomCallState = aStandByCallState(), pinnedMessagesBannerState: PinnedMessagesBannerState = aLoadedPinnedMessagesBannerState(), @@ -126,13 +144,13 @@ fun aMessagesState( voiceMessageComposerState = voiceMessageComposerState, timelineProtectionState = timelineProtectionState, identityChangeState = identityChangeState, + historyVisibleState = historyVisibleState, linkState = linkState, timelineState = timelineState, readReceiptBottomSheetState = readReceiptBottomSheetState, actionListState = actionListState, customReactionState = customReactionState, reactionSummaryState = reactionSummaryState, - hasNetworkConnection = hasNetworkConnection, snackbarMessage = null, inviteProgress = AsyncData.Uninitialized, showReinvitePrompt = showReinvitePrompt, @@ -147,11 +165,9 @@ fun aMessagesState( ) fun aRoomMemberModerationState( - canKick: Boolean = false, - canBan: Boolean = false, + permissions: RoomMemberModerationPermissions = RoomMemberModerationPermissions.DEFAULT, ) = object : RoomMemberModerationState { - override val canKick: Boolean = canKick - override val canBan: Boolean = canBan + override val permissions: RoomMemberModerationPermissions = permissions override val eventSink: (RoomMemberModerationEvents) -> Unit = {} } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 07fc178721..5479836bb3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -27,10 +28,16 @@ import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role @@ -42,16 +49,17 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme -import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerEvents +import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerEvent import io.element.android.features.messages.impl.actionlist.ActionListEvents import io.element.android.features.messages.impl.actionlist.ActionListView import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction +import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleStateView import io.element.android.features.messages.impl.crypto.identity.IdentityChangeStateView import io.element.android.features.messages.impl.link.LinkEvents import io.element.android.features.messages.impl.link.LinkView import io.element.android.features.messages.impl.messagecomposer.AttachmentsBottomSheet import io.element.android.features.messages.impl.messagecomposer.DisabledComposerView -import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents +import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent import io.element.android.features.messages.impl.messagecomposer.MessageComposerView import io.element.android.features.messages.impl.messagecomposer.suggestions.SuggestionsPickerView import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState @@ -71,7 +79,6 @@ import io.element.android.features.messages.impl.topbars.MessagesViewTopBar import io.element.android.features.messages.impl.topbars.ThreadTopBar import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessagePermissionRationaleDialog import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageSendingFailedDialog -import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorView import io.element.android.libraries.androidutils.ui.hideKeyboard import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertMolecule import io.element.android.libraries.designsystem.components.ExpandableBottomSheetLayout @@ -81,6 +88,7 @@ import io.element.android.libraries.designsystem.components.rememberExpandableBo import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.text.toAnnotatedString +import io.element.android.libraries.designsystem.text.toDp import io.element.android.libraries.designsystem.theme.components.BottomSheetDragHandle import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text @@ -119,7 +127,7 @@ fun MessagesView( knockRequestsBannerView: @Composable () -> Unit, ) { OnLifecycleEvent { _, event -> - state.voiceMessageComposerState.eventSink(VoiceMessageComposerEvents.LifecycleEvent(event)) + state.voiceMessageComposerState.eventSink(VoiceMessageComposerEvent.LifecycleEvent(event)) } KeepScreenOn(state.voiceMessageComposerState.keepScreenOn) @@ -128,6 +136,8 @@ fun MessagesView( val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage) + var maxComposerHeightPx by remember { mutableIntStateOf(120) } + // This is needed because the composer is inside an AndroidView that can't be affected by the FocusManager in Compose val localView = LocalView.current @@ -178,34 +188,37 @@ fun MessagesView( modifier = modifier .fillMaxSize() .imePadding() - .systemBarsPadding(), + .systemBarsPadding() + .onSizeChanged { size -> + // Let the composer takes at max half of the available height. + // The value will be different if the soft keyboard is displayed + // or not. + maxComposerHeightPx = (size.height * 0.5f).toInt() + }, content = { Scaffold( contentWindowInsets = WindowInsets.statusBars, topBar = { - Column { - ConnectivityIndicatorView(isOnline = state.hasNetworkConnection) - if (state.timelineState.timelineMode is Timeline.Mode.Thread) { - ThreadTopBar( - roomName = state.roomName, - roomAvatarData = state.roomAvatar, - heroes = state.heroes, - isTombstoned = state.isTombstoned, - onBackClick = onBackClick, - ) - } else { - MessagesViewTopBar( - roomName = state.roomName, - roomAvatar = state.roomAvatar, - isTombstoned = state.isTombstoned, - heroes = state.heroes, - roomCallState = state.roomCallState, - dmUserIdentityState = state.dmUserVerificationState, - onBackClick = { hidingKeyboard { onBackClick() } }, - onRoomDetailsClick = { hidingKeyboard { onRoomDetailsClick() } }, - onJoinCallClick = onJoinCallClick, - ) - } + if (state.timelineState.timelineMode is Timeline.Mode.Thread) { + ThreadTopBar( + roomName = state.roomName, + roomAvatarData = state.roomAvatar, + heroes = state.heroes, + isTombstoned = state.isTombstoned, + onBackClick = onBackClick, + ) + } else { + MessagesViewTopBar( + roomName = state.roomName, + roomAvatar = state.roomAvatar, + isTombstoned = state.isTombstoned, + heroes = state.heroes, + roomCallState = state.roomCallState, + dmUserIdentityState = state.dmUserVerificationState, + onBackClick = { hidingKeyboard { onBackClick() } }, + onRoomDetailsClick = { hidingKeyboard { onRoomDetailsClick() } }, + onJoinCallClick = onJoinCallClick, + ) } }, content = { padding -> @@ -259,7 +272,7 @@ fun MessagesView( roomAvatarData = state.roomAvatar, suggestions = state.composerState.suggestions, onSelectSuggestion = { - state.composerState.eventSink(MessageComposerEvents.InsertSuggestion(it)) + state.composerState.eventSink(MessageComposerEvent.InsertSuggestion(it)) } ) } @@ -281,14 +294,13 @@ fun MessagesView( }, ) }, - sheetDragHandle = if (state.composerState.showTextFormatting) { - @Composable { toggleAction -> + sheetDragHandle = @Composable { toggleAction -> + if (state.composerState.showTextFormatting) { val expandA11yLabel = stringResource(CommonStrings.a11y_expand_message_text_field) val collapseA11yLabel = stringResource(CommonStrings.a11y_collapse_message_text_field) BottomSheetDragHandle( modifier = Modifier.semantics { role = Role.Button - // Accessibility action to toggle the bottom sheet state val label = when (expandableState.position) { ExpandableBottomSheetLayoutState.Position.COLLAPSED, ExpandableBottomSheetLayoutState.Position.DRAGGING -> expandA11yLabel @@ -300,9 +312,14 @@ fun MessagesView( } } ) + } else { + LaunchedEffect(Unit) { + // Ensure that the bottom sheet is collapsed + if (expandableState.position == ExpandableBottomSheetLayoutState.Position.EXPANDED) { + toggleAction() + } + } } - } else { - @Composable {} }, isSwipeGestureEnabled = state.composerState.showTextFormatting, state = expandableState, @@ -311,7 +328,7 @@ fun MessagesView( } else { RectangleShape }, - maxBottomSheetContentHeight = 360.dp, + maxBottomSheetContentHeight = maxComposerHeightPx.toDp(), ) ActionListView( @@ -397,17 +414,17 @@ private fun MessagesViewContent( if (state.voiceMessageComposerState.showPermissionRationaleDialog) { VoiceMessagePermissionRationaleDialog( onContinue = { - state.voiceMessageComposerState.eventSink(VoiceMessageComposerEvents.AcceptPermissionRationale) + state.voiceMessageComposerState.eventSink(VoiceMessageComposerEvent.AcceptPermissionRationale) }, onDismiss = { - state.voiceMessageComposerState.eventSink(VoiceMessageComposerEvents.DismissPermissionsRationale) + state.voiceMessageComposerState.eventSink(VoiceMessageComposerEvent.DismissPermissionsRationale) }, appName = state.appName ) } if (state.voiceMessageComposerState.showSendFailureDialog) { VoiceMessageSendingFailedDialog( - onDismiss = { state.voiceMessageComposerState.eventSink(VoiceMessageComposerEvents.DismissSendFailureDialog) }, + onDismiss = { state.voiceMessageComposerState.eventSink(VoiceMessageComposerEvent.DismissSendFailureDialog) }, ) } @@ -470,10 +487,17 @@ private fun MessagesViewComposerBottomSheetContents( // Do not show the identity change if user is composing a Rich message or is seeing suggestion(s). if (state.composerState.suggestions.isEmpty() && state.composerState.textEditorState is TextEditorState.Markdown) { - IdentityChangeStateView( - state = state.identityChangeState, - onLinkClick = onLinkClick, - ) + if (state.identityChangeState.roomMemberIdentityStateChanges.isNotEmpty()) { + IdentityChangeStateView( + state = state.identityChangeState, + onLinkClick = onLinkClick, + ) + } else { + HistoryVisibleStateView( + state = state.historyVisibleState, + onLinkClick = onLinkClick, + ) + } } val verificationViolation = state.identityChangeState.roomMemberIdentityStateChanges.firstOrNull { it.identityState == IdentityState.VerificationViolation @@ -526,7 +550,6 @@ private fun SuccessorRoomBanner( content = stringResource(R.string.screen_room_timeline_tombstoned_room_message).toAnnotatedString(), onSubmitClick = { onRoomSuccessorClick(roomSuccessor.roomId) }, modifier = modifier, - isCritical = false, submitText = stringResource(R.string.screen_room_timeline_tombstoned_room_action) ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/UserEventPermissions.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/UserEventPermissions.kt index b60f7eeb75..349c8e58dc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/UserEventPermissions.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/UserEventPermissions.kt @@ -1,12 +1,16 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl +import io.element.android.libraries.matrix.api.room.MessageEventType +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions + /** * Represents the permissions a user has in a room. * It's dependent of the user's power level in the room. @@ -28,3 +32,13 @@ data class UserEventPermissions( ) } } + +fun RoomPermissions.userEventPermissions(): UserEventPermissions { + return UserEventPermissions( + canRedactOwn = canOwnUserRedactOwn(), + canRedactOther = canOwnUserRedactOther(), + canSendMessage = canOwnUserSendMessage(MessageEventType.RoomMessage), + canSendReaction = canOwnUserSendMessage(MessageEventType.Reaction), + canPinUnpin = canOwnUserPinUnpin() + ) +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt index 2052df77f1..415a80a48b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 25da8c2749..36166409b9 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -43,10 +44,10 @@ import io.element.android.libraries.di.RoomScope import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.core.EventId -import io.element.android.libraries.matrix.api.recentemojis.GetRecentEmojis import io.element.android.libraries.matrix.api.room.BaseRoom import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.preferences.api.store.AppPreferencesStore +import io.element.android.libraries.recentemojis.api.GetRecentEmojis import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -87,6 +88,8 @@ class DefaultActionListPresenter( private val comparator = TimelineItemActionComparator() + private val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏") + @Composable override fun present(): ActionListState { val localCoroutineScope = rememberCoroutineScope() @@ -104,7 +107,7 @@ class DefaultActionListPresenter( val isThreadsEnabled = featureFlagService.isFeatureEnabledFlow(FeatureFlags.Threads).collectAsState(false) - fun handleEvents(event: ActionListEvents) { + fun handleEvent(event: ActionListEvents) { when (event) { ActionListEvents.Clear -> target.value = ActionListState.Target.None is ActionListEvents.ComputeForMessage -> localCoroutineScope.computeForMessage( @@ -120,7 +123,7 @@ class DefaultActionListPresenter( return ActionListState( target = target.value, - eventSink = { handleEvents(it) } + eventSink = ::handleEvent, ) } @@ -146,6 +149,7 @@ class DefaultActionListPresenter( val displayEmojiReactions = usersEventPermissions.canSendReaction && timelineItem.content.canReact() if (actions.isNotEmpty() || displayEmojiReactions || verifiedUserSendFailure != VerifiedUserSendFailure.None) { + val recentEmojis = getRecentEmojis().getOrNull()?.toImmutableList() ?: persistentListOf() target.value = ActionListState.Target.Success( event = timelineItem, sentTimeFull = dateFormatter.format( @@ -156,7 +160,10 @@ class DefaultActionListPresenter( displayEmojiReactions = displayEmojiReactions, verifiedUserSendFailure = verifiedUserSendFailure, actions = actions.toImmutableList(), - recentEmojis = getRecentEmojis().getOrNull()?.toImmutableList() ?: persistentListOf() + // Merge suggested and recent emojis, removing duplicates and returning at most 100 + recentEmojis = (suggestedEmojis + recentEmojis).distinct() + .take(100) + .toImmutableList() ) } else { target.value = ActionListState.Target.None diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt index 7524a737ff..c0554aaf0c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,6 @@ import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUser import io.element.android.features.messages.impl.timeline.model.TimelineItem import kotlinx.collections.immutable.ImmutableList -@Immutable data class ActionListState( val target: Target, val eventSink: (ActionListEvents) -> Unit, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt index eeab4aa3a0..e57e5bdc1e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -27,6 +28,8 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList open class ActionListStateProvider : PreviewParameterProvider { + private val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏") + override val values: Sequence get() { val reactionsState = aTimelineItemReactions(1, isHighlighted = true) @@ -42,7 +45,7 @@ open class ActionListStateProvider : PreviewParameterProvider { displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemActionList(), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ), anActionListState( @@ -58,7 +61,7 @@ open class ActionListStateProvider : PreviewParameterProvider { actions = aTimelineItemActionList( copyAction = TimelineItemAction.CopyCaption, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ), anActionListState( @@ -73,7 +76,7 @@ open class ActionListStateProvider : PreviewParameterProvider { actions = aTimelineItemActionList( copyAction = TimelineItemAction.CopyCaption, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ), anActionListState( @@ -88,7 +91,7 @@ open class ActionListStateProvider : PreviewParameterProvider { actions = aTimelineItemActionList( copyAction = null, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ), anActionListState( @@ -103,7 +106,7 @@ open class ActionListStateProvider : PreviewParameterProvider { actions = aTimelineItemActionList( copyAction = TimelineItemAction.CopyCaption, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ), anActionListState( @@ -118,7 +121,7 @@ open class ActionListStateProvider : PreviewParameterProvider { actions = aTimelineItemActionList( copyAction = null, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ), anActionListState( @@ -131,7 +134,7 @@ open class ActionListStateProvider : PreviewParameterProvider { displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemActionList(), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ), anActionListState( @@ -144,7 +147,7 @@ open class ActionListStateProvider : PreviewParameterProvider { displayEmojiReactions = false, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemActionList(), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ), ), anActionListState( @@ -157,7 +160,7 @@ open class ActionListStateProvider : PreviewParameterProvider { displayEmojiReactions = false, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemPollActionList(), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ), ), anActionListState( @@ -170,7 +173,7 @@ open class ActionListStateProvider : PreviewParameterProvider { displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemActionList(), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ), anActionListState( @@ -180,7 +183,7 @@ open class ActionListStateProvider : PreviewParameterProvider { displayEmojiReactions = true, verifiedUserSendFailure = anUnsignedDeviceSendFailure(), actions = aTimelineItemActionList(), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ), ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt index a891e9d587..704f79a4f5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -97,8 +98,6 @@ import io.element.android.libraries.matrix.ui.messages.sender.SenderName import io.element.android.libraries.matrix.ui.messages.sender.SenderNameMode import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -345,7 +344,6 @@ private fun MessageSummary( } private val emojiRippleRadius = 24.dp -private val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏") @Composable private fun EmojiReactionsRow( @@ -360,12 +358,6 @@ private fun EmojiReactionsRow( ) { val backgroundColor = ElementTheme.colors.bgCanvasDefault - val emojis = remember(recentEmojis) { - (suggestedEmojis + recentEmojis.filter { it !in suggestedEmojis }) - .take(100) - .toImmutableList() - } - LazyRow( modifier = Modifier .weight(1f, fill = true) @@ -388,7 +380,7 @@ private fun EmojiReactionsRow( contentPadding = PaddingValues(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - items(emojis) { emoji -> + items(recentEmojis) { emoji -> val isHighlighted = highlightedEmojis.contains(emoji) EmojiButton( modifier = Modifier diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt index ab02e6caea..25e75beb99 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemAction.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,11 +10,9 @@ package io.element.android.features.messages.impl.actionlist.model import androidx.annotation.DrawableRes import androidx.annotation.StringRes -import androidx.compose.runtime.Immutable import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.ui.strings.CommonStrings -@Immutable enum class TimelineItemAction( @StringRes val titleRes: Int, @DrawableRes val icon: Int, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparator.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparator.kt index 5033b172bf..0a0d9b17af 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparator.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionPostProcessor.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionPostProcessor.kt index c2cc605b74..421a68f12f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionPostProcessor.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionPostProcessor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/Attachment.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/Attachment.kt index 9913b8395f..d989b34ab3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/Attachment.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/Attachment.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewEvents.kt index e679a43da7..d8e29de92e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewEvents.kt @@ -1,15 +1,13 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl.attachments.preview -import androidx.compose.runtime.Immutable - -@Immutable sealed interface AttachmentsPreviewEvents { data object SendAttachment : AttachmentsPreviewEvents data object CancelAndDismiss : AttachmentsPreviewEvents diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewNode.kt index 1df9969f72..451398d7d3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewNode.kt @@ -1,13 +1,17 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl.attachments.preview import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node @@ -15,12 +19,15 @@ import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode +import io.element.android.compound.colors.SemanticColorsLightDark import io.element.android.compound.theme.ForcedDarkElementTheme +import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.mediaviewer.api.local.LocalMediaRenderer @@ -31,6 +38,8 @@ class AttachmentsPreviewNode( @Assisted plugins: List, presenterFactory: AttachmentsPreviewPresenter.Factory, private val localMediaRenderer: LocalMediaRenderer, + private val sessionId: SessionId, + private val enterpriseService: EnterpriseService, ) : Node(buildContext, plugins = plugins) { data class Inputs( val attachment: Attachment, @@ -53,7 +62,12 @@ class AttachmentsPreviewNode( @Composable override fun View(modifier: Modifier) { - ForcedDarkElementTheme { + val colors by remember { + enterpriseService.semanticColorsFlow(sessionId = sessionId) + }.collectAsState(SemanticColorsLightDark.default) + ForcedDarkElementTheme( + colors = colors, + ) { val state = presenter.present() AttachmentsPreviewView( state = state, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt index 34a4f12fe5..f7641ec21b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -36,7 +37,8 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig -import io.element.android.libraries.mediaupload.api.MediaSender +import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider +import io.element.android.libraries.mediaupload.api.MediaSenderFactory import io.element.android.libraries.mediaupload.api.MediaUploadInfo import io.element.android.libraries.mediaupload.api.allFiles import io.element.android.libraries.preferences.api.store.VideoCompressionPreset @@ -55,12 +57,13 @@ class AttachmentsPreviewPresenter( @Assisted private val onDoneListener: OnDoneListener, @Assisted private val timelineMode: Timeline.Mode, @Assisted private val inReplyToEventId: EventId?, - mediaSenderFactory: MediaSender.Factory, + mediaSenderFactory: MediaSenderFactory, private val permalinkBuilder: PermalinkBuilder, private val temporaryUriDeleter: TemporaryUriDeleter, private val mediaOptimizationSelectorPresenterFactory: MediaOptimizationSelectorPresenter.Factory, @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, private val dispatchers: CoroutineDispatchers, + private val mediaOptimizationConfigProvider: MediaOptimizationConfigProvider, ) : Presenter { @AssistedFactory interface Factory { @@ -106,13 +109,9 @@ class AttachmentsPreviewPresenter( // to prepare it for sending. This is done to avoid blocking the UI thread when the // user clicks on the send button. if (mediaOptimizationSelectorState.displayMediaSelectorViews == false) { - val mediaOptimizationConfig = MediaOptimizationConfig( - compressImages = mediaOptimizationSelectorState.isImageOptimizationEnabled == true, - videoCompressionPreset = mediaOptimizationSelectorState.selectedVideoPreset ?: VideoCompressionPreset.STANDARD, - ) preprocessMediaJob = preProcessAttachment( attachment = attachment, - mediaOptimizationConfig = mediaOptimizationConfig, + mediaOptimizationConfig = mediaOptimizationConfigProvider.get(), displayProgress = false, sendActionState = sendActionState, ) @@ -141,8 +140,8 @@ class AttachmentsPreviewPresenter( } } - fun handleEvents(attachmentsPreviewEvents: AttachmentsPreviewEvents) { - when (attachmentsPreviewEvents) { + fun handleEvent(event: AttachmentsPreviewEvents) { + when (event) { is AttachmentsPreviewEvents.SendAttachment -> { ongoingSendAttachmentJob.value = coroutineScope.launch { // If the media optimization selector is displayed, we need to wait for the user to select the options @@ -230,7 +229,7 @@ class AttachmentsPreviewPresenter( textEditorState = textEditorState, mediaOptimizationSelectorState = mediaOptimizationSelectorState, displayFileTooLargeError = displayFileTooLargeError, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt index 96ff83a8a7..42d01a4f21 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt index ac7034efe7..70d7ab006e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -94,7 +95,7 @@ fun aMediaUploadInfo( ) fun aMediaOptimisationSelectorState( - maxUploadSize: Long = 100, + maxUploadSize: Long = 100 * 1024 * 1024, videoSizeEstimations: AsyncData> = AsyncData.Success(persistentListOf()), isImageOptimizationEnabled: Boolean = true, selectedVideoPreset: VideoCompressionPreset = VideoCompressionPreset.STANDARD, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt index 0a94d225dd..8f55957e32 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -230,7 +231,7 @@ private fun ImageOptimizationSelector(state: MediaOptimizationSelectorState) { Text( modifier = Modifier.weight(1f).align(Alignment.CenterVertically), text = stringResource(R.string.screen_media_upload_preview_optimize_image_quality_title), - style = ElementTheme.materialTypography.bodyLarge, + style = ElementTheme.typography.fontBodyLgRegular, ) Switch( modifier = Modifier.height(32.dp), @@ -336,7 +337,7 @@ private fun VideoQualitySelectorDialog( supportingContent = { Text( text = preset.subtitle(), - style = ElementTheme.materialTypography.bodyMedium, + style = ElementTheme.typography.fontBodyMdRegular, color = ElementTheme.colors.textSecondary, ) }, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/OnDoneListener.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/OnDoneListener.kt index 5b5fab6d8a..948370fc89 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/OnDoneListener.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/OnDoneListener.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/error/ErrorFormatter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/error/ErrorFormatter.kt index 51507667ba..89b14fefb7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/error/ErrorFormatter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/error/ErrorFormatter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/DefaultMediaOptimizationSelectorPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/DefaultMediaOptimizationSelectorPresenter.kt index b62b4116e6..c81c306f90 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/DefaultMediaOptimizationSelectorPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/DefaultMediaOptimizationSelectorPresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -24,13 +25,12 @@ import io.element.android.libraries.di.SessionScope import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.mediaupload.api.MaxUploadSizeProvider +import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider import io.element.android.libraries.mediaupload.api.compressorHelper import io.element.android.libraries.mediaviewer.api.local.LocalMedia -import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.preferences.api.store.VideoCompressionPreset import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.flow.first import timber.log.Timber import kotlin.math.roundToLong @@ -38,8 +38,8 @@ import kotlin.math.roundToLong class DefaultMediaOptimizationSelectorPresenter( @Assisted private val localMedia: LocalMedia, private val maxUploadSizeProvider: MaxUploadSizeProvider, - private val sessionPreferencesStore: SessionPreferencesStore, private val featureFlagService: FeatureFlagService, + private val mediaOptimizationConfigProvider: MediaOptimizationConfigProvider, mediaExtractorFactory: VideoMetadataExtractor.Factory, ) : MediaOptimizationSelectorPresenter { @ContributesBinding(SessionScope::class) @@ -123,11 +123,12 @@ class DefaultMediaOptimizationSelectorPresenter( var selectedVideoOptimizationPreset by remember { mutableStateOf>(AsyncData.Loading()) } LaunchedEffect(videoSizeEstimations.dataOrNull()) { - selectedImageOptimization = AsyncData.Success(sessionPreferencesStore.doesOptimizeImages().first()) + val mediaOptimizationConfig = mediaOptimizationConfigProvider.get() + selectedImageOptimization = AsyncData.Success(mediaOptimizationConfig.compressImages) // Find the best video preset based on the default preset and the video size estimations // Since the estimation for the current preset may be way too large to upload, we check the ones that provide lower file sizes selectedVideoOptimizationPreset = findBestVideoPreset( - defaultVideoPreset = sessionPreferencesStore.getVideoCompressionPreset().first(), + defaultVideoPreset = mediaOptimizationConfig.videoCompressionPreset, videoSizeEstimations = videoSizeEstimations, ) } @@ -172,7 +173,7 @@ class DefaultMediaOptimizationSelectorPresenter( selectedVideoPreset = selectedVideoOptimizationPreset.dataOrNull(), displayMediaSelectorViews = displayMediaSelectorViews, displayVideoPresetSelectorDialog = displayVideoPresetSelectorDialog, - eventSink = { handleEvent(it) }, + eventSink = ::handleEvent, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/MediaOptimizationSelectorEvent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/MediaOptimizationSelectorEvent.kt index 3f6beaa750..ec1c3b82fe 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/MediaOptimizationSelectorEvent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/MediaOptimizationSelectorEvent.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/MediaOptimizationSelectorPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/MediaOptimizationSelectorPresenter.kt index 5efa9964ff..80cdfd9467 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/MediaOptimizationSelectorPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/MediaOptimizationSelectorPresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/MediaOptimizationSelectorState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/MediaOptimizationSelectorState.kt index 24e76f0f0b..29e51d39af 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/MediaOptimizationSelectorState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/MediaOptimizationSelectorState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/VideoMetadataExtractor.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/VideoMetadataExtractor.kt index a63668acaa..a6945b5ebe 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/VideoMetadataExtractor.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/video/VideoMetadataExtractor.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleAcknowledgementRepository.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleAcknowledgementRepository.kt new file mode 100644 index 0000000000..1fa992fc3e --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleAcknowledgementRepository.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.messages.impl.crypto.historyvisible + +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.androidutils.hash.hash +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +interface HistoryVisibleAcknowledgementRepository { + fun hasAcknowledged(roomId: RoomId): Flow + suspend fun setAcknowledged(roomId: RoomId, value: Boolean) +} + +@ContributesBinding(SessionScope::class) +class DefaultHistoryVisibleAcknowledgementRepository( + sessionId: SessionId, + preferenceDataStoreFactory: PreferenceDataStoreFactory, +) : HistoryVisibleAcknowledgementRepository { + val store = + sessionId.value.hash().take(16).let { hash -> + preferenceDataStoreFactory.create("elementx_historyvisible_$hash") + } + + override fun hasAcknowledged(roomId: RoomId): Flow { + return store.data.map { prefs -> + val acknowledged = prefs[booleanPreferencesKey(roomId.value)] ?: false + acknowledged + } + } + + override suspend fun setAcknowledged(roomId: RoomId, value: Boolean) { + store.edit { prefs -> + prefs[booleanPreferencesKey(roomId.value)] = value + } + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleEvent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleEvent.kt new file mode 100644 index 0000000000..775d9c00d4 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleEvent.kt @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.messages.impl.crypto.historyvisible + +sealed interface HistoryVisibleEvent { + data object Acknowledge : HistoryVisibleEvent +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleState.kt new file mode 100644 index 0000000000..3f980eb086 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleState.kt @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.messages.impl.crypto.historyvisible + +data class HistoryVisibleState( + val showAlert: Boolean, + val eventSink: (HistoryVisibleEvent) -> Unit, +) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStatePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStatePresenter.kt new file mode 100644 index 0000000000..e79681cd5c --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStatePresenter.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.messages.impl.crypto.historyvisible + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import dev.zacsweers.metro.Inject +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.JoinedRoom +import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +@Inject +class HistoryVisibleStatePresenter( + private val featureFlagService: FeatureFlagService, + private val repository: HistoryVisibleAcknowledgementRepository, + private val room: JoinedRoom, +) : Presenter { + @Composable + override fun present(): HistoryVisibleState { + val isFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.EnableKeyShareOnInvite).collectAsState(initial = false) + val roomInfo by room.roomInfoFlow.collectAsState() + // Implicitly assume the alert is initially acknowledged to avoid flashes in UI. + val acknowledged by repository.hasAcknowledged(room.roomId).collectAsState(initial = true) + val isHistoryVisible = roomInfo.historyVisibility == RoomHistoryVisibility.Shared || roomInfo.historyVisibility == RoomHistoryVisibility.WorldReadable + + val coroutineScope = rememberCoroutineScope() + + LaunchedEffect(isHistoryVisible, acknowledged) { + if (!isHistoryVisible && acknowledged) { + // Clear the dismissed flag, if it is set to ensure that if a room is changed public -> private -> public, + // we show the banner again when it is set back to public. + repository.setAcknowledged(room.roomId, false) + } + } + + fun handleEvent(event: HistoryVisibleEvent) { + when (event) { + is HistoryVisibleEvent.Acknowledge -> coroutineScope.setAcknowledged(room.roomId, true) + } + } + + return HistoryVisibleState( + showAlert = isFeatureEnabled && isHistoryVisible && roomInfo.isEncrypted == true && !acknowledged, + eventSink = ::handleEvent, + ) + } + + private fun CoroutineScope.setAcknowledged(roomId: RoomId, value: Boolean) = launch { + repository.setAcknowledged(roomId, value) + } +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStateProvider.kt new file mode 100644 index 0000000000..752abdc76b --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStateProvider.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.messages.impl.crypto.historyvisible + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider + +class HistoryVisibleStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aHistoryVisibleState(showAlert = true), + ) +} + +internal fun aHistoryVisibleState( + showAlert: Boolean = false, + eventSink: (HistoryVisibleEvent) -> Unit = {}, +) = HistoryVisibleState( + showAlert, + eventSink = eventSink, +) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStateView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStateView.kt new file mode 100644 index 0000000000..d0655f695d --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStateView.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.messages.impl.crypto.historyvisible + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.appconfig.LearnMoreConfig +import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertLevel +import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertMolecule +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.text.stringWithLink +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun HistoryVisibleStateView( + state: HistoryVisibleState, + onLinkClick: (String, Boolean) -> Unit, + modifier: Modifier = Modifier, +) { + if (!state.showAlert) { + return + } + ComposerAlertMolecule( + modifier = modifier, + avatar = null, + showIcon = true, + level = ComposerAlertLevel.Info, + content = stringWithLink( + textRes = CommonStrings.crypto_history_visible, + url = LearnMoreConfig.HISTORY_VISIBLE_URL, + onLinkClick = { url -> onLinkClick(url, true) }, + ), + submitText = stringResource(CommonStrings.action_dismiss), + onSubmitClick = { state.eventSink(HistoryVisibleEvent.Acknowledge) }, + ) +} + +@PreviewsDayNight +@Composable +internal fun HistoryVisibleStateViewPreview( + @PreviewParameter(HistoryVisibleStateProvider::class) state: HistoryVisibleState, +) = ElementPreview { + HistoryVisibleStateView( + state = state, + onLinkClick = { _, _ -> }, + ) +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/MessagesViewWithHistoryVisiblePreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/MessagesViewWithHistoryVisiblePreview.kt new file mode 100644 index 0000000000..07cf5170d3 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/MessagesViewWithHistoryVisiblePreview.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.messages.impl.crypto.historyvisible + +import androidx.compose.runtime.Composable +import io.element.android.features.messages.impl.MessagesView +import io.element.android.features.messages.impl.aMessagesState +import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown + +@PreviewsDayNight +@Composable +internal fun MessagesViewWithHistoryVisiblePreview() = ElementPreview { + MessagesView( + state = aMessagesState( + composerState = aMessageComposerState( + textEditorState = aTextEditorStateMarkdown( + initialText = "", + initialFocus = false, + ) + ), + historyVisibleState = aHistoryVisibleState(showAlert = true), + ), + onBackClick = {}, + onRoomDetailsClick = {}, + onEventContentClick = { _, _ -> false }, + onUserDataClick = {}, + onLinkClick = { _, _ -> }, + onSendLocationClick = {}, + onCreatePollClick = {}, + onJoinCallClick = {}, + onViewAllPinnedMessagesClick = {}, + knockRequestsBannerView = {} + ) +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeEvent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeEvent.kt index 149789d97d..d11f5f90f2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeEvent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeEvent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeState.kt index 0ff989fe70..25344bf844 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenter.kt index 85675d784e..dcf9056905 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt index 858231ebd5..47d1947766 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateView.kt index 0f55985740..92352732b0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -19,6 +20,7 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.appconfig.LearnMoreConfig import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertLevel import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertMolecule import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -113,7 +115,7 @@ private fun ViolationAlert( }, submitText = stringResource(submitTextId), onSubmitClick = onSubmitClick, - isCritical = isCritical, + level = if (isCritical) ComposerAlertLevel.Critical else ComposerAlertLevel.Default, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt index cdd91e86e4..b434656f7a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/VerifiedUserSendFailure.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/VerifiedUserSendFailure.kt index 4c458922f7..0204617f08 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/VerifiedUserSendFailure.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/VerifiedUserSendFailure.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/VerifiedUserSendFailureFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/VerifiedUserSendFailureFactory.kt index 09f85805c8..65184d94ee 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/VerifiedUserSendFailureFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/VerifiedUserSendFailureFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureEvents.kt index f2b7523f50..242dc58453 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailurePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailurePresenter.kt index 54aa26e6ab..720e6762f7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailurePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailurePresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -47,7 +48,7 @@ class ResolveVerifiedUserSendFailurePresenter( } val coroutineScope = rememberCoroutineScope() - fun handleEvents(event: ResolveVerifiedUserSendFailureEvents) { + fun handleEvent(event: ResolveVerifiedUserSendFailureEvents) { when (event) { is ResolveVerifiedUserSendFailureEvents.ComputeForMessage -> { val sendState = event.messageEvent.localSendState as? LocalEventSendState.Failed.VerifiedUser @@ -92,7 +93,7 @@ class ResolveVerifiedUserSendFailurePresenter( verifiedUserSendFailure = verifiedUserSendFailure, resolveAction = resolveAction.value, retryAction = retryAction.value, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureState.kt index ead95f17f1..dfe9ac36de 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureStateProvider.kt index 47a29be2dd..251f0e6564 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureView.kt index b6b9762c44..3f881e7bb9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/VerifiedUserSendFailureIterator.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/VerifiedUserSendFailureIterator.kt index 0e9f3c0b32..db82606887 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/VerifiedUserSendFailureIterator.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/VerifiedUserSendFailureIterator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/VerifiedUserSendFailureResolver.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/VerifiedUserSendFailureResolver.kt index 166ce212dd..c669606b2c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/VerifiedUserSendFailureResolver.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/VerifiedUserSendFailureResolver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesBindsModule.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesBindsModule.kt index 50f9606273..a88dbb1b49 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesBindsModule.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesBindsModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,6 +11,8 @@ package io.element.android.features.messages.impl.di import dev.zacsweers.metro.BindingContainer import dev.zacsweers.metro.Binds import dev.zacsweers.metro.ContributesTo +import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState +import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleStatePresenter import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState import io.element.android.features.messages.impl.crypto.identity.IdentityChangeStatePresenter import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailurePresenter @@ -60,4 +63,7 @@ interface MessagesBindsModule { @Binds fun bindIdentityChangeStatePresenter(presenter: IdentityChangeStatePresenter): Presenter + + @Binds + fun bindHistoryVisibleStatePresenter(presenter: HistoryVisibleStatePresenter): Presenter } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesProvidesModule.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesProvidesModule.kt index d353ae2c09..856a14cdb0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesProvidesModule.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesProvidesModule.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/ComposerDraftService.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/ComposerDraftService.kt index 489e5bd046..765fa69172 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/ComposerDraftService.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/ComposerDraftService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/ComposerDraftStore.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/ComposerDraftStore.kt index c460acb065..0e8d70791c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/ComposerDraftStore.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/ComposerDraftStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/DefaultComposerDraftService.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/DefaultComposerDraftService.kt index a0cb3877cb..5ac81a3b7f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/DefaultComposerDraftService.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/DefaultComposerDraftService.kt @@ -1,21 +1,20 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl.draft import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.room.draft.ComposerDraft @ContributesBinding(RoomScope::class) -@Inject class DefaultComposerDraftService( private val volatileComposerDraftStore: VolatileComposerDraftStore, private val matrixComposerDraftStore: MatrixComposerDraftStore, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/MatrixComposerDraftStore.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/MatrixComposerDraftStore.kt index 6ce82b6522..e10585171a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/MatrixComposerDraftStore.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/MatrixComposerDraftStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/VolatileComposerDraftStore.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/VolatileComposerDraftStore.kt index 138763bd13..0dd6e334d3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/VolatileComposerDraftStore.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/draft/VolatileComposerDraftStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/ConfirmingLinkClick.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/ConfirmingLinkClick.kt index 612b8a231b..88106db62a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/ConfirmingLinkClick.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/ConfirmingLinkClick.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkChecker.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkChecker.kt index eec953f4cd..1a8456b439 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkChecker.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkChecker.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.features.messages.impl.link import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.extensions.containsRtLOverride import io.element.android.wysiwyg.link.Link @@ -20,7 +20,6 @@ interface LinkChecker { } @ContributesBinding(AppScope::class) -@Inject class DefaultLinkChecker : LinkChecker { override fun isSafe(link: Link): Boolean { return if (link.url.containsRtLOverride()) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkEvents.kt index 717b68f57f..ce817bf642 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkPresenter.kt index 9c4694bafa..b6886652d5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkPresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -24,16 +25,16 @@ class LinkPresenter( override fun present(): LinkState { val linkClick: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } - fun handleEvents(linkEvents: LinkEvents) { - when (linkEvents) { + fun handleEvent(event: LinkEvents) { + when (event) { is LinkEvents.OnLinkClick -> { linkClick.value = AsyncAction.Loading - val result = linkChecker.isSafe(linkEvents.link) + val result = linkChecker.isSafe(event.link) if (result) { - linkClick.value = AsyncAction.Success(linkEvents.link) + linkClick.value = AsyncAction.Success(event.link) } else { // Confirm first - linkClick.value = ConfirmingLinkClick(linkEvents.link) + linkClick.value = ConfirmingLinkClick(event.link) } } LinkEvents.Confirm -> { @@ -48,7 +49,7 @@ class LinkPresenter( } return LinkState( linkClick = linkClick.value, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkState.kt index 0986378096..c06d23ef9a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkStateProvider.kt index 478f8bf322..8388cbea52 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkView.kt index 53306c8e99..1a7558de05 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/link/LinkView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 fe40b21499..1fdb61f484 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -63,7 +64,7 @@ internal fun AttachmentsBottomSheet( // Send 'DismissAttachmentMenu' event when the bottomsheet was just hidden LaunchedEffect(isVisible) { if (!isVisible) { - state.eventSink(MessageComposerEvents.DismissAttachmentMenu) + state.eventSink(MessageComposerEvent.DismissAttachmentMenu) } } @@ -98,25 +99,25 @@ private fun AttachmentSourcePickerMenu( .imePadding() ) { ListItem( - modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera) }, + modifier = Modifier.clickable { state.eventSink(MessageComposerEvent.PickAttachmentSource.PhotoFromCamera) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.TakePhoto())), headlineContent = { Text(stringResource(R.string.screen_room_attachment_source_camera_photo)) }, style = ListItemStyle.Primary, ) ListItem( - modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.VideoFromCamera) }, + modifier = Modifier.clickable { state.eventSink(MessageComposerEvent.PickAttachmentSource.VideoFromCamera) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.VideoCall())), headlineContent = { Text(stringResource(R.string.screen_room_attachment_source_camera_video)) }, style = ListItemStyle.Primary, ) ListItem( - modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) }, + modifier = Modifier.clickable { state.eventSink(MessageComposerEvent.PickAttachmentSource.FromGallery) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Image())), headlineContent = { Text(stringResource(R.string.screen_room_attachment_source_gallery)) }, style = ListItemStyle.Primary, ) ListItem( - modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) }, + modifier = Modifier.clickable { state.eventSink(MessageComposerEvent.PickAttachmentSource.FromFiles) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Attachment())), headlineContent = { Text(stringResource(R.string.screen_room_attachment_source_files)) }, style = ListItemStyle.Primary, @@ -124,7 +125,7 @@ private fun AttachmentSourcePickerMenu( if (state.canShareLocation) { ListItem( modifier = Modifier.clickable { - state.eventSink(MessageComposerEvents.PickAttachmentSource.Location) + state.eventSink(MessageComposerEvent.PickAttachmentSource.Location) onSendLocationClick() }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.LocationPin())), @@ -134,7 +135,7 @@ private fun AttachmentSourcePickerMenu( } ListItem( modifier = Modifier.clickable { - state.eventSink(MessageComposerEvents.PickAttachmentSource.Poll) + state.eventSink(MessageComposerEvent.PickAttachmentSource.Poll) onCreatePollClick() }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Polls())), @@ -143,7 +144,7 @@ private fun AttachmentSourcePickerMenu( ) if (enableTextFormatting) { ListItem( - modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.ToggleTextFormatting(enabled = true)) }, + modifier = Modifier.clickable { state.eventSink(MessageComposerEvent.ToggleTextFormatting(enabled = true)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.TextFormatting())), headlineContent = { Text(stringResource(R.string.screen_room_attachment_text_formatting)) }, style = ListItemStyle.Primary, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DefaultMessageComposerContext.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DefaultMessageComposerContext.kt index b895572c43..495731b41e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DefaultMessageComposerContext.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DefaultMessageComposerContext.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.features.messages.api.MessageComposerContext import io.element.android.libraries.di.RoomScope @@ -19,7 +19,6 @@ import io.element.android.libraries.textcomposer.model.MessageComposerMode @SingleIn(RoomScope::class) @ContributesBinding(RoomScope::class) -@Inject class DefaultMessageComposerContext : MessageComposerContext { override var composerMode: MessageComposerMode by mutableStateOf(MessageComposerMode.Normal) internal set diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DisabledComposerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DisabledComposerView.kt index 02d009d2ce..c6429048c5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DisabledComposerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/DisabledComposerView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvent.kt similarity index 65% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvent.kt index bcbd575c73..ae82c60f2a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerEvent.kt @@ -1,28 +1,27 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl.messagecomposer import android.net.Uri -import androidx.compose.runtime.Immutable import io.element.android.libraries.textcomposer.mentions.ResolvedSuggestion import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.Suggestion -@Immutable -sealed interface MessageComposerEvents { - data object ToggleFullScreenState : MessageComposerEvents - data object SendMessage : MessageComposerEvents - data class SendUri(val uri: Uri) : MessageComposerEvents - data object CloseSpecialMode : MessageComposerEvents - data class SetMode(val composerMode: MessageComposerMode) : MessageComposerEvents - data object AddAttachment : MessageComposerEvents - data object DismissAttachmentMenu : MessageComposerEvents - sealed interface PickAttachmentSource : MessageComposerEvents { +sealed interface MessageComposerEvent { + data object ToggleFullScreenState : MessageComposerEvent + data object SendMessage : MessageComposerEvent + data class SendUri(val uri: Uri) : MessageComposerEvent + data object CloseSpecialMode : MessageComposerEvent + data class SetMode(val composerMode: MessageComposerMode) : MessageComposerEvent + data object AddAttachment : MessageComposerEvent + data object DismissAttachmentMenu : MessageComposerEvent + sealed interface PickAttachmentSource : MessageComposerEvent { data object FromGallery : PickAttachmentSource data object FromFiles : PickAttachmentSource data object PhotoFromCamera : PickAttachmentSource @@ -30,10 +29,11 @@ sealed interface MessageComposerEvents { data object Location : PickAttachmentSource data object Poll : PickAttachmentSource } - data class ToggleTextFormatting(val enabled: Boolean) : MessageComposerEvents - data class Error(val error: Throwable) : MessageComposerEvents - data class TypingNotice(val isTyping: Boolean) : MessageComposerEvents - data class SuggestionReceived(val suggestion: Suggestion?) : MessageComposerEvents - data class InsertSuggestion(val resolvedSuggestion: ResolvedSuggestion) : MessageComposerEvents - data object SaveDraft : MessageComposerEvents + + data class ToggleTextFormatting(val enabled: Boolean) : MessageComposerEvent + data class Error(val error: Throwable) : MessageComposerEvent + data class TypingNotice(val isTyping: Boolean) : MessageComposerEvent + data class SuggestionReceived(val suggestion: Suggestion?) : MessageComposerEvent + data class InsertSuggestion(val resolvedSuggestion: ResolvedSuggestion) : MessageComposerEvent + data object SaveDraft : MessageComposerEvent } 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 9505f0d758..fd803109c7 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -54,15 +55,16 @@ import io.element.android.libraries.matrix.api.room.draft.ComposerDraft import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType import io.element.android.libraries.matrix.api.room.getDirectRoomMember import io.element.android.libraries.matrix.api.room.isDm +import io.element.android.libraries.matrix.api.room.powerlevels.use import io.element.android.libraries.matrix.api.timeline.TimelineException import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails import io.element.android.libraries.matrix.ui.messages.reply.map import io.element.android.libraries.mediapickers.api.PickerProvider import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider -import io.element.android.libraries.mediaupload.api.MediaSender +import io.element.android.libraries.mediaupload.api.MediaSenderFactory import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory -import io.element.android.libraries.permissions.api.PermissionsEvents +import io.element.android.libraries.permissions.api.PermissionsEvent import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.push.api.notifications.conversations.NotificationConversationService @@ -97,6 +99,7 @@ import timber.log.Timber import kotlin.time.Duration.Companion.seconds import io.element.android.libraries.core.mimetype.MimeTypes.Any as AnyMimeTypes +@Suppress("LargeClass") @AssistedInject class MessageComposerPresenter( @Assisted private val navigator: MessagesNavigator, @@ -106,7 +109,7 @@ class MessageComposerPresenter( private val mediaPickerProvider: PickerProvider, private val sessionPreferencesStore: SessionPreferencesStore, private val localMediaFactory: LocalMediaFactory, - private val mediaSenderFactory: MediaSender.Factory, + mediaSenderFactory: MediaSenderFactory, private val snackbarDispatcher: SnackbarDispatcher, private val analyticsService: AnalyticsService, private val locationService: LocationService, @@ -131,7 +134,7 @@ class MessageComposerPresenter( private val mediaSender = mediaSenderFactory.create(timelineMode = timelineController.mainTimelineMode()) private val cameraPermissionPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA) - private var pendingEvent: MessageComposerEvents? = null + private var pendingEvent: MessageComposerEvent? = null private val suggestionSearchTrigger = MutableStateFlow(null) // Used to disable some UI related elements in tests @@ -185,8 +188,8 @@ class MessageComposerPresenter( LaunchedEffect(cameraPermissionState.permissionGranted) { if (cameraPermissionState.permissionGranted) { when (pendingEvent) { - is MessageComposerEvents.PickAttachmentSource.PhotoFromCamera -> cameraPhotoPicker.launch() - is MessageComposerEvents.PickAttachmentSource.VideoFromCamera -> cameraVideoPicker.launch() + is MessageComposerEvent.PickAttachmentSource.PhotoFromCamera -> cameraPhotoPicker.launch() + is MessageComposerEvent.PickAttachmentSource.VideoFromCamera -> cameraVideoPicker.launch() else -> Unit } pendingEvent = null @@ -227,10 +230,10 @@ class MessageComposerPresenter( } } - fun handleEvents(event: MessageComposerEvents) { + fun handleEvent(event: MessageComposerEvent) { when (event) { - MessageComposerEvents.ToggleFullScreenState -> isFullScreen.value = !isFullScreen.value - MessageComposerEvents.CloseSpecialMode -> { + MessageComposerEvent.ToggleFullScreenState -> isFullScreen.value = !isFullScreen.value + MessageComposerEvent.CloseSpecialMode -> { if (messageComposerContext.composerMode.isEditing) { localCoroutineScope.launch { resetComposer(markdownTextEditorState, richTextEditorState, fromEdit = true) @@ -239,13 +242,13 @@ class MessageComposerPresenter( messageComposerContext.composerMode = MessageComposerMode.Normal } } - is MessageComposerEvents.SendMessage -> { + is MessageComposerEvent.SendMessage -> { sessionCoroutineScope.sendMessage( markdownTextEditorState = markdownTextEditorState, richTextEditorState = richTextEditorState, ) } - is MessageComposerEvents.SendUri -> { + is MessageComposerEvent.SendUri -> { val inReplyToEventId = (messageComposerContext.composerMode as? MessageComposerMode.Reply)?.eventId sessionCoroutineScope.sendAttachment( attachment = Attachment.Media( @@ -262,65 +265,65 @@ class MessageComposerPresenter( // Reset composer since the attachment has been sent messageComposerContext.composerMode = MessageComposerMode.Normal } - is MessageComposerEvents.SetMode -> { + is MessageComposerEvent.SetMode -> { localCoroutineScope.setMode(event.composerMode, markdownTextEditorState, richTextEditorState) } - MessageComposerEvents.AddAttachment -> localCoroutineScope.launch { + MessageComposerEvent.AddAttachment -> localCoroutineScope.launch { showAttachmentSourcePicker = true } - MessageComposerEvents.DismissAttachmentMenu -> showAttachmentSourcePicker = false - MessageComposerEvents.PickAttachmentSource.FromGallery -> localCoroutineScope.launch { + MessageComposerEvent.DismissAttachmentMenu -> showAttachmentSourcePicker = false + MessageComposerEvent.PickAttachmentSource.FromGallery -> localCoroutineScope.launch { showAttachmentSourcePicker = false galleryMediaPicker.launch() } - MessageComposerEvents.PickAttachmentSource.FromFiles -> localCoroutineScope.launch { + MessageComposerEvent.PickAttachmentSource.FromFiles -> localCoroutineScope.launch { showAttachmentSourcePicker = false filesPicker.launch() } - MessageComposerEvents.PickAttachmentSource.PhotoFromCamera -> localCoroutineScope.launch { + MessageComposerEvent.PickAttachmentSource.PhotoFromCamera -> localCoroutineScope.launch { showAttachmentSourcePicker = false if (cameraPermissionState.permissionGranted) { cameraPhotoPicker.launch() } else { pendingEvent = event - cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions) + cameraPermissionState.eventSink(PermissionsEvent.RequestPermissions) } } - MessageComposerEvents.PickAttachmentSource.VideoFromCamera -> localCoroutineScope.launch { + MessageComposerEvent.PickAttachmentSource.VideoFromCamera -> localCoroutineScope.launch { showAttachmentSourcePicker = false if (cameraPermissionState.permissionGranted) { cameraVideoPicker.launch() } else { pendingEvent = event - cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions) + cameraPermissionState.eventSink(PermissionsEvent.RequestPermissions) } } - MessageComposerEvents.PickAttachmentSource.Location -> { + MessageComposerEvent.PickAttachmentSource.Location -> { showAttachmentSourcePicker = false // Navigation to the location picker screen is done at the view layer } - MessageComposerEvents.PickAttachmentSource.Poll -> { + MessageComposerEvent.PickAttachmentSource.Poll -> { showAttachmentSourcePicker = false // Navigation to the create poll screen is done at the view layer } - is MessageComposerEvents.ToggleTextFormatting -> { + is MessageComposerEvent.ToggleTextFormatting -> { showAttachmentSourcePicker = false localCoroutineScope.toggleTextFormatting(event.enabled, markdownTextEditorState, richTextEditorState) } - is MessageComposerEvents.Error -> { + is MessageComposerEvent.Error -> { analyticsService.trackError(event.error) } - is MessageComposerEvents.TypingNotice -> { + is MessageComposerEvent.TypingNotice -> { if (sendTypingNotifications) { localCoroutineScope.launch { room.typingNotice(event.isTyping) } } } - is MessageComposerEvents.SuggestionReceived -> { + is MessageComposerEvent.SuggestionReceived -> { suggestionSearchTrigger.value = event.suggestion } - is MessageComposerEvents.InsertSuggestion -> { + is MessageComposerEvent.InsertSuggestion -> { localCoroutineScope.launch { if (showTextFormatting) { when (val suggestion = event.resolvedSuggestion) { @@ -347,7 +350,7 @@ class MessageComposerPresenter( } } } - MessageComposerEvents.SaveDraft -> { + MessageComposerEvent.SaveDraft -> { val draft = createDraftFromState(markdownTextEditorState, richTextEditorState) sessionCoroutineScope.updateDraft(draft, isVolatile = false) } @@ -382,7 +385,7 @@ class MessageComposerPresenter( suggestions = suggestions.toImmutableList(), resolveMentionDisplay = resolveMentionDisplay, resolveAtRoomMentionDisplay = resolveAtRoomMentionDisplay, - eventSink = { handleEvents(it) }, + eventSink = ::handleEvent, ) } @@ -395,7 +398,9 @@ class MessageComposerPresenter( val currentUserId = room.sessionId suspend fun canSendRoomMention(): Boolean { - val userCanSendAtRoom = room.canUserTriggerRoomNotification(currentUserId).getOrDefault(false) + val userCanSendAtRoom = room.roomPermissions().use(false) { perms -> + perms.canOwnUserTriggerRoomNotification() + } return !room.isDm() && userCanSendAtRoom } @@ -528,7 +533,7 @@ class MessageComposerPresenter( ) val mediaAttachment = Attachment.Media(localMedia) val inReplyToEventId = (messageComposerContext.composerMode as? MessageComposerMode.Reply)?.eventId - navigator.onPreviewAttachment(persistentListOf(mediaAttachment), inReplyToEventId) + navigator.navigateToPreviewAttachments(persistentListOf(mediaAttachment), inReplyToEventId) // Reset composer since the attachment will be sent in a separate flow messageComposerContext.composerMode = MessageComposerMode.Normal 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 6eadc14260..424e8c07b9 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 @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -25,5 +26,5 @@ data class MessageComposerState( val suggestions: ImmutableList, val resolveMentionDisplay: (String, String) -> TextDisplay, val resolveAtRoomMentionDisplay: () -> TextDisplay, - val eventSink: (MessageComposerEvents) -> Unit, + val eventSink: (MessageComposerEvent) -> 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 36e2c69f96..a06bf30dad 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 @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -31,7 +32,7 @@ fun aMessageComposerState( showAttachmentSourcePicker: Boolean = false, canShareLocation: Boolean = true, suggestions: ImmutableList = persistentListOf(), - eventSink: (MessageComposerEvents) -> Unit = {}, + eventSink: (MessageComposerEvent) -> Unit = {}, ) = MessageComposerState( textEditorState = textEditorState, isFullScreen = isFullScreen, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt index 7206d2571b..4b346e0c15 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,7 +18,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp -import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerEvents +import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerEvent import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerState import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerStateProvider import io.element.android.features.messages.api.timeline.voicemessages.composer.aVoiceMessageComposerState @@ -37,36 +38,36 @@ internal fun MessageComposerView( ) { val view = LocalView.current fun sendMessage() { - state.eventSink(MessageComposerEvents.SendMessage) + state.eventSink(MessageComposerEvent.SendMessage) } fun sendUri(uri: Uri) { - state.eventSink(MessageComposerEvents.SendUri(uri)) + state.eventSink(MessageComposerEvent.SendUri(uri)) } fun onAddAttachment() { - state.eventSink(MessageComposerEvents.AddAttachment) + state.eventSink(MessageComposerEvent.AddAttachment) } fun onCloseSpecialMode() { - state.eventSink(MessageComposerEvents.CloseSpecialMode) + state.eventSink(MessageComposerEvent.CloseSpecialMode) } fun onDismissTextFormatting() { view.clearFocus() - state.eventSink(MessageComposerEvents.ToggleTextFormatting(enabled = false)) + state.eventSink(MessageComposerEvent.ToggleTextFormatting(enabled = false)) } fun onSuggestionReceived(suggestion: Suggestion?) { - state.eventSink(MessageComposerEvents.SuggestionReceived(suggestion)) + state.eventSink(MessageComposerEvent.SuggestionReceived(suggestion)) } fun onError(error: Throwable) { - state.eventSink(MessageComposerEvents.Error(error)) + state.eventSink(MessageComposerEvent.Error(error)) } fun onTyping(typing: Boolean) { - state.eventSink(MessageComposerEvents.TypingNotice(typing)) + state.eventSink(MessageComposerEvent.TypingNotice(typing)) } val coroutineScope = rememberCoroutineScope() @@ -77,19 +78,19 @@ internal fun MessageComposerView( } val onVoiceRecorderEvent = { press: VoiceMessageRecorderEvent -> - voiceMessageState.eventSink(VoiceMessageComposerEvents.RecorderEvent(press)) + voiceMessageState.eventSink(VoiceMessageComposerEvent.RecorderEvent(press)) } val onSendVoiceMessage = { - voiceMessageState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage) + voiceMessageState.eventSink(VoiceMessageComposerEvent.SendVoiceMessage) } val onDeleteVoiceMessage = { - voiceMessageState.eventSink(VoiceMessageComposerEvents.DeleteVoiceMessage) + voiceMessageState.eventSink(VoiceMessageComposerEvent.DeleteVoiceMessage) } val onVoicePlayerEvent = { event: VoiceMessagePlayerEvent -> - voiceMessageState.eventSink(VoiceMessageComposerEvents.PlayerEvent(event)) + voiceMessageState.eventSink(VoiceMessageComposerEvent.PlayerEvent(event)) } TextComposer( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/RichTextEditorStateFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/RichTextEditorStateFactory.kt index c22325a9a5..79fc6e7bbc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/RichTextEditorStateFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/RichTextEditorStateFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.features.messages.impl.messagecomposer import androidx.compose.runtime.Composable import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.wysiwyg.compose.RichTextEditorState import io.element.android.wysiwyg.compose.rememberRichTextEditorState @@ -20,7 +20,6 @@ interface RichTextEditorStateFactory { } @ContributesBinding(AppScope::class) -@Inject class DefaultRichTextEditorStateFactory : RichTextEditorStateFactory { @Composable override fun remember(): RichTextEditorState { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/RoomAliasSuggestionsDataSource.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/RoomAliasSuggestionsDataSource.kt index 5b3a1edf1e..d1ba363488 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/RoomAliasSuggestionsDataSource.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/RoomAliasSuggestionsDataSource.kt @@ -1,14 +1,14 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl.messagecomposer.suggestions import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId @@ -28,7 +28,6 @@ interface RoomAliasSuggestionsDataSource { } @ContributesBinding(SessionScope::class) -@Inject class DefaultRoomAliasSuggestionsDataSource( private val roomListService: RoomListService, ) : RoomAliasSuggestionsDataSource { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt index 0f0480f404..e9e38e1730 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsPickerView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessor.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessor.kt index 4f008705a5..789a027cf7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessor.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -69,6 +70,7 @@ class SuggestionsProcessor { } } SuggestionType.Command, + SuggestionType.Emoji, is SuggestionType.Custom -> { // Clear suggestions emptyList() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/PinnedEventsTimelineProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/DefaultPinnedEventsTimelineProvider.kt similarity index 90% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/PinnedEventsTimelineProvider.kt rename to features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/DefaultPinnedEventsTimelineProvider.kt index 811516e022..ee3167712b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/PinnedEventsTimelineProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/DefaultPinnedEventsTimelineProvider.kt @@ -1,14 +1,16 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl.pinned -import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.SingleIn +import io.element.android.features.messages.api.pinned.PinnedEventsTimelineProvider import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.mapState @@ -17,7 +19,6 @@ import io.element.android.libraries.matrix.api.room.CreateTimelineParams import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.timeline.Timeline -import io.element.android.libraries.matrix.api.timeline.TimelineProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableStateFlow @@ -29,12 +30,12 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.withContext @SingleIn(RoomScope::class) -@Inject -class PinnedEventsTimelineProvider( +@ContributesBinding(RoomScope::class) +class DefaultPinnedEventsTimelineProvider( private val room: JoinedRoom, private val syncService: SyncService, private val dispatchers: CoroutineDispatchers, -) : TimelineProvider { +) : PinnedEventsTimelineProvider { private val _timelineStateFlow: MutableStateFlow> = MutableStateFlow(AsyncData.Uninitialized) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerEvents.kt index 92525724b7..d4926404fe 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerItem.kt index 0f1bfe97fc..7e7904edde 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerItemFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerItemFactory.kt index c6e177d87a..d1d53b36b4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerItemFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerItemFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenter.kt index 5833da56dc..eada4b06d7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,7 +19,7 @@ import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import dev.zacsweers.metro.Inject -import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider +import io.element.android.features.messages.impl.pinned.DefaultPinnedEventsTimelineProvider import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.room.BaseRoom @@ -35,7 +36,7 @@ import kotlinx.coroutines.flow.onEach class PinnedMessagesBannerPresenter( private val room: BaseRoom, private val itemFactory: PinnedMessagesBannerItemFactory, - private val pinnedEventsTimelineProvider: PinnedEventsTimelineProvider, + private val pinnedEventsTimelineProvider: DefaultPinnedEventsTimelineProvider, ) : Presenter { private val pinnedItems = mutableStateOf>>(AsyncData.Uninitialized) @@ -70,7 +71,7 @@ class PinnedMessagesBannerPresenter( expectedPinnedMessagesCount = expectedPinnedMessagesCount, pinnedItems = pinnedItems.value, currentPinnedMessageIndex = currentPinnedMessageIndex, - eventSink = ::handleEvent + eventSink = ::handleEvent, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerState.kt index 785de15954..0ed43376ff 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerStateProvider.kt index 702f675d63..ad7713e6f3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerView.kt index 194cc433f4..ee44f7b95d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListEvents.kt index 2e116d358f..4a3dcf6146 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListNavigator.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListNavigator.kt index d33ad54eed..a3728cb9f4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListNavigator.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListNavigator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,7 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo interface PinnedMessagesListNavigator { - fun onViewInTimelineClick(eventId: EventId) - fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) - fun onForwardEventClick(eventId: EventId) + fun viewInTimeline(eventId: EventId) + fun navigateToEventDebugInfo(eventId: EventId?, debugInfo: TimelineItemDebugInfo) + fun forwardEvent(eventId: EventId) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListNode.kt index 8ba776c520..292a77ba6a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,10 +15,10 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.stringResource import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode @@ -27,6 +28,7 @@ import io.element.android.features.messages.impl.timeline.di.TimelineItemPresent import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.libraries.androidutils.system.copyToClipboard import io.element.android.libraries.androidutils.system.openUrlInExternalApp +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId @@ -34,7 +36,6 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo -import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.ui.strings.CommonStrings @ContributesNode(RoomScope::class) @@ -48,14 +49,15 @@ class PinnedMessagesListNode( private val permalinkParser: PermalinkParser, ) : Node(buildContext, plugins = plugins), PinnedMessagesListNavigator { interface Callback : Plugin { - fun onEventClick(event: TimelineItem.Event) - fun onUserDataClick(userId: UserId) - fun onViewInTimelineClick(eventId: EventId) - fun onRoomPermalinkClick(data: PermalinkData.RoomLink) - fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) - fun onForwardEventClick(eventId: EventId) + fun handleEventClick(event: TimelineItem.Event) + fun navigateToRoomMemberDetails(userId: UserId) + fun viewInTimeline(eventId: EventId) + fun handlePermalinkClick(data: PermalinkData.RoomLink) + fun navigateToEventDebugInfo(eventId: EventId?, debugInfo: TimelineItemDebugInfo) + fun handleForwardEventClick(eventId: EventId) } + private val callback: Callback = callback() private val presenter = presenterFactory.create( navigator = this, actionListPresenter = actionListPresenterFactory.create( @@ -63,25 +65,16 @@ class PinnedMessagesListNode( timelineMode = Timeline.Mode.PinnedEvents, ) ) - private val callbacks = plugins() - - private fun onEventClick(event: TimelineItem.Event) { - return callbacks.forEach { it.onEventClick(event) } - } - - private fun onUserDataClick(user: MatrixUser) { - callbacks.forEach { it.onUserDataClick(user.userId) } - } private fun onLinkClick(context: Context, url: String) { when (val permalink = permalinkParser.parse(url)) { is PermalinkData.UserLink -> { // Open the room member profile, it will fallback to // the user profile if the user is not in the room - callbacks.forEach { it.onUserDataClick(permalink.userId) } + callback.navigateToRoomMemberDetails(permalink.userId) } is PermalinkData.RoomLink -> { - callbacks.forEach { it.onRoomPermalinkClick(permalink) } + callback.handlePermalinkClick(permalink) } is PermalinkData.FallbackLink, is PermalinkData.RoomEmailInviteLink -> { @@ -90,16 +83,16 @@ class PinnedMessagesListNode( } } - override fun onViewInTimelineClick(eventId: EventId) { - callbacks.forEach { it.onViewInTimelineClick(eventId) } + override fun viewInTimeline(eventId: EventId) { + callback.viewInTimeline(eventId) } - override fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { - callbacks.forEach { it.onShowEventDebugInfoClick(eventId, debugInfo) } + override fun navigateToEventDebugInfo(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { + callback.navigateToEventDebugInfo(eventId, debugInfo) } - override fun onForwardEventClick(eventId: EventId) { - callbacks.forEach { it.onForwardEventClick(eventId) } + override fun forwardEvent(eventId: EventId) { + callback.handleForwardEventClick(eventId) } @Composable @@ -108,21 +101,22 @@ class PinnedMessagesListNode( LocalTimelineItemPresenterFactories provides timelineItemPresenterFactories, ) { val context = LocalContext.current + val toastMessage = stringResource(CommonStrings.common_copied_to_clipboard) val view = LocalView.current val state = presenter.present() PinnedMessagesListView( state = state, onBackClick = ::navigateUp, - onEventClick = ::onEventClick, - onUserDataClick = ::onUserDataClick, + onEventClick = callback::handleEventClick, + onUserDataClick = { callback.navigateToRoomMemberDetails(it.userId) }, onLinkClick = { link -> onLinkClick(context, link.url) }, onLinkLongClick = { view.performHapticFeedback( HapticFeedbackConstants.LONG_PRESS ) context.copyToClipboard( - it.url, - context.getString(CommonStrings.common_copied_to_clipboard) + text = it.url, + toastMessage = toastMessage, ) }, modifier = modifier diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt index 0c7fb8948a..b2d2caa7f9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,11 +10,10 @@ package io.element.android.features.messages.impl.pinned.list import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue @@ -22,17 +22,19 @@ import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedInject import im.vector.app.features.analytics.plan.Interaction import im.vector.app.features.analytics.plan.PinUnpinAction +import io.element.android.features.messages.api.timeline.HtmlConverterProvider import io.element.android.features.messages.impl.UserEventPermissions import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.link.LinkState -import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider +import io.element.android.features.messages.impl.pinned.DefaultPinnedEventsTimelineProvider import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState import io.element.android.features.messages.impl.typing.TypingNotificationState +import io.element.android.features.messages.impl.userEventPermissions import io.element.android.features.roomcall.api.aStandByCallState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter @@ -42,11 +44,9 @@ import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.room.JoinedRoom -import io.element.android.libraries.matrix.api.room.powerlevels.canPinUnpin -import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther -import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn +import io.element.android.libraries.matrix.api.room.isDm +import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState import io.element.android.libraries.matrix.api.room.roomMembers -import io.element.android.libraries.matrix.ui.room.isDmAsState import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction @@ -66,7 +66,7 @@ class PinnedMessagesListPresenter( @Assisted private val navigator: PinnedMessagesListNavigator, private val room: JoinedRoom, timelineItemsFactoryCreator: TimelineItemsFactory.Creator, - private val timelineProvider: PinnedEventsTimelineProvider, + private val timelineProvider: DefaultPinnedEventsTimelineProvider, private val timelineProtectionPresenter: Presenter, private val linkPresenter: Presenter, private val snackbarDispatcher: SnackbarDispatcher, @@ -75,6 +75,7 @@ class PinnedMessagesListPresenter( private val sessionCoroutineScope: CoroutineScope, private val analyticsService: AnalyticsService, private val featureFlagService: FeatureFlagService, + private val htmlConverterProvider: HtmlConverterProvider, ) : Presenter { @AssistedFactory interface Factory { @@ -93,31 +94,34 @@ class PinnedMessagesListPresenter( @Composable override fun present(): PinnedMessagesListState { - val isDm by room.isDmAsState() - - val timelineRoomInfo = remember(isDm) { - TimelineRoomInfo( - isDm = isDm, - name = room.info().name, - // We don't need to compute those values - userHasPermissionToSendMessage = false, - userHasPermissionToSendReaction = false, - // We do not care about the call state here. - roomCallState = aStandByCallState(), - // don't compute this value or the pin icon will be shown - pinnedEventIds = emptyList(), - typingNotificationState = TypingNotificationState( - renderTypingNotifications = false, - typingMembers = persistentListOf(), - reserveSpace = false, - ), - predecessorRoom = room.predecessorRoom(), - ) + htmlConverterProvider.Update() + val roomInfo by room.roomInfoFlow.collectAsState() + val timelineRoomInfo by remember { + derivedStateOf { + TimelineRoomInfo( + isDm = roomInfo.isDm, + name = roomInfo.name, + // We don't need to compute those values + userHasPermissionToSendMessage = false, + userHasPermissionToSendReaction = false, + // We do not care about the call state here. + roomCallState = aStandByCallState(), + // don't compute this value or the pin icon will be shown + pinnedEventIds = persistentListOf(), + typingNotificationState = TypingNotificationState( + renderTypingNotifications = false, + typingMembers = persistentListOf(), + reserveSpace = false, + ), + predecessorRoom = room.predecessorRoom(), + ) + } } val timelineProtectionState = timelineProtectionPresenter.present() val linkState = linkPresenter.present() - val syncUpdateFlow = room.syncUpdateFlow.collectAsState() - val userEventPermissions by userEventPermissions(syncUpdateFlow.value) + val userEventPermissions by room.permissionsAsState(UserEventPermissions.DEFAULT) { perms -> + perms.userEventPermissions() + } val displayThreadSummaries by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Threads).collectAsState(false) @@ -130,7 +134,7 @@ class PinnedMessagesListPresenter( } ) - fun handleEvents(event: PinnedMessagesListEvents) { + fun handleEvent(event: PinnedMessagesListEvents) { when (event) { is PinnedMessagesListEvents.HandleAction -> sessionCoroutineScope.handleTimelineAction(event.action, event.event) } @@ -143,7 +147,7 @@ class PinnedMessagesListPresenter( displayThreadSummaries = displayThreadSummaries, userEventPermissions = userEventPermissions, timelineItems = pinnedMessageItems, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } @@ -153,18 +157,18 @@ class PinnedMessagesListPresenter( ) = launch { when (action) { TimelineItemAction.ViewSource -> { - navigator.onShowEventDebugInfoClick(targetEvent.eventId, targetEvent.debugInfo) + navigator.navigateToEventDebugInfo(targetEvent.eventId, targetEvent.debugInfo) } TimelineItemAction.Forward -> { targetEvent.eventId?.let { eventId -> - navigator.onForwardEventClick(eventId) + navigator.forwardEvent(eventId) } } TimelineItemAction.Unpin -> handleUnpinAction(targetEvent) TimelineItemAction.ViewInTimeline -> { targetEvent.eventId?.let { eventId -> analyticsService.captureInteraction(Interaction.Name.PinnedMessageListViewTimeline) - navigator.onViewInTimelineClick(eventId) + navigator.viewInTimeline(eventId) } } else -> Unit @@ -188,19 +192,6 @@ class PinnedMessagesListPresenter( } } - @Composable - private fun userEventPermissions(updateKey: Long): State { - return produceState(UserEventPermissions.DEFAULT, key1 = updateKey) { - value = UserEventPermissions( - canSendMessage = false, - canSendReaction = false, - canRedactOwn = room.canRedactOwn().getOrElse { false }, - canRedactOther = room.canRedactOther().getOrElse { false }, - canPinUnpin = room.canPinUnpin().getOrElse { false }, - ) - } - } - @Composable private fun PinnedMessagesListEffect(onItemsChange: (AsyncData>) -> Unit) { val updatedOnItemsChange by rememberUpdatedState(onItemsChange) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListState.kt index 8d71899134..c62d293ce8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListStateProvider.kt index 70b457b90c..a3ed06c53f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListTimelineActionPostProcessor.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListTimelineActionPostProcessor.kt index df73186aa4..a7d03fac8f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListTimelineActionPostProcessor.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListTimelineActionPostProcessor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt index 0abfc4d75a..18175f12c9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageEvents.kt index 7dbbc8fe3b..966b83d61b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageNode.kt index e40de20824..9a19e82137 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenter.kt index 90b6585718..5dee4f5b79 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -53,7 +54,7 @@ class ReportMessagePresenter( var blockUser by rememberSaveable { mutableStateOf(false) } var result: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } - fun handleEvents(event: ReportMessageEvents) { + fun handleEvent(event: ReportMessageEvents) { when (event) { is ReportMessageEvents.UpdateReason -> reason = event.reason ReportMessageEvents.ToggleBlockUser -> blockUser = !blockUser @@ -66,7 +67,7 @@ class ReportMessagePresenter( reason = reason, blockUser = blockUser, result = result.value, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageState.kt index 895d938560..38eee9d746 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageStateProvider.kt index cb3b553497..0e2b4b0c50 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageView.kt index d3d9e1e37a..a2c398776f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/report/ReportMessageView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt index f732def95f..10922ca5e7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/threads/ThreadedMessagesNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -22,7 +23,6 @@ import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode @@ -33,7 +33,7 @@ import io.element.android.features.messages.impl.MessagesView import io.element.android.features.messages.impl.actionlist.ActionListPresenter import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor import io.element.android.features.messages.impl.attachments.Attachment -import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents +import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter import io.element.android.features.messages.impl.timeline.TimelineController import io.element.android.features.messages.impl.timeline.TimelineEvents @@ -44,8 +44,8 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.androidutils.system.openUrlInExternalApp import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs -import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.designsystem.utils.OnLifecycleEvent import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.annotations.SessionCoroutineScope @@ -64,6 +64,7 @@ import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo import io.element.android.libraries.mediaplayer.api.MediaPlayer import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope @@ -85,15 +86,15 @@ class ThreadedMessagesNode( private val timelineItemPresenterFactories: TimelineItemPresenterFactories, private val mediaPlayer: MediaPlayer, private val permalinkParser: PermalinkParser, + private val appNavigationStateService: AppNavigationStateService, ) : Node(buildContext, plugins = plugins), MessagesNavigator { - private val callbacks = plugins() - data class Inputs( val threadRootEventId: ThreadId, val focusedEventId: EventId?, ) : NodeInputs private val inputs = inputs() + private val callback: Callback = callback() // TODO use a loading state node to preload this instead of using `runBlocking` private val threadedTimeline = runBlocking { room.createTimeline(CreateTimelineParams.Threaded(threadRootEventId = inputs.threadRootEventId)).getOrThrow() } @@ -111,18 +112,18 @@ class ThreadedMessagesNode( ) interface Callback : Plugin { - fun onEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event): Boolean - fun onPreviewAttachments(attachments: ImmutableList, inReplyToEventId: EventId?) - fun onUserDataClick(userId: UserId) - fun onPermalinkClick(data: PermalinkData) - fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) - fun onForwardEventClick(eventId: EventId) - fun onReportMessage(eventId: EventId, senderId: UserId) - fun onSendLocationClick() - fun onCreatePollClick() - fun onEditPollClick(eventId: EventId) - fun onJoinCallClick(roomId: RoomId) - fun onOpenThread(threadRootId: ThreadId, focusedEventId: EventId?) + fun handleEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event): Boolean + fun navigateToPreviewAttachments(attachments: ImmutableList, inReplyToEventId: EventId?) + fun navigateToRoomMemberDetails(userId: UserId) + fun handlePermalinkClick(data: PermalinkData) + fun navigateToEventDebugInfo(eventId: EventId?, debugInfo: TimelineItemDebugInfo) + fun handleForwardEventClick(eventId: EventId) + fun navigateToReportMessage(eventId: EventId, senderId: UserId) + fun navigateToSendLocation() + fun navigateToCreatePoll() + fun navigateToEditPoll(eventId: EventId) + fun navigateToRoomCall(roomId: RoomId) + fun navigateToThread(threadRootId: ThreadId, focusedEventId: EventId?) } override fun onBuilt() { @@ -131,26 +132,18 @@ class ThreadedMessagesNode( onCreate = { sessionCoroutineScope.launch { analyticsService.capture(room.toAnalyticsViewRoom()) } }, + onStart = { + appNavigationStateService.onNavigateToThread(id, inputs.threadRootEventId) + }, + onStop = { + appNavigationStateService.onLeavingThread(id) + }, onDestroy = { mediaPlayer.close() } ) } - private fun onEventClick(timelineMode: Timeline.Mode, event: TimelineItem.Event): Boolean { - // Note: cannot use `callbacks.all { it.onEventClick(event) }` because: - // - if callbacks is empty, it will return true and we want to return false. - // - if a callback returns false, the other callback will not be invoked. - return callbacks.takeIf { it.isNotEmpty() } - ?.map { it.onEventClick(timelineMode, event) } - ?.all { it } - .orFalse() - } - - private fun onUserDataClick(userId: UserId) { - callbacks.forEach { it.onUserDataClick(userId) } - } - private fun onLinkClick( activity: Activity, darkTheme: Boolean, @@ -162,7 +155,7 @@ class ThreadedMessagesNode( is PermalinkData.UserLink -> { // Open the room member profile, it will fallback to // the user profile if the user is not in the room - callbacks.forEach { it.onUserDataClick(permalink.userId) } + callback.navigateToRoomMemberDetails(permalink.userId) } is PermalinkData.RoomLink -> { handleRoomLinkClick(permalink, eventSink) @@ -196,50 +189,40 @@ class ThreadedMessagesNode( navigateUp() } } else { - callbacks.forEach { it.onPermalinkClick(roomLink) } + callback.handlePermalinkClick(roomLink) } } - override fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { - callbacks.forEach { it.onShowEventDebugInfoClick(eventId, debugInfo) } + override fun navigateToEventDebugInfo(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { + callback.navigateToEventDebugInfo(eventId, debugInfo) } - override fun onForwardEventClick(eventId: EventId) { - callbacks.forEach { it.onForwardEventClick(eventId) } + override fun forwardEvent(eventId: EventId) { + callback.handleForwardEventClick(eventId) } - override fun onReportContentClick(eventId: EventId, senderId: UserId) { - callbacks.forEach { it.onReportMessage(eventId, senderId) } + override fun navigateToReportMessage(eventId: EventId, senderId: UserId) { + callback.navigateToReportMessage(eventId, senderId) } - override fun onEditPollClick(eventId: EventId) { - callbacks.forEach { it.onEditPollClick(eventId) } + override fun navigateToEditPoll(eventId: EventId) { + callback.navigateToEditPoll(eventId) } - override fun onPreviewAttachment(attachments: ImmutableList, inReplyToEventId: EventId?) { - callbacks.forEach { it.onPreviewAttachments(attachments, inReplyToEventId) } + override fun navigateToPreviewAttachments(attachments: ImmutableList, inReplyToEventId: EventId?) { + callback.navigateToPreviewAttachments(attachments, inReplyToEventId) } - override fun onNavigateToRoom(roomId: RoomId, eventId: EventId?, serverNames: List) { + override fun navigateToRoom(roomId: RoomId, eventId: EventId?, serverNames: List) { val permalinkData = PermalinkData.RoomLink(roomId.toRoomIdOrAlias(), eventId, viaParameters = serverNames.toImmutableList()) - callbacks.forEach { it.onPermalinkClick(permalinkData) } + callback.handlePermalinkClick(permalinkData) } - override fun onOpenThread(threadRootId: ThreadId, focusedEventId: EventId?) { - callbacks.forEach { it.onOpenThread(threadRootId, focusedEventId) } + override fun navigateToThread(threadRootId: ThreadId, focusedEventId: EventId?) { + callback.navigateToThread(threadRootId, focusedEventId) } - private fun onSendLocationClick() { - callbacks.forEach { it.onSendLocationClick() } - } - - private fun onCreatePollClick() { - callbacks.forEach { it.onCreatePollClick() } - } - - private fun onJoinCallClick() { - callbacks.forEach { it.onJoinCallClick(room.roomId) } - } + override fun close() = navigateUp() @Composable override fun View(modifier: Modifier) { @@ -251,7 +234,7 @@ class ThreadedMessagesNode( val state = presenter.present() OnLifecycleEvent { _, event -> when (event) { - Lifecycle.Event.ON_PAUSE -> state.composerState.eventSink(MessageComposerEvents.SaveDraft) + Lifecycle.Event.ON_PAUSE -> state.composerState.eventSink(MessageComposerEvent.SaveDraft) else -> Unit } } @@ -261,17 +244,17 @@ class ThreadedMessagesNode( onRoomDetailsClick = {}, onEventContentClick = { isLive, event -> if (isLive) { - onEventClick(timelineController.mainTimelineMode(), event) + callback.handleEventClick(timelineController.mainTimelineMode(), event) } else { val detachedTimelineMode = timelineController.detachedTimelineMode() if (detachedTimelineMode != null) { - onEventClick(detachedTimelineMode, event) + callback.handleEventClick(detachedTimelineMode, event) } else { false } } }, - onUserDataClick = this::onUserDataClick, + onUserDataClick = callback::navigateToRoomMemberDetails, onLinkClick = { url, customTab -> onLinkClick( activity = activity, @@ -281,9 +264,9 @@ class ThreadedMessagesNode( customTab = customTab, ) }, - onSendLocationClick = this::onSendLocationClick, - onCreatePollClick = this::onCreatePollClick, - onJoinCallClick = this::onJoinCallClick, + onSendLocationClick = callback::navigateToSendLocation, + onCreatePollClick = callback::navigateToCreatePoll, + onJoinCallClick = { callback.navigateToRoomCall(room.roomId) }, onViewAllPinnedMessagesClick = {}, modifier = modifier, knockRequestsBannerView = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt index 54c4e55deb..fbf5b9dea0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,7 +15,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.features.messages.api.timeline.HtmlConverterProvider import io.element.android.libraries.core.bool.orFalse @@ -29,7 +29,6 @@ import uniffi.wysiwyg_composer.newMentionDetector @ContributesBinding(RoomScope::class) @SingleIn(RoomScope::class) -@Inject class DefaultHtmlConverterProvider( private val mentionSpanProvider: MentionSpanProvider, ) : HtmlConverterProvider { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/MarkAsFullyRead.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/MarkAsFullyRead.kt index 6fe1cd687c..7fe17059be 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/MarkAsFullyRead.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/MarkAsFullyRead.kt @@ -1,38 +1,34 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl.timeline import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject +import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.timeline.ReceiptType -import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import timber.log.Timber interface MarkAsFullyRead { - operator fun invoke(roomId: RoomId) + suspend operator fun invoke(roomId: RoomId, eventId: EventId): Result } @ContributesBinding(SessionScope::class) -@Inject class DefaultMarkAsFullyRead( private val matrixClient: MatrixClient, + private val coroutineDispatchers: CoroutineDispatchers, ) : MarkAsFullyRead { - override fun invoke(roomId: RoomId) { - matrixClient.sessionCoroutineScope.launch { - matrixClient.getRoom(roomId)?.use { room -> - room.markAsRead(receiptType = ReceiptType.FULLY_READ) - .onFailure { - Timber.e("Failed to mark room $roomId as fully read", it) - } - } + override suspend fun invoke(roomId: RoomId, eventId: EventId): Result = withContext(coroutineDispatchers.io) { + matrixClient.markRoomAsFullyRead(roomId, eventId).onFailure { + Timber.e(it, "Failed to mark room $roomId as fully read for event $eventId") } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineController.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineController.kt index 779ebe984a..e41ac7bee7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineController.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineController.kt @@ -1,14 +1,14 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl.timeline import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import dev.zacsweers.metro.binding import io.element.android.features.messages.impl.timeline.di.LiveTimeline @@ -44,7 +44,6 @@ import java.util.Optional */ @SingleIn(RoomScope::class) @ContributesBinding(RoomScope::class, binding = binding()) -@Inject class TimelineController( private val room: JoinedRoom, @LiveTimeline private val liveTimeline: Timeline, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt index 0fe4394fa6..262c4f9522 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexer.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexer.kt index 36b64be0ce..b6129085e6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexer.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index f03a1e8903..5bafaf3e88 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -1,14 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl.timeline import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState @@ -24,6 +24,7 @@ import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedInject import io.element.android.features.messages.impl.MessagesNavigator +import io.element.android.features.messages.impl.UserEventPermissions import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureEvents import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory @@ -32,12 +33,12 @@ import io.element.android.features.messages.impl.timeline.model.NewEventState import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemTypingNotificationModel import io.element.android.features.messages.impl.typing.TypingNotificationState +import io.element.android.features.messages.impl.userEventPermissions import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager import io.element.android.features.poll.api.actions.EndPollAction import io.element.android.features.poll.api.actions.SendPollResponseAction import io.element.android.features.roomcall.api.RoomCallState import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.featureflag.api.FeatureFlagService @@ -46,20 +47,26 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.api.core.asEventId import io.element.android.libraries.matrix.api.room.JoinedRoom -import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.isDm +import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState import io.element.android.libraries.matrix.api.room.roomMembers import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin -import io.element.android.libraries.matrix.ui.room.canSendMessageAsState import io.element.android.libraries.preferences.api.store.SessionPreferencesStore +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.DisplayFirstTimelineItems +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.NotificationTapOpensTimeline +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.OpenRoom +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.api.finishLongRunningTransaction +import io.element.android.services.analyticsproviders.api.AnalyticsUserData import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -85,9 +92,11 @@ class TimelinePresenter( private val resolveVerifiedUserSendFailurePresenter: Presenter, private val typingNotificationPresenter: Presenter, private val roomCallStatePresenter: Presenter, - private val markAsFullyRead: MarkAsFullyRead, private val featureFlagService: FeatureFlagService, + private val analyticsService: AnalyticsService, ) : Presenter { + private val tag = "TimelinePresenter" + @AssistedFactory interface Factory { fun create( @@ -104,23 +113,23 @@ class TimelinePresenter( ) private var timelineItems by mutableStateOf>(persistentListOf()) + private val focusRequestState: MutableState = mutableStateOf(FocusRequestState.None) + @Composable override fun present(): TimelineState { + LaunchedEffect(Unit) { + val parent = analyticsService.getLongRunningTransaction(OpenRoom) + analyticsService.startLongRunningTransaction(DisplayFirstTimelineItems, parent) + } + val localScope = rememberCoroutineScope() val timelineMode = remember { timelineController.mainTimelineMode() } - var focusRequestState: FocusRequestState by remember { mutableStateOf(FocusRequestState.None) } - val lastReadReceiptId = rememberSaveable { mutableStateOf(null) } val roomInfo by room.roomInfoFlow.collectAsState() - val syncUpdateFlow = room.syncUpdateFlow.collectAsState() - - val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.RoomMessage, updateKey = syncUpdateFlow.value) - val userHasPermissionToSendReaction by room.canSendMessageAsState(type = MessageEventType.Reaction, updateKey = syncUpdateFlow.value) - val prevMostRecentItemId = rememberSaveable { mutableStateOf(null) } val newEventState = remember { mutableStateOf(NewEventState.None) } @@ -141,7 +150,7 @@ class TimelinePresenter( value = featureFlagService.isFeatureEnabled(FeatureFlags.Threads) } - fun handleEvents(event: TimelineEvents) { + fun handleEvent(event: TimelineEvents) { when (event) { is TimelineEvents.LoadMore -> { if (event.direction == Timeline.PaginationDirection.FORWARDS && timelineMode is Timeline.Mode.Thread) { @@ -157,7 +166,7 @@ class TimelinePresenter( if (event.firstIndex == 0) { newEventState.value = NewEventState.None } - Timber.d("## sendReadReceiptIfNeeded firstVisibleIndex: ${event.firstIndex}") + Timber.tag(tag).d("## sendReadReceiptIfNeeded firstVisibleIndex: ${event.firstIndex}") sessionCoroutineScope.sendReadReceiptIfNeeded( firstVisibleIndex = event.firstIndex, timelineItems = timelineItems, @@ -186,16 +195,22 @@ class TimelinePresenter( } } is TimelineEvents.EditPoll -> { - navigator.onEditPollClick(event.pollStartId) - } - is TimelineEvents.FocusOnEvent -> { - focusRequestState = FocusRequestState.Requested(event.eventId, event.debounce) + navigator.navigateToEditPoll(event.pollStartId) } + is TimelineEvents.FocusOnEvent -> sessionCoroutineScope.launch { + focusRequestState.value = FocusRequestState.Requested(event.eventId, event.debounce) + delay(event.debounce) + Timber.tag(tag).d("Started focus on ${event.eventId}") + focusOnEvent(event.eventId, focusRequestState) + }.start() is TimelineEvents.OnFocusEventRender -> { - focusRequestState = focusRequestState.onFocusEventRender() + // If there was a pending 'notification tap opens timeline' transaction, finish it now we're focused in the required event + analyticsService.finishLongRunningTransaction(NotificationTapOpensTimeline) + + focusRequestState.value = focusRequestState.value.onFocusEventRender() } is TimelineEvents.ClearFocusRequestState -> { - focusRequestState = FocusRequestState.None + focusRequestState.value = FocusRequestState.None } is TimelineEvents.JumpToLive -> { timelineController.focusOnLive() @@ -208,10 +223,10 @@ class TimelinePresenter( is TimelineEvents.NavigateToPredecessorOrSuccessorRoom -> { // Navigate to the predecessor or successor room val serverNames = calculateServerNamesForRoom(room) - navigator.onNavigateToRoom(event.roomId, null, serverNames) + navigator.navigateToRoom(event.roomId, null, serverNames) } is TimelineEvents.OpenThread -> { - navigator.onOpenThread( + navigator.navigateToThread( threadRootId = event.threadRootEventId, focusedEventId = event.focusedEvent, ) @@ -219,107 +234,64 @@ class TimelinePresenter( } } - DisposableEffect(Unit) { - onDispose { - markAsFullyRead(room.roomId) - } - } - LaunchedEffect(Unit) { timelineItemsFactory.timelineItems .onEach { newTimelineItems -> timelineItemIndexer.process(newTimelineItems) timelineItems = newTimelineItems + + analyticsService.run { + finishLongRunningTransaction(DisplayFirstTimelineItems) + finishLongRunningTransaction(OpenRoom) + } } .launchIn(this) combine(timelineController.timelineItems(), room.membersStateFlow) { items, membersState -> + val parent = analyticsService.getLongRunningTransaction(DisplayFirstTimelineItems) + val transaction = parent?.startChild("timelineItemsFactory.replaceWith", "Processing timeline items") + transaction?.putExtraData(AnalyticsUserData.TIMELINE_ITEM_COUNT, items.count().toString()) timelineItemsFactory.replaceWith( timelineItems = items, roomMembers = membersState.roomMembers().orEmpty() ) + transaction?.finish() items } .onEach(redactedVoiceMessageManager::onEachMatrixTimelineItem) + .flowOn(dispatchers.computation) .launchIn(this) } - LaunchedEffect(focusRequestState) { - Timber.d("## focusRequestState: $focusRequestState") - when (val currentFocusRequestState = focusRequestState) { - is FocusRequestState.Requested -> { - delay(currentFocusRequestState.debounce) - if (timelineItemIndexer.isKnown(currentFocusRequestState.eventId)) { - val index = timelineItemIndexer.indexOf(currentFocusRequestState.eventId) - focusRequestState = FocusRequestState.Success(eventId = currentFocusRequestState.eventId, index = index) - } else { - focusRequestState = FocusRequestState.Loading(eventId = currentFocusRequestState.eventId) - } - } - is FocusRequestState.Loading -> { - val eventId = currentFocusRequestState.eventId - val threadId = room.threadRootIdForEvent(eventId).getOrElse { - focusRequestState = FocusRequestState.Failure(it) - return@LaunchedEffect - } - - if (timelineController.mainTimelineMode() is Timeline.Mode.Thread && threadId == null) { - // We are in a thread timeline, and the event isn't part of a thread, we need to navigate back to the room - focusRequestState = FocusRequestState.None - navigator.onNavigateToRoom(room.roomId, eventId, calculateServerNamesForRoom(room)) - } else { - timelineController.focusOnEvent(eventId, threadId) - .onSuccess { result -> - when (result) { - is EventFocusResult.FocusedOnLive -> { - focusRequestState = FocusRequestState.Success(eventId = eventId) - } - is EventFocusResult.IsInThread -> { - val currentThreadId = (timelineController.mainTimelineMode() as? Timeline.Mode.Thread)?.threadRootId - if (currentThreadId == result.threadId) { - // It's the same thread, we just focus on the event - focusRequestState = FocusRequestState.Success(eventId = eventId) - } else { - focusRequestState = FocusRequestState.Success(eventId = result.threadId.asEventId()) - // It's part of a thread we're not in, let's open it in another timeline - navigator.onOpenThread(result.threadId, eventId) - } - } - } - } - .onFailure { - focusRequestState = FocusRequestState.Failure(it) - } - } - } - else -> Unit - } - } - LaunchedEffect(timelineItems.size) { computeNewItemState(timelineItems, prevMostRecentItemId, newEventState) } - LaunchedEffect(timelineItems.size, focusRequestState) { - val currentFocusRequestState = focusRequestState + LaunchedEffect(timelineItems.size, focusRequestState.value) { + val currentFocusRequestState = focusRequestState.value if (currentFocusRequestState is FocusRequestState.Success && !currentFocusRequestState.rendered) { val eventId = currentFocusRequestState.eventId if (timelineItemIndexer.isKnown(eventId)) { val index = timelineItemIndexer.indexOf(eventId) - focusRequestState = FocusRequestState.Success(eventId = eventId, index = index) + focusRequestState.value = FocusRequestState.Success(eventId = eventId, index = index) + } else { + Timber.w("Unknown timeline item for focused item, can't render focus") } } } val typingNotificationState = typingNotificationPresenter.present() val roomCallState = roomCallStatePresenter.present() + val userEventPermissions by room.permissionsAsState(UserEventPermissions.DEFAULT) { perms -> + perms.userEventPermissions() + } val timelineRoomInfo by remember(typingNotificationState, roomCallState, roomInfo) { derivedStateOf { TimelineRoomInfo( name = roomInfo.name, - isDm = roomInfo.isDm.orFalse(), - userHasPermissionToSendMessage = userHasPermissionToSendMessage, - userHasPermissionToSendReaction = userHasPermissionToSendReaction, + isDm = roomInfo.isDm, + userHasPermissionToSendMessage = userEventPermissions.canSendMessage, + userHasPermissionToSendReaction = userEventPermissions.canSendReaction, roomCallState = roomCallState, pinnedEventIds = roomInfo.pinnedEventIds, typingNotificationState = typingNotificationState, @@ -327,6 +299,11 @@ class TimelinePresenter( ) } } + + LaunchedEffect(focusRequestState.value) { + Timber.tag(tag).d("Timeline: $timelineMode | focus state: ${focusRequestState.value}") + } + return TimelineState( timelineItems = timelineItems, timelineMode = timelineMode, @@ -334,14 +311,63 @@ class TimelinePresenter( renderReadReceipts = renderReadReceipts, newEventState = newEventState.value, isLive = isLive, - focusRequestState = focusRequestState, + focusRequestState = focusRequestState.value, messageShield = messageShield.value, resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailureState, displayThreadSummaries = displayThreadSummaries, - eventSink = { handleEvents(it) } + eventSink = ::handleEvent, ) } + private suspend fun focusOnEvent( + eventId: EventId, + focusRequestState: MutableState, + ) { + if (timelineItemIndexer.isKnown(eventId)) { + val index = timelineItemIndexer.indexOf(eventId) + focusRequestState.value = FocusRequestState.Success(eventId = eventId, index = index) + return + } + + Timber.tag(tag).d("Event $eventId not found in the loaded timeline, loading a focused timeline") + focusRequestState.value = FocusRequestState.Loading(eventId = eventId) + + val threadId = room.threadRootIdForEvent(eventId).getOrElse { + focusRequestState.value = FocusRequestState.Failure(it) + return + } + + if (timelineController.mainTimelineMode() is Timeline.Mode.Thread && threadId == null) { + // We are in a thread timeline, and the event isn't part of a thread, we need to navigate back to the room + focusRequestState.value = FocusRequestState.None + navigator.navigateToRoom(room.roomId, eventId, calculateServerNamesForRoom(room)) + } else { + Timber.tag(tag).d("Focusing on event $eventId - thread $threadId") + timelineController.focusOnEvent(eventId, threadId) + .onSuccess { result -> + when (result) { + is EventFocusResult.FocusedOnLive -> { + focusRequestState.value = FocusRequestState.Success(eventId = eventId) + } + is EventFocusResult.IsInThread -> { + val currentThreadId = (timelineController.mainTimelineMode() as? Timeline.Mode.Thread)?.threadRootId + if (currentThreadId == result.threadId) { + // It's the same thread, we just focus on the event + focusRequestState.value = FocusRequestState.Success(eventId = eventId) + } else { + focusRequestState.value = FocusRequestState.Success(eventId = result.threadId.asEventId()) + // It's part of a thread we're not in, let's open it in another timeline + navigator.navigateToThread(result.threadId, eventId) + } + } + } + } + .onFailure { + focusRequestState.value = FocusRequestState.Failure(it) + } + } + } + /** * This method compute the hasNewItem state passed as a [MutableState] each time the timeline items size changes. * Basically, if we got new timeline event from sync or local, either from us or another user, we update the state so we tell we have new items. @@ -368,9 +394,8 @@ class TimelinePresenter( newMostRecentItemId != prevMostRecentItemIdValue if (hasNewEvent) { - val newMostRecentEvent = newMostRecentItem // Scroll to bottom if the new event is from me, even if sent from another device - val fromMe = newMostRecentEvent?.isMine == true + val fromMe = newMostRecentItem.isMine newEventState.value = if (fromMe) { NewEventState.FromMe } else { @@ -388,13 +413,17 @@ class TimelinePresenter( ) = launch(dispatchers.computation) { // If we are at the bottom of timeline, we mark the room as read. if (firstVisibleIndex == 0) { - room.markAsRead(receiptType = readReceiptType) + timelineController.invokeOnCurrentTimeline { + markAsRead(receiptType = readReceiptType) + } } else { // Get last valid EventId seen by the user, as the first index might refer to a Virtual item val eventId = getLastEventIdBeforeOrAt(firstVisibleIndex, timelineItems) if (eventId != null && eventId != lastReadReceiptId.value) { lastReadReceiptId.value = eventId - room.liveTimeline.sendReadReceipt(eventId = eventId, receiptType = readReceiptType) + timelineController.invokeOnCurrentTimeline { + sendReadReceipt(eventId = eventId, receiptType = readReceiptType) + } } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt index bd1c58c9d7..3ec168fd47 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -21,7 +22,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield import kotlinx.collections.immutable.ImmutableList import kotlin.time.Duration -@Immutable data class TimelineState( val timelineItems: ImmutableList, val timelineRoomInfo: TimelineRoomInfo, @@ -72,14 +72,13 @@ sealed interface FocusRequestState { } } -@Immutable data class TimelineRoomInfo( val isDm: Boolean, val name: String?, val userHasPermissionToSendMessage: Boolean, val userHasPermissionToSendReaction: Boolean, val roomCallState: RoomCallState, - val pinnedEventIds: List, + val pinnedEventIds: ImmutableList, val typingNotificationState: TypingNotificationState, val predecessorRoom: PredecessorRoom?, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt index 0cc61b4e4c..b5f9e328dc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -259,7 +260,7 @@ internal fun aTimelineRoomInfo( userHasPermissionToSendMessage = userHasPermissionToSendMessage, userHasPermissionToSendReaction = true, roomCallState = aStandByCallState(), - pinnedEventIds = pinnedEventIds, + pinnedEventIds = pinnedEventIds.toImmutableList(), typingNotificationState = typingNotificationState, predecessorRoom = predecessorRoom, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index 0d729020dd..73a15b797f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -122,6 +123,7 @@ fun TimelineView( } val context = LocalContext.current + val toastMessage = stringResource(CommonStrings.common_copied_to_clipboard) val view = LocalView.current // Disable reverse layout when TalkBack is enabled to avoid incorrect ordering issues seen in the current Compose UI version val useReverseLayout = !isTalkbackActive() @@ -135,8 +137,8 @@ fun TimelineView( HapticFeedbackConstants.LONG_PRESS ) context.copyToClipboard( - link.url, - context.getString(CommonStrings.common_copied_to_clipboard) + text = link.url, + toastMessage = toastMessage, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt index 37ca90fd90..5f9c3d0364 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/a11y/Reactions.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/a11y/Reactions.kt index 9da0168b38..cd71250153 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/a11y/Reactions.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/a11y/Reactions.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt index 0ac882a95f..6be00fc14a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/CallMenuItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/CallMenuItem.kt index 4bbccabc53..73e6c1873a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/CallMenuItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/CallMenuItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ContentPadding.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ContentPadding.kt index cf132a6c1a..f7c3627b2a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ContentPadding.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ContentPadding.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageEventBubble.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageEventBubble.kt index 1dbe5d3a24..8923b0c963 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageEventBubble.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageEventBubble.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt index 9d7d4caac5..401fd7eec8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageStateEventContainer.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageStateEventContainer.kt index 82eeb77177..3c4643793b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageStateEventContainer.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageStateEventContainer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesReactionButton.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesReactionButton.kt index c2673c0d84..dc8e171298 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesReactionButton.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesReactionButton.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ReplySwipeIndicator.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ReplySwipeIndicator.kt index 1eb09ca93f..56fa2d9f39 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ReplySwipeIndicator.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ReplySwipeIndicator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt index d8af3c1736..40369595ea 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineEventTimestampView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemCallNotifyView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemCallNotifyView.kt index aaebfb4957..c921135b96 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemCallNotifyView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemCallNotifyView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt index 96d139bfba..3689c84875 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventForTimestampViewProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index 1503bf236b..1f40e94a9b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -102,7 +103,7 @@ import io.element.android.libraries.matrix.api.timeline.item.EmbeddedEventInfo import io.element.android.libraries.matrix.api.timeline.item.ThreadSummary import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent -import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName @@ -541,7 +542,7 @@ private fun TimelineItemEventRowContent( @Composable private fun MessageSenderInformation( senderId: UserId, - senderProfile: ProfileTimelineDetails, + senderProfile: ProfileDetails, senderAvatar: AvatarData, onClick: () -> Unit, modifier: Modifier = Modifier @@ -843,7 +844,7 @@ internal fun TimelineItemEventRowWithThreadSummaryPreview() = ElementPreview { type = TextMessageType("This is the latest message in the thread", null) ), senderId = UserId("@user:id"), - senderProfile = ProfileTimelineDetails.Ready( + senderProfile = ProfileDetails.Ready( displayName = "Alice", avatarUrl = null, displayNameAmbiguous = false, @@ -876,7 +877,7 @@ internal fun ThreadSummaryViewPreview() { type = TextMessageType(body, null) ), senderId = UserId("@user:id"), - senderProfile = ProfileTimelineDetails.Ready( + senderProfile = ProfileDetails.Ready( displayName = "Alice", avatarUrl = null, displayNameAmbiguous = true, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowDisambiguatedPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowDisambiguatedPreview.kt index fc56d7c453..4b6223fd97 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowDisambiguatedPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowDisambiguatedPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowForDirectRoomPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowForDirectRoomPreview.kt index d5af740baa..86afd9d4b7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowForDirectRoomPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowForDirectRoomPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowLongSenderNamePreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowLongSenderNamePreview.kt index a444b6f019..e40ab16aa5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowLongSenderNamePreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowLongSenderNamePreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowShieldPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowShieldPreview.kt index b7f1564d8c..4478854788 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowShieldPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowShieldPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt index 0555670e46..c588b1d071 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowTimestampPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowUtdPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowUtdPreview.kt index baa74890b3..49328ac025 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowUtdPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowUtdPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithManyReactionsPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithManyReactionsPreview.kt index 9dd3bfb713..9b9080e130 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithManyReactionsPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithManyReactionsPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithRRPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithRRPreview.kt index d97e5d8952..f46ba780f3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithRRPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithRRPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyInformativePreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyInformativePreview.kt index 8315d54f30..cdc60a9e7e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyInformativePreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyInformativePreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyOtherPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyOtherPreview.kt index 07f9f1073a..3071fe114a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyOtherPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyOtherPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt index 7cc1843466..7ddcadfe3c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventTimestampBelowPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventTimestampBelowPreview.kt index 1f78ce4a5c..975073cc46 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventTimestampBelowPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventTimestampBelowPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt index 1a597fbda6..66f2459491 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsLayout.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsLayout.kt index 390d264353..b7cefb8862 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsLayout.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemReactionsLayout.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 07811b532d..9698227891 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 @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt index 119a235cf8..4a0a6f70b8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -37,12 +38,11 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionEvent import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState +import io.element.android.libraries.designsystem.colors.gradientSubtleColors import io.element.android.libraries.designsystem.modifiers.onKeyboardContextMenuAction -import io.element.android.libraries.designsystem.modifiers.subtleColorStops import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.text.toPx -import io.element.android.libraries.designsystem.theme.LocalBuildMeta import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.user.MatrixUser @@ -220,19 +220,14 @@ internal fun TimelineItemRow( @Composable private fun Modifier.focusedEvent( focusedEventOffset: Dp, - isEnterpriseBuild: Boolean = LocalBuildMeta.current.isEnterpriseBuild, ): Modifier { - val highlightedLineColor = if (isEnterpriseBuild) { - ElementTheme.colors.textActionAccent - } else { - ElementTheme.colors.borderAccentSubtle - } - val gradientColors = subtleColorStops(isEnterpriseBuild) + val highlightedLineColor = ElementTheme.colors.borderAccentSubtle + val gradientColors = gradientSubtleColors() val verticalOffset = focusedEventOffset.toPx() val verticalRatio = 0.7f return drawWithCache { val brush = Brush.verticalGradient( - colorStops = gradientColors, + colors = gradientColors, endY = size.height * verticalRatio, ) onDrawBehind { @@ -261,18 +256,3 @@ internal fun FocusedEventPreview() = ElementPreview { .focusedEvent(0.dp), ) } - -@PreviewsDayNight -@Composable -internal fun FocusedEventEnterprisePreview() = ElementPreview { - Box( - modifier = Modifier - .padding(16.dp) - .fillMaxWidth() - .height(160.dp) - .focusedEvent( - focusedEventOffset = 0.dp, - isEnterpriseBuild = true, - ), - ) -} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt index 84fbd2920a..e796e93311 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt index 7c0309aac4..be94512c2c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimestampPosition.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimestampPosition.kt index 7cdaddbbf8..605db65da3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimestampPosition.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimestampPosition.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt index d0c848e393..0226eea7b9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvents.kt index 0abe892921..b73d1295c4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt index ba13c461e4..4ccfc3f883 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,10 +18,10 @@ import androidx.compose.runtime.setValue import dev.zacsweers.metro.Inject import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.matrix.api.recentemojis.GetRecentEmojis +import io.element.android.libraries.recentemojis.api.EmojibaseProvider +import io.element.android.libraries.recentemojis.api.GetRecentEmojis import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableSet import kotlinx.coroutines.launch @@ -41,7 +42,7 @@ class CustomReactionPresenter( fun handleShowCustomReactionSheet(event: TimelineItem.Event) { target.value = CustomReactionState.Target.Loading(event) localCoroutineScope.launch { - recentEmojis = getRecentEmojis().getOrNull().orEmpty().toImmutableList() + recentEmojis = getRecentEmojis().getOrNull() ?: persistentListOf() target.value = CustomReactionState.Target.Success( event = event, emojibaseStore = emojibaseProvider.emojibaseStore @@ -53,7 +54,7 @@ class CustomReactionPresenter( target.value = CustomReactionState.Target.None } - fun handleEvents(event: CustomReactionEvents) { + fun handleEvent(event: CustomReactionEvents) { when (event) { is CustomReactionEvents.ShowCustomReactionSheet -> handleShowCustomReactionSheet(event.event) is CustomReactionEvents.DismissCustomReactionSheet -> handleDismissCustomReactionSheet() @@ -71,7 +72,7 @@ class CustomReactionPresenter( target = target.value, selectedEmoji = selectedEmoji, recentEmojis = recentEmojis, - eventSink = { handleEvents(it) } + eventSink = ::handleEvent, ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt index 9a9a985e62..1d3e3e820e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojiItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojiItem.kt index 360cb9756e..629eaa72ab 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojiItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojiItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseExtensions.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseExtensions.kt index b2e176c7a1..0f967cc41d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseExtensions.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseExtensions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPicker.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPicker.kt index 83b21df092..70d962ff49 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPicker.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPicker.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerEvents.kt index 53fa6b7b7a..d0c4907b82 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerPresenter.kt index ce9600b1f7..aed568420a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerPresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -90,7 +91,7 @@ class EmojiPickerPresenter( } val isInPreview = LocalInspectionMode.current - fun handleEvents(event: EmojiPickerEvents) { + fun handleEvent(event: EmojiPickerEvents) { when (event) { // For some reason, in preview mode the SearchBar emits this event with an `isActive = true` value automatically is EmojiPickerEvents.ToggleSearchActive -> if (!isInPreview) { @@ -106,7 +107,7 @@ class EmojiPickerPresenter( searchQuery = searchQuery, isSearchActive = isSearchActive, searchResults = emojiResults, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerState.kt index 595349a503..b050c346a5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerState.kt @@ -1,18 +1,22 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl.timeline.components.customreaction.picker import androidx.annotation.StringRes +import androidx.compose.runtime.Immutable import io.element.android.emojibasebindings.Emoji import io.element.android.libraries.designsystem.theme.components.IconSource import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import kotlinx.collections.immutable.ImmutableList +// Emoji is unstable (because from an external library?), so we annotate with @Immutable +@Immutable data class EmojiPickerState( val categories: ImmutableList, val allEmojis: ImmutableList, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerStateProvider.kt index f248efe893..e26941e172 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAspectRatioBox.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAspectRatioBox.kt index c9ea1b43ff..f25957e07f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAspectRatioBox.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAspectRatioBox.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAttachmentView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAttachmentView.kt index dc40fd77fa..061886cef7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAttachmentView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAttachmentView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAudioView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAudioView.kt index d25d6aa457..d691575993 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAudioView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemAudioView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 97eefd44ee..48b3acfbb8 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt index 8660d82e7c..73cd1ad8b8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt index 86218b8e25..293e1c8f45 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemFileView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt index df5b3e0cbb..a8cbb89e96 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemInformativeView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemInformativeView.kt index 74594210b0..41a950ee5e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemInformativeView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemInformativeView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLegacyCallInviteView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLegacyCallInviteView.kt index c2fa1edfa0..d20892a47c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLegacyCallInviteView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLegacyCallInviteView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt index d792b51984..9ebe35a51b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt index b7aba57b77..ba72c067f0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 62ecef2447..e32de7f4ae 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStateView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStateView.kt index 334a7463f7..4eaf932396 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStateView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStateView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt index 1ad75fb933..104e420d51 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt index d496b94f3b..0449ef26f0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemTextView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 08358d5a14..fbbefca207 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 87e895f76c..f5e760736e 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt index f231988f90..1010075803 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVoiceView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 22825b532c..0c13214a7d 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/layout/ContentAvoidingLayout.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/layout/ContentAvoidingLayout.kt index 36f3204f89..04974dfbc5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/layout/ContentAvoidingLayout.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/layout/ContentAvoidingLayout.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryEvents.kt index a57aa2ac17..987b61e505 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt index 4354ef5b25..a95fe57bc7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -36,7 +37,7 @@ class ReactionSummaryPresenter( } val targetWithAvatars = populateSenderAvatars(members = membersState.roomMembers().orEmpty().toImmutableList(), summary = target.value) - fun handleEvents(event: ReactionSummaryEvents) { + fun handleEvent(event: ReactionSummaryEvents) { when (event) { is ReactionSummaryEvents.ShowReactionSummary -> target.value = ReactionSummaryState.Summary( reactions = event.reactions.toImmutableList(), @@ -48,7 +49,7 @@ class ReactionSummaryPresenter( } return ReactionSummaryState( target = targetWithAvatars.value, - eventSink = { handleEvents(it) } + eventSink = ::handleEvent, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryState.kt index ab2c3d1359..cf5342f853 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryStateProvider.kt index be22f6867a..82974c8908 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt index d3dc63c031..944fd9a195 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewState.kt index ea0ab96225..2d3f2307b9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewStateForTimelineItemEventRowProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewStateForTimelineItemEventRowProvider.kt index d32de08178..c5140c4c03 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewStateForTimelineItemEventRowProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewStateForTimelineItemEventRowProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewStateProvider.kt index 1295abdacc..64a7cf34ff 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/ReadReceiptViewStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt index 2d783c394c..b03aaf01c1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/TimelineItemReadReceiptView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt index 7894fe7e02..57c46a6326 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheet.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetEvents.kt index f76a18b1a1..04723d0214 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenter.kt index 33316a134a..e1264b77e7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -36,7 +37,7 @@ class ReadReceiptBottomSheetPresenter : Presenter { return ReadReceiptBottomSheetState( selectedEvent = selectedEvent, - eventSink = { handleEvent(it) }, + eventSink = ::handleEvent, ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetState.kt index c143887584..7ec4107f8f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetState.kt @@ -1,16 +1,15 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet -import androidx.compose.runtime.Immutable import io.element.android.features.messages.impl.timeline.model.TimelineItem -@Immutable data class ReadReceiptBottomSheetState( val selectedEvent: TimelineItem.Event?, val eventSink: (ReadReceiptBottomSheetEvents) -> Unit, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetStateProvider.kt index 18532f903c..ff297a4f8d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 08fe34b724..30befea07d 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemReadMarkerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemReadMarkerView.kt index b666f6ad56..bb9ab2a414 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemReadMarkerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemReadMarkerView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemRoomBeginningView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemRoomBeginningView.kt index a3cab8610f..f812b40e61 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemRoomBeginningView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineItemRoomBeginningView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -45,7 +46,6 @@ fun TimelineItemRoomBeginningView( avatar = null, content = stringResource(R.string.screen_room_timeline_upgraded_room_message).toAnnotatedString(), onSubmitClick = { onPredecessorRoomClick(predecessorRoom.roomId) }, - isCritical = false, submitText = stringResource(R.string.screen_room_timeline_upgraded_room_action) ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineLoadingMoreIndicator.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineLoadingMoreIndicator.kt index 2454b807ef..571c884b1f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineLoadingMoreIndicator.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/virtual/TimelineLoadingMoreIndicator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoNode.kt index d9e85c2eb2..6288f3f159 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoView.kt index cd22006804..3b0448ddb3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/debug/EventDebugInfoView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -111,7 +112,7 @@ fun EventDebugInfoView( private fun prettyJSON(maybeJSON: String): String { return try { JSONObject(maybeJSON).toString(2) - } catch (e: JSONException) { + } catch (_: JSONException) { // Prefer not pretty-printing over crashing if the data is not actually JSON maybeJSON } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/FakeTimelineItemPresenterFactories.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/FakeTimelineItemPresenterFactories.kt index 85812b64a3..7c36521fc0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/FakeTimelineItemPresenterFactories.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/FakeTimelineItemPresenterFactories.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/LiveTimeline.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/LiveTimeline.kt index 6fe88272af..ff7a1703f1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/LiveTimeline.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/LiveTimeline.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/LocalTimelineItemPresenterFactories.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/LocalTimelineItemPresenterFactories.kt index 66588e8446..beef7a893c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/LocalTimelineItemPresenterFactories.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/LocalTimelineItemPresenterFactories.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/TimelineItemEventContentKey.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/TimelineItemEventContentKey.kt index e315da06f2..49b10a4a88 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/TimelineItemEventContentKey.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/TimelineItemEventContentKey.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/TimelineItemPresenterFactories.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/TimelineItemPresenterFactories.kt index 7df549223e..1063157bef 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/TimelineItemPresenterFactories.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/TimelineItemPresenterFactories.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/TimelineItemPresenterFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/TimelineItemPresenterFactory.kt index ccb5bec542..7d8cd20f04 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/TimelineItemPresenterFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/di/TimelineItemPresenterFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/diff/TimelineItemsCacheInvalidator.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/diff/TimelineItemsCacheInvalidator.kt index 7321c7eba2..7cc6d2376c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/diff/TimelineItemsCacheInvalidator.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/diff/TimelineItemsCacheInvalidator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt index 029373d42b..7b369fe6b7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactoryConfig.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactoryConfig.kt index d23c421a56..50124eb50f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactoryConfig.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactoryConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt index 4f1c9ba90a..31cf689ef8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -24,7 +25,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.LegacyCallInv import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent import io.element.android.libraries.matrix.api.timeline.item.event.PollContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent -import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent import io.element.android.libraries.matrix.api.timeline.item.event.StateContent @@ -62,7 +63,7 @@ class TimelineItemContentFactory( eventId: EventId?, isEditable: Boolean, sender: UserId, - senderProfile: ProfileTimelineDetails, + senderProfile: ProfileDetails, ): TimelineItemEventContent { val isOutgoing = sessionId == sender return when (itemContent) { 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 ae7e49c80d..c0c3310eac 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 38edb21b55..a7075d127a 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt index 53dae43dfd..3d8467729a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt @@ -1,14 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl.timeline.factories.event import android.text.style.URLSpan -import androidx.core.text.buildSpannedString import androidx.core.text.getSpans import androidx.core.text.toSpannable import dev.zacsweers.metro.Inject @@ -34,11 +34,9 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType -import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessageType import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent -import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType import io.element.android.libraries.matrix.api.timeline.item.event.OtherMessageType import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType @@ -49,6 +47,7 @@ import io.element.android.libraries.matrix.ui.messages.toHtmlDocument import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractor import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList +import org.jsoup.nodes.Document import kotlin.time.Duration @Inject @@ -59,7 +58,7 @@ class TimelineItemContentMessageFactory( private val permalinkParser: PermalinkParser, private val textPillificationHelper: TextPillificationHelper, ) { - suspend fun create( + fun create( content: MessageContent, senderDisambiguatedDisplayName: String, eventId: EventId?, @@ -67,26 +66,29 @@ class TimelineItemContentMessageFactory( return when (val messageType = content.type) { is EmoteMessageType -> { val emoteBody = "* $senderDisambiguatedDisplayName ${messageType.body.trimEnd()}" - val formattedBody = parseHtml(messageType.formatted, prefix = "* $senderDisambiguatedDisplayName") ?: textPillificationHelper.pillify( - emoteBody - ).safeLinkify() + val dom = messageType.formatted?.toHtmlDocument( + permalinkParser = permalinkParser, + prefix = "* $senderDisambiguatedDisplayName", + ) + val formattedBody = dom?.let(::parseHtml) + ?: textPillificationHelper.pillify(emoteBody).safeLinkify() TimelineItemEmoteContent( body = emoteBody, - htmlDocument = messageType.formatted?.toHtmlDocument( - permalinkParser = permalinkParser, - prefix = "* $senderDisambiguatedDisplayName", - ), + htmlDocument = dom, formattedBody = formattedBody, isEdited = content.isEdited, ) } is ImageMessageType -> { + val dom = messageType.formattedCaption?.toHtmlDocument(permalinkParser = permalinkParser) + val formattedCaption = dom?.let(::parseHtml) + ?: messageType.caption?.withLinks() val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height) TimelineItemImageContent( filename = messageType.filename, fileSize = messageType.info?.size ?: 0, caption = messageType.caption?.trimEnd(), - formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(), + formattedCaption = formattedCaption, isEdited = content.isEdited, mediaSource = messageType.source, thumbnailSource = messageType.info?.thumbnailSource, @@ -102,12 +104,15 @@ class TimelineItemContentMessageFactory( ) } is StickerMessageType -> { + val dom = messageType.formattedCaption?.toHtmlDocument(permalinkParser = permalinkParser) + val formattedCaption = dom?.let(::parseHtml) + ?: messageType.caption?.withLinks() val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height) TimelineItemStickerContent( filename = messageType.filename, fileSize = messageType.info?.size ?: 0, caption = messageType.caption?.trimEnd(), - formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(), + formattedCaption = formattedCaption, isEdited = content.isEdited, mediaSource = messageType.source, thumbnailSource = messageType.info?.thumbnailSource, @@ -139,12 +144,15 @@ class TimelineItemContentMessageFactory( } } is VideoMessageType -> { + val dom = messageType.formattedCaption?.toHtmlDocument(permalinkParser = permalinkParser) + val formattedCaption = dom?.let(::parseHtml) + ?: messageType.caption?.withLinks() val aspectRatio = aspectRatioOf(messageType.info?.width, messageType.info?.height) TimelineItemVideoContent( filename = messageType.filename, fileSize = messageType.info?.size ?: 0, caption = messageType.caption?.trimEnd(), - formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(), + formattedCaption = formattedCaption, isEdited = content.isEdited, thumbnailSource = messageType.info?.thumbnailSource, mediaSource = messageType.source, @@ -161,11 +169,14 @@ class TimelineItemContentMessageFactory( ) } is AudioMessageType -> { + val dom = messageType.formattedCaption?.toHtmlDocument(permalinkParser = permalinkParser) + val formattedCaption = dom?.let(::parseHtml) + ?: messageType.caption?.withLinks() TimelineItemAudioContent( filename = messageType.filename, fileSize = messageType.info?.size ?: 0, caption = messageType.caption?.trimEnd(), - formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(), + formattedCaption = formattedCaption, isEdited = content.isEdited, mediaSource = messageType.source, duration = messageType.info?.duration ?: Duration.ZERO, @@ -175,12 +186,15 @@ class TimelineItemContentMessageFactory( ) } is VoiceMessageType -> { + val dom = messageType.formattedCaption?.toHtmlDocument(permalinkParser = permalinkParser) + val formattedCaption = dom?.let(::parseHtml) + ?: messageType.caption?.withLinks() TimelineItemVoiceContent( eventId = eventId, filename = messageType.filename, fileSize = messageType.info?.size ?: 0, caption = messageType.caption?.trimEnd(), - formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(), + formattedCaption = formattedCaption, isEdited = content.isEdited, mediaSource = messageType.source, duration = messageType.info?.duration ?: Duration.ZERO, @@ -191,12 +205,15 @@ class TimelineItemContentMessageFactory( ) } is FileMessageType -> { + val dom = messageType.formattedCaption?.toHtmlDocument(permalinkParser = permalinkParser) + val formattedCaption = dom?.let(::parseHtml) + ?: messageType.caption?.withLinks() val fileExtension = fileExtensionExtractor.extractFromName(messageType.filename) TimelineItemFileContent( filename = messageType.filename, fileSize = messageType.info?.size ?: 0, caption = messageType.caption?.trimEnd(), - formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(), + formattedCaption = formattedCaption, isEdited = content.isEdited, thumbnailSource = messageType.info?.thumbnailSource, mediaSource = messageType.source, @@ -207,9 +224,9 @@ class TimelineItemContentMessageFactory( } is NoticeMessageType -> { val body = messageType.body.trimEnd() - val formattedBody = parseHtml(messageType.formatted) ?: textPillificationHelper.pillify( - body - ).safeLinkify() + val dom = messageType.formatted?.toHtmlDocument(permalinkParser = permalinkParser) + val formattedBody = dom?.let(::parseHtml) + ?: textPillificationHelper.pillify(body).safeLinkify() val htmlDocument = messageType.formatted?.toHtmlDocument(permalinkParser = permalinkParser) TimelineItemNoticeContent( body = body, @@ -220,12 +237,13 @@ class TimelineItemContentMessageFactory( } is TextMessageType -> { val body = messageType.body.trimEnd() - val formattedBody = parseHtml(messageType.formatted) ?: textPillificationHelper.pillify( - body - ).safeLinkify() + val dom = messageType.formatted?.toHtmlDocument(permalinkParser = permalinkParser) + val formattedBody = dom?.let(::parseHtml) + ?: textPillificationHelper.pillify(body).safeLinkify() + val htmlDocument = messageType.formatted?.toHtmlDocument(permalinkParser = permalinkParser) TimelineItemTextContent( body = body, - htmlDocument = messageType.formatted?.toHtmlDocument(permalinkParser = permalinkParser), + htmlDocument = htmlDocument, formattedBody = formattedBody, isEdited = content.isEdited, ) @@ -252,21 +270,11 @@ class TimelineItemContentMessageFactory( return result?.takeIf { it.isFinite() } } - private fun parseHtml(formattedBody: FormattedBody?, prefix: String? = null): CharSequence? { - if (formattedBody == null || formattedBody.format != MessageFormat.HTML) return null - val result = htmlConverterProvider.provide() - .fromHtmlToSpans(formattedBody.body.trimEnd()) + private fun parseHtml(document: Document): CharSequence? { + return htmlConverterProvider.provide() + .fromDocumentToSpans(document) .let { textPillificationHelper.pillify(it) } .safeLinkify() - return if (prefix != null) { - buildSpannedString { - append(prefix) - append(" ") - append(result) - } - } else { - result - } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt index ec2eba6544..2ceb81c487 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentProfileChangeFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentProfileChangeFactory.kt index a0a0b1f357..083b25658a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentProfileChangeFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentProfileChangeFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 c79f2abbbc..d60a0635de 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRoomMembershipFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRoomMembershipFactory.kt index a602e5274b..73c92c4cb7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRoomMembershipFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentRoomMembershipFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStateFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStateFactory.kt index 6716a7ff83..7eb071b1d6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStateFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentStateFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 0652f41365..2f3525471e 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentUTDFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentUTDFactory.kt index 0d44b7bf65..0b3719f41a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentUTDFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentUTDFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt index 6043cb57ff..77f1f96332 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemEventFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -21,7 +22,6 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItemReac import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo import io.element.android.features.messages.impl.utils.messagesummary.MessageSummaryFormatter -import io.element.android.libraries.architecture.map import io.element.android.libraries.core.bool.orTrue import io.element.android.libraries.dateformatter.api.DateFormatter import io.element.android.libraries.dateformatter.api.DateFormatterMode @@ -37,7 +37,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.getDisambigua import io.element.android.libraries.matrix.ui.messages.reply.map import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import java.util.Date @AssistedInject class TimelineItemEventFactory( @@ -146,10 +145,9 @@ class TimelineItemEventFactory( senders = reaction.senders .sortedByDescending { it.timestamp } .map { - val date = Date(it.timestamp) AggregatedReactionSender( senderId = it.senderId, - timestamp = date, + timestamp = it.timestamp, sentTime = dateFormatter.format( it.timestamp, DateFormatterMode.TimeOrDate, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemDaySeparatorFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemDaySeparatorFactory.kt index a93a8888ea..afdd9734ca 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemDaySeparatorFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemDaySeparatorFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt index bcb81ff9e4..73fc46669e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/focus/FocusRequestStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/focus/FocusRequestStateProvider.kt index 1660ab67cc..0a6fb70cc5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/focus/FocusRequestStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/focus/FocusRequestStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/focus/FocusRequestStateView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/focus/FocusRequestStateView.kt index 226d0959d5..3dff7ac4f7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/focus/FocusRequestStateView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/focus/FocusRequestStateView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt index a56a4d09e9..12c457175f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/Groupability.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt index 2bbcc4f3ba..6af4f8be6a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReaction.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReaction.kt index c9ae593af9..5fab8071c1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReaction.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReaction.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionProvider.kt index b7400c7616..e5eb52c91e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -33,13 +34,14 @@ fun anAggregatedReaction( val timeFormatter = DateFormat.getTimeInstance(DateFormat.SHORT, java.util.Locale.US).apply { timeZone = TimeZone.getTimeZone("UTC") } - val date = Date(1_689_061_264L) + val timestamp = 1_689_061_264L + val date = Date(timestamp) val senders = buildList { repeat(count) { index -> add( AggregatedReactionSender( senderId = if (isHighlighted && index == 0) userId else UserId("@user$index:server.org"), - timestamp = date, + timestamp = timestamp, sentTime = timeFormatter.format(date), ) ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionSender.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionSender.kt index 9da9781bc8..347de26888 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionSender.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionSender.kt @@ -1,21 +1,19 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl.timeline.model -import androidx.compose.runtime.Immutable import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.user.MatrixUser -import java.util.Date -@Immutable data class AggregatedReactionSender( val senderId: UserId, - val timestamp: Date, + val timestamp: Long, val sentTime: String, val user: MatrixUser? = null ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/NewEventState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/NewEventState.kt index 0d6a551410..cac2798fdd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/NewEventState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/NewEventState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt index 5591616517..1f01b16012 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -27,7 +28,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransa import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield import io.element.android.libraries.matrix.api.timeline.item.event.MessageShieldProvider -import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails import io.element.android.libraries.matrix.api.timeline.item.event.SendHandleProvider import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemDebugInfoProvider import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin @@ -57,20 +58,18 @@ sealed interface TimelineItem { is GroupedEvents -> "groupedEvent" } - @Immutable data class Virtual( val id: UniqueId, val model: TimelineItemVirtualModel ) : TimelineItem - @Immutable data class Event( val id: UniqueId, // Note: eventId can be null when the event is a local echo val eventId: EventId? = null, val transactionId: TransactionId? = null, val senderId: UserId, - val senderProfile: ProfileTimelineDetails, + val senderProfile: ProfileDetails, val senderAvatar: AvatarData, val content: TimelineItemEventContent, val sentTimeMillis: Long = 0L, @@ -124,7 +123,6 @@ sealed interface TimelineItem { val sendhandle: SendHandle? get() = sendHandleProvider() } - @Immutable data class GroupedEvents( val id: UniqueId, val events: ImmutableList, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemGroupPosition.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemGroupPosition.kt index 536ebd13ef..32cbe8906b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemGroupPosition.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemGroupPosition.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReactions.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReactions.kt index 115d8dab56..b20cc5148b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReactions.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReactions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReactionsProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReactionsProvider.kt index b0084f20ee..3190e61a75 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReactionsProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReactionsProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReadReceipts.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReadReceipts.kt index 9181e94a72..e8d423e7fb 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReadReceipts.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItemReadReceipts.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/bubble/BubbleState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/bubble/BubbleState.kt index 81aa459413..2cbd145811 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/bubble/BubbleState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/bubble/BubbleState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/bubble/BubbleStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/bubble/BubbleStateProvider.kt index 02a6e19504..cdd24ed264 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/bubble/BubbleStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/bubble/BubbleStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt index d543294d07..f28de4f673 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContentProvider.kt index 5e9facbde5..9fddfa98ff 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContentProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEmoteContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEmoteContent.kt index dc04e5b269..28946e8eea 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEmoteContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEmoteContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEncryptedContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEncryptedContent.kt index 69266a75fd..5d82d17d7b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEncryptedContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEncryptedContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEncryptedContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEncryptedContentProvider.kt index 8871c75cd2..bb53da3b7b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEncryptedContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEncryptedContentProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt index be7c47439a..9c4c48d11e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt index 36c32222be..ac93a0ac4f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContentProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContent.kt index c012cfcba7..680797e36b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContentProvider.kt index 9f29c861da..58e25e5dcd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContentProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt index 5b26d28806..4828f22ba8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentProvider.kt index a5527bc74f..d07d5db6a4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContentProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLegacyCallInviteContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLegacyCallInviteContent.kt index bffeeec767..088e438c6d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLegacyCallInviteContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLegacyCallInviteContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLocationContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLocationContent.kt index c5d8d33061..1114b2ab15 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLocationContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLocationContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLocationContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLocationContentProvider.kt index 5776d31a1f..0fd3f5f41b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLocationContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemLocationContentProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemNoticeContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemNoticeContent.kt index 6f2601a233..bb6f6db685 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemNoticeContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemNoticeContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt index 7fee6aee73..8ddeb3d041 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt index 5704b9d9fc..26577f5230 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemProfileChangeContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemProfileChangeContent.kt index c6eb1d5a46..b4d7530a47 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemProfileChangeContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemProfileChangeContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRedactedContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRedactedContent.kt index 0652255d38..2a9a23d996 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRedactedContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRedactedContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRoomMembershipContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRoomMembershipContent.kt index 5f1e3a9f0a..4d43f0d40a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRoomMembershipContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRoomMembershipContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRtcNotificationContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRtcNotificationContent.kt index 0c2f21fc5b..00ad32ba5f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRtcNotificationContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemRtcNotificationContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStateContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStateContent.kt index 50814dafef..7293fb70de 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStateContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStateContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStateEventContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStateEventContent.kt index 1d4d104470..6c24e5044f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStateEventContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStateEventContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContent.kt index 6d7809b258..d333c0b6e7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContentProvider.kt index acda873b77..eb564cd803 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContentProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextBasedContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextBasedContent.kt index 9bac2ce531..4d941e73db 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextBasedContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextBasedContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextContent.kt index 7cedb48377..2de0d6cead 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemTextContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemUnknownContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemUnknownContent.kt index 3783712136..ce9525cb03 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemUnknownContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemUnknownContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt index b8eeedd3bb..80635c6e18 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContentProvider.kt index a7499dc7dd..0fadb6c9a6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContentProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContent.kt index cc4a764c2c..29a432e6bd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContentProvider.kt index 211bc5037b..d596963ebc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContentProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemDaySeparatorModel.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemDaySeparatorModel.kt index 8d6f363859..a55564488a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemDaySeparatorModel.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemDaySeparatorModel.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemDaySeparatorModelProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemDaySeparatorModelProvider.kt index 2ae4928a09..d31d35d62f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemDaySeparatorModelProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemDaySeparatorModelProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemLastForwardIndicatorModel.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemLastForwardIndicatorModel.kt index 3de014c30f..a87b577dfe 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemLastForwardIndicatorModel.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemLastForwardIndicatorModel.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemLoadingIndicatorModel.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemLoadingIndicatorModel.kt index 5ff2ac6c5b..f05110c4b3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemLoadingIndicatorModel.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemLoadingIndicatorModel.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemReadMarkerModel.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemReadMarkerModel.kt index c79218461f..b163760023 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemReadMarkerModel.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemReadMarkerModel.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemRoomBeginningModel.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemRoomBeginningModel.kt index 0b198c9f3c..b53c96d720 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemRoomBeginningModel.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemRoomBeginningModel.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemTypingNotificationModel.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemTypingNotificationModel.kt index 9ffb2c88a2..cdb957e41d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemTypingNotificationModel.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemTypingNotificationModel.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemVirtualModel.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemVirtualModel.kt index 39feea9527..b1ab5c99c8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemVirtualModel.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemVirtualModel.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/AspectRatioProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/AspectRatioProvider.kt index 6e27158e9e..0ebd63439a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/AspectRatioProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/AspectRatioProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedView.kt index f5129480c4..de55735b76 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/RatioHelper.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/RatioHelper.kt index 1ef27005c8..d6031dcf1e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/RatioHelper.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/RatioHelper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineItem.kt index 1f306c74ea..5a5363f0c6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionEvent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionEvent.kt index 99eb9fd078..3d75268777 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionEvent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionEvent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenter.kt index 0d9db51d98..b2ea1d6bd8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -56,7 +57,7 @@ class TimelineProtectionPresenter( return TimelineProtectionState( protectionState = protectionState, - eventSink = { event -> handleEvent(event) } + eventSink = ::handleEvent, ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionState.kt index ddcd5c224b..169e5ba7f4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionStateProvider.kt index 012fdc87c3..3e7bfdb28b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/Modifiers.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/Modifiers.kt index e9bf26a164..311ff8c803 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/Modifiers.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/util/Modifiers.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/MessagesViewTopBar.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/MessagesViewTopBar.kt index 62c9c3e206..7727b28398 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/MessagesViewTopBar.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/MessagesViewTopBar.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/ThreadTopBar.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/ThreadTopBar.kt index 5a95c2c4b2..2247566531 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/ThreadTopBar.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/topbars/ThreadTopBar.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt index bedbf8104c..a27d91688f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationState.kt index 488284314c..e6c1f56dad 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt index 41b10478aa..0506026b86 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt index 0d867425f2..c8e5722b19 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingRoomMember.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingRoomMember.kt index 50a0af9135..de7232c9ea 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingRoomMember.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingRoomMember.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/Emoji.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/Emoji.kt index 84fdd62de1..3a0f16b3e9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/Emoji.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/Emoji.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelper.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelper.kt index bb95e1a26c..f74d3b5f30 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelper.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/TextPillificationHelper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,7 +15,6 @@ import android.text.style.URLSpan import android.util.Patterns import androidx.core.text.getSpans import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.MatrixPatternType import io.element.android.libraries.matrix.api.core.MatrixPatterns @@ -33,7 +33,6 @@ interface TextPillificationHelper { } @ContributesBinding(RoomScope::class) -@Inject class DefaultTextPillificationHelper( private val mentionSpanProvider: MentionSpanProvider, private val permalinkParser: PermalinkParser, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/DefaultMessageSummaryFormatter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/DefaultMessageSummaryFormatter.kt index 1f1619e3f6..0aeb3bb8fc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/DefaultMessageSummaryFormatter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/DefaultMessageSummaryFormatter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.features.messages.impl.utils.messagesummary import android.content.Context import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent @@ -33,7 +33,6 @@ import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.ui.strings.CommonStrings @ContributesBinding(RoomScope::class) -@Inject class DefaultMessageSummaryFormatter( @ApplicationContext private val context: Context, ) : MessageSummaryFormatter { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/MessageSummaryFormatter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/MessageSummaryFormatter.kt index 6e590c8779..084dce192c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/MessageSummaryFormatter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/utils/messagesummary/MessageSummaryFormatter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/DefaultVoiceMessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/DefaultVoiceMessageComposerPresenter.kt index 7c5eeb4969..56b0e402d2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/DefaultVoiceMessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/DefaultVoiceMessageComposerPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -25,14 +26,14 @@ import dev.zacsweers.metro.AssistedInject import dev.zacsweers.metro.ContributesBinding import im.vector.app.features.analytics.plan.Composer import io.element.android.features.messages.api.MessageComposerContext -import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerEvents +import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerEvent import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerPresenter import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerState import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.matrix.api.timeline.Timeline -import io.element.android.libraries.mediaupload.api.MediaSender -import io.element.android.libraries.permissions.api.PermissionsEvents +import io.element.android.libraries.mediaupload.api.MediaSenderFactory +import io.element.android.libraries.permissions.api.PermissionsEvent import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent @@ -56,7 +57,7 @@ class DefaultVoiceMessageComposerPresenter( @Assisted private val timelineMode: Timeline.Mode, private val voiceRecorder: VoiceRecorder, private val analyticsService: AnalyticsService, - mediaSenderFactory: MediaSender.Factory, + mediaSenderFactory: MediaSenderFactory, private val player: VoiceMessageComposerPlayer, private val messageComposerContext: MessageComposerContext, permissionsPresenterFactory: PermissionsPresenter.Factory @@ -68,7 +69,6 @@ class DefaultVoiceMessageComposerPresenter( } private val permissionsPresenter = permissionsPresenterFactory.create(Manifest.permission.RECORD_AUDIO) - private val mediaSender = mediaSenderFactory.create(timelineMode) @Composable @@ -88,7 +88,7 @@ class DefaultVoiceMessageComposerPresenter( player.setMedia(recording.file.path) } - val onLifecycleEvent = { event: Lifecycle.Event -> + fun handleLifecycleEvent(event: Lifecycle.Event) { when (event) { Lifecycle.Event.ON_PAUSE -> { sessionCoroutineScope.finishRecording() @@ -101,18 +101,17 @@ class DefaultVoiceMessageComposerPresenter( } } - val onVoiceMessageRecorderEvent = { event: VoiceMessageComposerEvents.RecorderEvent -> - val permissionGranted = permissionState.permissionGranted - when (event.recorderEvent) { + fun handleVoiceMessageRecorderEvent(event: VoiceMessageRecorderEvent) { + when (event) { VoiceMessageRecorderEvent.Start -> { Timber.v("Voice message record button pressed") when { - permissionGranted -> { + permissionState.permissionGranted -> { localCoroutineScope.startRecording() } else -> { Timber.i("Voice message permission needed") - permissionState.eventSink(PermissionsEvents.RequestPermissions) + permissionState.eventSink(PermissionsEvent.RequestPermissions) } } } @@ -126,7 +125,8 @@ class DefaultVoiceMessageComposerPresenter( } } } - val onPlayerEvent = { event: VoiceMessagePlayerEvent -> + + fun handleVoiceMessagePlayerEvent(event: VoiceMessagePlayerEvent) { localCoroutineScope.launch { when (event) { VoiceMessagePlayerEvent.Play -> player.play() @@ -136,28 +136,16 @@ class DefaultVoiceMessageComposerPresenter( } } - val onAcceptPermissionsRationale = { - permissionState.eventSink(PermissionsEvents.OpenSystemSettingAndCloseDialog) - } - - val onDismissPermissionsRationale = { - permissionState.eventSink(PermissionsEvents.CloseDialog) - } - - val onDismissSendFailureDialog = { - showSendFailureDialog = false - } - - val onSendButtonPress = lambda@{ + fun sendVoiceMessage() { val finishedState = recorderState as? VoiceRecorderState.Finished if (finishedState == null) { val exception = VoiceMessageException.FileException("No file to send") analyticsService.trackError(exception) Timber.e(exception) - return@lambda + return } if (isSending) { - return@lambda + return } isSending = true player.pause() @@ -176,21 +164,27 @@ class DefaultVoiceMessageComposerPresenter( } } - val handleEvents: (VoiceMessageComposerEvents) -> Unit = { event -> + fun handleEvent(event: VoiceMessageComposerEvent) { when (event) { - is VoiceMessageComposerEvents.RecorderEvent -> onVoiceMessageRecorderEvent(event) - is VoiceMessageComposerEvents.PlayerEvent -> onPlayerEvent(event.playerEvent) - is VoiceMessageComposerEvents.SendVoiceMessage -> localCoroutineScope.launch { - onSendButtonPress() + is VoiceMessageComposerEvent.RecorderEvent -> handleVoiceMessageRecorderEvent(event.recorderEvent) + is VoiceMessageComposerEvent.PlayerEvent -> handleVoiceMessagePlayerEvent(event.playerEvent) + is VoiceMessageComposerEvent.SendVoiceMessage -> localCoroutineScope.launch { + sendVoiceMessage() } - VoiceMessageComposerEvents.DeleteVoiceMessage -> { + VoiceMessageComposerEvent.DeleteVoiceMessage -> { player.pause() localCoroutineScope.deleteRecording() } - VoiceMessageComposerEvents.DismissPermissionsRationale -> onDismissPermissionsRationale() - VoiceMessageComposerEvents.AcceptPermissionRationale -> onAcceptPermissionsRationale() - is VoiceMessageComposerEvents.LifecycleEvent -> onLifecycleEvent(event.event) - VoiceMessageComposerEvents.DismissSendFailureDialog -> onDismissSendFailureDialog() + VoiceMessageComposerEvent.DismissPermissionsRationale -> { + permissionState.eventSink(PermissionsEvent.CloseDialog) + } + VoiceMessageComposerEvent.AcceptPermissionRationale -> { + permissionState.eventSink(PermissionsEvent.OpenSystemSettingAndCloseDialog) + } + is VoiceMessageComposerEvent.LifecycleEvent -> handleLifecycleEvent(event.event) + VoiceMessageComposerEvent.DismissSendFailureDialog -> { + showSendFailureDialog = false + } } } @@ -198,7 +192,10 @@ class DefaultVoiceMessageComposerPresenter( voiceMessageState = when (val state = recorderState) { is VoiceRecorderState.Recording -> VoiceMessageState.Recording( duration = state.elapsedTime, - levels = state.levels.toImmutableList(), + levels = state.levels + // Keep only the last 128 samples for display, else we can have a crash + .takeLast(128) + .toImmutableList(), ) is VoiceRecorderState.Finished -> previewState( @@ -211,7 +208,7 @@ class DefaultVoiceMessageComposerPresenter( showPermissionRationaleDialog = permissionState.showDialog, showSendFailureDialog = showSendFailureDialog, keepScreenOn = keepScreenOn, - eventSink = handleEvents, + eventSink = ::handleEvent, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPlayer.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPlayer.kt index a8ca37dac7..1e55c45542 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPlayer.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPlayer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessagePermissionRationaleDialog.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessagePermissionRationaleDialog.kt index 7c61aa6929..e45f28d3bf 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessagePermissionRationaleDialog.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessagePermissionRationaleDialog.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageSendingFailedDialog.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageSendingFailedDialog.kt index 5e96ed4480..9a7a2b8fce 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageSendingFailedDialog.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageSendingFailedDialog.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManager.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManager.kt index be1db85d6b..08b5630d3f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManager.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManager.kt @@ -1,14 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl.voicemessages.timeline import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem @@ -21,7 +21,6 @@ interface RedactedVoiceMessageManager { } @ContributesBinding(RoomScope::class) -@Inject class DefaultRedactedVoiceMessageManager( private val dispatchers: CoroutineDispatchers, private val mediaPlayer: MediaPlayer, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePresenter.kt index 2931f6af53..eef5c5efab 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/VoiceMessagePresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/main/res/values-bg/translations.xml b/features/messages/impl/src/main/res/values-bg/translations.xml index 8efecfaf26..57a9fed4db 100644 --- a/features/messages/impl/src/main/res/values-bg/translations.xml +++ b/features/messages/impl/src/main/res/values-bg/translations.xml @@ -39,10 +39,15 @@ "Нямате разрешение да публикувате в тази стая" "Показване на по-малко" "Показване на повече" + "Нови" "%1$d промяна в стаята" "%1$d промени в стаята" + "Преминаване към новата стая" + "Тази стая е заменена и вече не е активна" + "Преглед на старите съобщения" + "Тази стая е продължение на друга стая" "%1$s, %2$s и %3$d друг" "%1$s, %2$s и %3$d други" diff --git a/features/messages/impl/src/main/res/values-eo/translations.xml b/features/messages/impl/src/main/res/values-eo/translations.xml deleted file mode 100644 index a0810ca15b..0000000000 --- a/features/messages/impl/src/main/res/values-eo/translations.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - "Message history is unavailable in this room. Confirm this device to see your message history." - diff --git a/features/messages/impl/src/main/res/values-fa/translations.xml b/features/messages/impl/src/main/res/values-fa/translations.xml index 1446c3d389..b102f82a98 100644 --- a/features/messages/impl/src/main/res/values-fa/translations.xml +++ b/features/messages/impl/src/main/res/values-fa/translations.xml @@ -7,9 +7,11 @@ "اشیا" "شکلک‌ها و افراد" "سفر و مکان‌ها" + "شکلک‌های اخیر" "نمادها" "پردازش رسانه برای بارگذاری شکست خورد. لطفاً دوباره تلاش کنید." "بارگذاری رسانه شکست خورد. لطفاً دوباره تلاش کنید." + "پردازش کردن…" "انسداد کاربر" "اگر می‌خواهید همه پیام‌های فعلی و آینده را از این کاربر را پنهان کنید، علامت بزنید" "این پیام به مدیر کارساز خانگی شما گزارش خواهد شد. آنها قادر به خواندن پیام های رمزگذاری شده نخواهند بود." @@ -23,6 +25,7 @@ "نظرسنجی" "قالب‌بندی متن" "تاریخچه پیام درحال حاضر دردسترس نیست." + "تاریخچهٔ پیام‌های این اتاق در دسترس نیست. برای دیدن تاریخچهٔ پیام‌هایتان این افزاره را تأیید کنید." "می‌خواهید دوباره دعوتش کنید؟" "در این گپ تنهایید" "آگاهی به تمام اتاق" @@ -37,10 +40,15 @@ "اجازهٔ فرستادن به این اتاق را ندارید" "نمایش کم‌تر" "نمایش بیش‌تر" + "نمایش خلاصهٔ واکنش‌ها" "جدید" "%1$dتغییر اتاق" "%1$dتغییر اتاق" + "پرش به اتاق جدید" + "این اتاق جایگزین شده و دیگر فعّال نیست" + "دیدن پیام‌های قدیمی" + "این اتاق ادامهٔ اتاقی دیگر است" "%1$s و %2$s" diff --git a/features/messages/impl/src/main/res/values-hr/translations.xml b/features/messages/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..c3427df591 --- /dev/null +++ b/features/messages/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,84 @@ + + + "Aktivnosti" + "Zastave" + "Hrana i piće" + "Životinje i priroda" + "Objekti" + "Emotikoni i osobe" + "Putovanja i mjesta" + "Nedavni emotikoni" + "Simboli" + "Opisi možda neće biti vidljivi osobama koji se služe starijim aplikacijama." + "Dodirnite za promjenu kvalitete prijenosa videozapisa" + "Datoteka se nije mogla prenijeti." + "Prijenos medija za obradu nije uspio, pokušajte ponovno." + "Prijenos medija nije uspio, pokušajte ponovno." + "Maksimalna dopuštena veličina datoteke je %1$s." + "Datoteka je prevelika za prijenos" + "Stavka %1$d od %2$d" + "Optimiziraj kvalitetu slike" + "Obrada…" + "Blokiraj korisnika" + "Označite ako želite sakriti sve trenutačne i buduće poruke od ovog korisnika" + "Ova poruka bit će prijavljena administratoru vašeg matičnog poslužitelja. On neće moći pročitati nijednu šifriranu poruku." + "Razlog za prijavu ovog sadržaja" + "Kamera" + "Uslikaj" + "Snimi videozapis" + "Privitak" + "Biblioteka fotografija i videozapisa" + "Lokacija" + "Anketa" + "Oblikovanje teksta" + "Povijest poruka trenutačno nije dostupna." + "Povijest poruka nije dostupna u ovoj sobi. Potvrdite ovaj uređaj kako biste vidjeli povijest poruka." + "Želite li ih pozvati natrag?" + "Sami ste u ovom razgovoru" + "Obavijestite cijelu sobu" + "Svi" + "Pošalji ponovno" + "Slanje vaše poruke nije uspjelo" + "Dodaj reakciju" + "Ovo je početak sobe %1$s." + "Ovo je početak ovog razgovora." + "Nepodržani poziv. Pitajte pozivatelja može li se služiti novom aplikacijom Element X." + "Prikaži manje" + "Poruka je kopirana" + "Nemate dopuštenje za objavljivanje u ovoj sobi" + + "%1$d član reagirao je s %2$s" + "%1$d člana reagirala su s %2$s" + "%1$d članova reagiralo je s %2$s" + + + "Vi i %1$d član reagirali ste s %2$s" + "Vi i %1$d člana reagirali ste s %2$s" + "Vi i %1$d članova reagirali ste s %2$s" + + "Reagirali ste s %1$s" + "Prikaži manje" + "Prikaži više" + "Prikaži sažetak reakcija" + "Novo" + + "%1$d promjena sobe" + "%1$d promjene sobe" + "%1$d promjena sobe" + + "Prijeđi u novu sobu" + "Ova je soba zamijenjena i više nije aktivna" + "Pogledaj stare poruke" + "Ova je soba nastavak druge sobe" + + "%1$s, %2$s i ostalih %3$d" + "%1$s, %2$s i ostalih %3$d" + "%1$s, %2$s i ostalih %3$d" + + + "%1$s tipka" + "%1$s tipka" + "%1$s tipka" + + "%1$s i %2$s" + diff --git a/features/messages/impl/src/main/res/values-it/translations.xml b/features/messages/impl/src/main/res/values-it/translations.xml index 87a7a05c93..c5b98c8178 100644 --- a/features/messages/impl/src/main/res/values-it/translations.xml +++ b/features/messages/impl/src/main/res/values-it/translations.xml @@ -7,6 +7,7 @@ "Oggetti" "Faccine & Persone" "Viaggi & Luoghi" + "Emoji recenti" "Simboli" "Le didascalie potrebbero non essere visibili agli utenti di app meno recenti." "Tocca per modificare la qualità di caricamento del video" @@ -15,6 +16,7 @@ "Caricamento del file multimediale fallito, riprova." "La dimensione massima consentita del file è %1$s ." "Il file è troppo grande per essere caricato" + "Elemento %1$d di %2$d" "Ottimizza la qualità delle immagini" "Elaborazione…" "Blocca utente" diff --git a/features/messages/impl/src/main/res/values-nb/translations.xml b/features/messages/impl/src/main/res/values-nb/translations.xml index 5df3e5eac3..69b6586b4c 100644 --- a/features/messages/impl/src/main/res/values-nb/translations.xml +++ b/features/messages/impl/src/main/res/values-nb/translations.xml @@ -16,6 +16,7 @@ "Opplasting av medier mislyktes, vennligst prøv igjen." "Maksimal tillatt filstørrelse er %1$s." "Filen er for stor til å lastes opp" + "Fil %1$d av %2$d" "Optimaliser bildekvaliteten" "Behandler…" "Blokker bruker" diff --git a/features/messages/impl/src/main/res/values-pl/translations.xml b/features/messages/impl/src/main/res/values-pl/translations.xml index b7925c7865..bd9f649522 100644 --- a/features/messages/impl/src/main/res/values-pl/translations.xml +++ b/features/messages/impl/src/main/res/values-pl/translations.xml @@ -7,13 +7,18 @@ "Obiekty" "Buźki i osoby" "Podróż i miejsca" + "Ostatnie emoji" "Symbole" "Opis może być niedostępny dla osób korzystających ze starszej wersji aplikacji." + "Dotknij, aby zmienić jakość przesyłania wideo." "Nie udało się przesłać pliku." "Przetwarzanie multimediów do przesłania nie powiodło się, spróbuj ponownie." "Przesyłanie multimediów nie powiodło się, spróbuj ponownie." "Maksymalny dozwolony rozmiar pliku to %1$s." "Plik jest za duży, aby go przesłać." + "Pozycja %1$d z %2$d" + "Zoptymalizuj jakość obrazu" + "Przetwarzanie…" "Zablokuj użytkownika" "Sprawdź, czy chcesz ukryć wszystkie bieżące i przyszłe wiadomości od tego użytkownika." "Ta wiadomość zostanie zgłoszona do administratora Twojego serwera domowego. Nie będzie mógł on przeczytać żadnych zaszyfrowanych wiadomości." diff --git a/features/messages/impl/src/main/res/values-pt-rBR/translations.xml b/features/messages/impl/src/main/res/values-pt-rBR/translations.xml index c85f17bdb3..6098fd0889 100644 --- a/features/messages/impl/src/main/res/values-pt-rBR/translations.xml +++ b/features/messages/impl/src/main/res/values-pt-rBR/translations.xml @@ -7,13 +7,18 @@ "Objetos" "Sorrisos & Pessoas" "Viagens & Lugares" + "Emojis recentes" "Símbolos" "As legendas podem não ser visíveis para pessoas que usam apps mais antigos." + "Toque para alterar a qualidade do envio do vídeo" "O arquivo não pôde ser enviado." "Falha ao processar a mídia para o envio. Tente novamente." "Falha ao enviar mídia. Tente novamente." "O tamanho de arquivo máximo permitido é %1$s." "O arquivo é muito grande para enviar" + "%1$d de %2$d itens" + "Otimizar qualidade da imagem" + "Processando…" "Bloquear usuário" "Marque se você deseja ocultar todas as mensagens atuais e futuras desse usuário" "Essa mensagem será reportada ao administrador do seu servidor-casa. Eles não conseguirão ler nenhuma mensagem criptografada." 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 6fe6ec73ec..bca6ccfc89 100644 --- a/features/messages/impl/src/main/res/values-sk/translations.xml +++ b/features/messages/impl/src/main/res/values-sk/translations.xml @@ -7,6 +7,7 @@ "Predmety" "Smajlíky a ľudia" "Cestovanie a miesta" + "Nedávne emotikony" "Symboly" "Titulky nemusia byť viditeľné pre ľudí používajúcich staršie aplikácie." "Ťuknutím zmeníte kvalitu nahratého videa" @@ -15,6 +16,7 @@ "Nepodarilo sa nahrať médiá, skúste to prosím znova." "Maximálna povolená veľkosť súboru je %1$s." "Súbor je príliš veľký na nahratie" + "Položka %1$d z %2$d" "Optimalizovať kvalitu obrázku" "Prebieha spracovanie…" "Zablokovať používateľa" diff --git a/features/messages/impl/src/main/res/values-uz/translations.xml b/features/messages/impl/src/main/res/values-uz/translations.xml index e48de2b6ad..8f75212a31 100644 --- a/features/messages/impl/src/main/res/values-uz/translations.xml +++ b/features/messages/impl/src/main/res/values-uz/translations.xml @@ -8,8 +8,15 @@ "Smayllar va odamlar" "Sayohat va Joylar" "Belgilar" + "Taglavhalar eski ilovalardan foydalanuvchilarga ko‘rinmasligi mumkin." + "Video yuklash sifatini oʻzgartirish uchun bosing" + "Faylni yuklab boʻlmadi." "Mediani yuklab bo‘lmadi, qayta urinib ko‘ring." "Media yuklanmadi, qayta urinib ko‘ring." + "Ruxsat etilgan maksimal fayl hajmi %1$s ." + "Fayl yuklash uchun juda katta" + "Tasvir sifatini optimallashtirish" + "Qayta ishlanmoqda…" "Foydalanuvchini bloklash" "Ushbu foydalanuvchidan barcha joriy va kelajakdagi xabarlarni yashirishni xohlayotganingizni tekshiring" "Bu xabar uy serveringiz administratoriga xabar qilinadi. Ular hech qanday shifrlangan xabarlarni o\'qiy olmaydi." @@ -33,16 +40,31 @@ "Emoji qo\'shmoq" "Bu %1$sni boshlanishi" "Bu suhbatning boshlanishi." + "Chaqiruv qabul qilinmaydi. Chaqiruvchidan yangi Element X ilovasidan foydalanishi mumkinligini so‘rang." "Kamroq ko\'rsatish" "Xabar nusxalandi" "Sizda bu xonaga post yozishga ruxsat yo‘q" + + "%1$d ta a’zo %2$s bilan munosabat bildirdi" + "%1$d ta a’zo %2$s bilan munosabat bildirdi" + + + "Siz va %1$d ta a’zo %2$s bilan munosabat bildirdi" + "Siz va %1$d ta a’zo %2$s bilan munosabat bildirdi" + + "%1$s bilan munosabat bildirdingiz" "Kamroq ko\'rsatish" "Ko\'proq ko\'rsatish" + "Reaksiyalar xulosasini chiqarish" "Yangi" "%1$dxonani almashtirish" "%1$dxona o\'zgarishi" + "Yangi xonaga o‘tish" + "Bu room almashtirildi va endi faol emas" + "Eski xabarlarni ko‘rish" + "Bu xona boshqa xonaning davomi" "%1$s, %2$s va %3$d boshqalar" "%1$s, %2$s va %3$d boshqalar" diff --git a/features/messages/impl/src/main/res/values-zh-rTW/translations.xml b/features/messages/impl/src/main/res/values-zh-rTW/translations.xml index 6abf951e4f..eb6ea81365 100644 --- a/features/messages/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/messages/impl/src/main/res/values-zh-rTW/translations.xml @@ -7,6 +7,7 @@ "物品" "表情與人物" "旅行與景點" + "最近使用的表情符號" "標誌" "使用舊應用程式的使用者可能看不到標題。" "輕點即可變更影片上傳品質" @@ -15,6 +16,7 @@ "無法上傳媒體檔案,請稍後再試。" "允許的最大檔案大小為 %1$s。" "檔案太大,無法上傳" + "第 %1$d 個項目,共 %2$d 個" "最佳化影像品質" "正在處理……" "封鎖使用者" diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPointTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPointTest.kt index 2de761ad69..90c31b911a 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPointTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/DefaultMessagesEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,25 +11,23 @@ package io.element.android.features.messages.impl import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.compose.runtime.Composable import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node import com.bumble.appyx.testing.junit4.util.MainDispatcherRule import com.google.common.truth.Truth.assertThat -import io.element.android.features.call.api.CallType -import io.element.android.features.call.api.ElementCallEntryPoint -import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint -import io.element.android.features.location.api.SendLocationEntryPoint -import io.element.android.features.location.api.ShowLocationEntryPoint +import io.element.android.features.call.test.FakeElementCallEntryPoint +import io.element.android.features.forward.test.FakeForwardEntryPoint +import io.element.android.features.knockrequests.test.FakeKnockRequestsListEntryPoint import io.element.android.features.location.test.FakeLocationService +import io.element.android.features.location.test.FakeSendLocationEntryPoint +import io.element.android.features.location.test.FakeShowLocationEntryPoint import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.features.messages.impl.pinned.banner.createPinnedEventsTimelineProvider import io.element.android.features.messages.impl.timeline.createTimelineController -import io.element.android.features.poll.api.create.CreatePollEntryPoint +import io.element.android.features.poll.test.create.FakeCreatePollEntryPoint import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.permalink.PermalinkData -import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_USER_ID @@ -36,7 +35,7 @@ import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache import io.element.android.libraries.matrix.ui.messages.RoomNamesCache -import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint +import io.element.android.libraries.mediaviewer.test.FakeMediaViewerEntryPoint import io.element.android.libraries.textcomposer.mentions.MentionSpanTheme import io.element.android.libraries.textcomposer.mentions.MentionSpanUpdater import io.element.android.services.analytics.test.FakeAnalyticsService @@ -63,33 +62,12 @@ class DefaultMessagesEntryPointTest { plugins = plugins, roomListService = FakeRoomListService(), sessionId = A_SESSION_ID, - sendLocationEntryPoint = object : SendLocationEntryPoint { - override fun builder(timelineMode: Timeline.Mode) = lambdaError() - }, - showLocationEntryPoint = object : ShowLocationEntryPoint { - override fun createNode(parentNode: Node, buildContext: BuildContext, inputs: ShowLocationEntryPoint.Inputs) = lambdaError() - }, - createPollEntryPoint = object : CreatePollEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext) = lambdaError() - }, - elementCallEntryPoint = object : ElementCallEntryPoint { - override fun startCall(callType: CallType) = lambdaError() - override suspend fun handleIncomingCall( - callType: CallType.RoomCall, - eventId: EventId, - senderId: UserId, - roomName: String?, - senderName: String?, - avatarUrl: String?, - timestamp: Long, - expirationTimestamp: Long, - notificationChannelId: String, - textContent: String?, - ) = lambdaError() - }, - mediaViewerEntryPoint = object : MediaViewerEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext) = lambdaError() - }, + sendLocationEntryPoint = FakeSendLocationEntryPoint(), + showLocationEntryPoint = FakeShowLocationEntryPoint(), + createPollEntryPoint = FakeCreatePollEntryPoint(), + elementCallEntryPoint = FakeElementCallEntryPoint(), + mediaViewerEntryPoint = FakeMediaViewerEntryPoint(), + forwardEntryPoint = FakeForwardEntryPoint(), analyticsService = FakeAnalyticsService(), locationService = FakeLocationService(), room = FakeBaseRoom(), @@ -104,25 +82,26 @@ class DefaultMessagesEntryPointTest { mentionSpanTheme = MentionSpanTheme(A_USER_ID), pinnedEventsTimelineProvider = createPinnedEventsTimelineProvider(), timelineController = createTimelineController(), - knockRequestsListEntryPoint = object : KnockRequestsListEntryPoint { - override fun createNode(parentNode: Node, buildContext: BuildContext) = lambdaError() - }, + knockRequestsListEntryPoint = FakeKnockRequestsListEntryPoint(), dateFormatter = FakeDateFormatter(), coroutineDispatchers = testCoroutineDispatchers(), ) } val callback = object : MessagesEntryPoint.Callback { - override fun onRoomDetailsClick() = lambdaError() - override fun onUserDataClick(userId: UserId) = lambdaError() - override fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean) = lambdaError() - override fun onForwardedToSingleRoom(roomId: RoomId) = lambdaError() + override fun navigateToRoomDetails() = lambdaError() + override fun navigateToRoomMemberDetails(userId: UserId) = lambdaError() + override fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) = lambdaError() + override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) = lambdaError() + override fun navigateToRoom(roomId: RoomId) = lambdaError() } val initialTarget = MessagesEntryPoint.InitialTarget.Messages(focusedEventId = AN_EVENT_ID) val params = MessagesEntryPoint.Params(initialTarget) - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .params(params) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + params = params, + callback = callback, + ) assertThat(result).isInstanceOf(MessagesFlowNode::class.java) assertThat(result.plugins).contains(MessagesEntryPoint.Params(initialTarget)) assertThat(result.plugins).contains(callback) @@ -131,7 +110,7 @@ class DefaultMessagesEntryPointTest { @Test fun `test initial target to nav target mapping`() { assertThat(MessagesEntryPoint.InitialTarget.Messages(focusedEventId = AN_EVENT_ID).toNavTarget()) - .isEqualTo(MessagesFlowNode.NavTarget.Messages(AN_EVENT_ID)) + .isEqualTo(MessagesFlowNode.NavTarget.Messages(focusedEventId = AN_EVENT_ID)) assertThat(MessagesEntryPoint.InitialTarget.PinnedMessages.toNavTarget()) .isEqualTo(MessagesFlowNode.NavTarget.PinnedMessagesList) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/FakeMessagesNavigator.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/FakeMessagesNavigator.kt index bb59ca8551..68d2cd824b 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/FakeMessagesNavigator.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/FakeMessagesNavigator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -24,32 +25,37 @@ class FakeMessagesNavigator( private val onPreviewAttachmentLambda: (attachments: ImmutableList, inReplyToEventId: EventId?) -> Unit = { _, _ -> lambdaError() }, private val onNavigateToRoomLambda: (roomId: RoomId, threadId: EventId?, serverNames: List) -> Unit = { _, _, _ -> lambdaError() }, private val onOpenThreadLambda: (threadRootId: ThreadId, focusedEventId: EventId?) -> Unit = { _, _ -> lambdaError() }, + private val closeLambda: () -> Unit = { lambdaError() }, ) : MessagesNavigator { - override fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { + override fun navigateToEventDebugInfo(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { onShowEventDebugInfoClickLambda(eventId, debugInfo) } - override fun onForwardEventClick(eventId: EventId) { + override fun forwardEvent(eventId: EventId) { onForwardEventClickLambda(eventId) } - override fun onReportContentClick(eventId: EventId, senderId: UserId) { + override fun navigateToReportMessage(eventId: EventId, senderId: UserId) { onReportContentClickLambda(eventId, senderId) } - override fun onEditPollClick(eventId: EventId) { + override fun navigateToEditPoll(eventId: EventId) { onEditPollClickLambda(eventId) } - override fun onPreviewAttachment(attachments: ImmutableList, inReplyToEventId: EventId?) { + override fun navigateToPreviewAttachments(attachments: ImmutableList, inReplyToEventId: EventId?) { onPreviewAttachmentLambda(attachments, inReplyToEventId) } - override fun onNavigateToRoom(roomId: RoomId, eventId: EventId?, serverNames: List) { + override fun navigateToRoom(roomId: RoomId, eventId: EventId?, serverNames: List) { onNavigateToRoomLambda(roomId, eventId, serverNames) } - override fun onOpenThread(threadRootId: ThreadId, focusedEventId: EventId?) { + override fun navigateToThread(threadRootId: ThreadId, focusedEventId: EventId?) { onOpenThreadLambda(threadRootId, focusedEventId) } + + override fun close() { + closeLambda() + } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 56a3badf26..bdf84e9eec 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,13 +17,16 @@ import io.element.android.features.messages.impl.actionlist.ActionListEvents import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.anActionListState import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction +import io.element.android.features.messages.impl.crypto.historyvisible.aHistoryVisibleState import io.element.android.features.messages.impl.crypto.identity.anIdentityChangeState import io.element.android.features.messages.impl.fixtures.aMessageEvent import io.element.android.features.messages.impl.link.aLinkState -import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents +import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent import io.element.android.features.messages.impl.messagecomposer.MessageComposerState import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMessagesBannerState +import io.element.android.features.messages.impl.timeline.FakeMarkAsFullyRead +import io.element.android.features.messages.impl.timeline.MarkAsFullyRead import io.element.android.features.messages.impl.timeline.TimelineController import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.aTimelineState @@ -57,10 +61,10 @@ import io.element.android.libraries.matrix.api.core.toThreadId import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.permalink.PermalinkParser -import io.element.android.libraries.matrix.api.recentemojis.AddRecentEmoji import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.RoomMembersState import io.element.android.libraries.matrix.api.room.RoomMembershipState +import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo @@ -76,7 +80,6 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID_2 import io.element.android.libraries.matrix.test.A_THREAD_ID import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID_2 -import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser @@ -84,10 +87,11 @@ import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomMember -import io.element.android.libraries.matrix.test.sync.FakeSyncService +import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.matrix.test.timeline.aTimelineItemDebugInfo import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails +import io.element.android.libraries.recentemojis.api.AddRecentEmoji import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.TextEditorState import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown @@ -130,7 +134,6 @@ class MessagesPresenterTest { .isEqualTo(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom)) assertThat(initialState.userEventPermissions.canSendMessage).isTrue() assertThat(initialState.userEventPermissions.canRedactOwn).isTrue() - assertThat(initialState.hasNetworkConnection).isTrue() assertThat(initialState.snackbarMessage).isNull() assertThat(initialState.inviteProgress).isEqualTo(AsyncData.Uninitialized) assertThat(initialState.showReinvitePrompt).isFalse() @@ -142,11 +145,7 @@ class MessagesPresenterTest { fun `present - check that the room's unread flag is removed`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), markAsReadResult = { lambdaError() } ), typingNoticeResult = { Result.success(Unit) }, @@ -165,22 +164,24 @@ class MessagesPresenterTest { val toggleReactionSuccess = lambdaRecorder { _: String, _: EventOrTransactionId -> Result.success(true) } val toggleReactionFailure = lambdaRecorder { _: String, _: EventOrTransactionId -> Result.failure(IllegalStateException("Failed to send reaction")) } + val addRecentEmojiResult = lambdaRecorder { _: String -> Result.success(Unit) } val timeline = FakeTimeline().apply { this.toggleReactionLambda = toggleReactionSuccess } val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ), liveTimeline = timeline, typingNoticeResult = { Result.success(Unit) }, ) - val presenter = createMessagesPresenter(joinedRoom = room, coroutineDispatchers = coroutineDispatchers) + val presenter = createMessagesPresenter( + timeline = timeline, + joinedRoom = room, + addRecentEmoji = AddRecentEmoji { addRecentEmojiResult(it) }, + coroutineDispatchers = coroutineDispatchers, + ) presenter.testWithLifecycleOwner { skipItems(1) val initialState = awaitItem() @@ -196,6 +197,7 @@ class MessagesPresenterTest { assert(toggleReactionFailure) .isCalledOnce() .with(value("👍"), value(AN_EVENT_ID.toEventOrTransactionId())) + addRecentEmojiResult.assertions().isCalledOnce().with(value("👍")) } } @@ -207,22 +209,25 @@ class MessagesPresenterTest { toggle = !toggle Result.success(toggle) } - + val addRecentEmoji = lambdaRecorder { _: String -> + Result.success(Unit) + } val timeline = FakeTimeline().apply { this.toggleReactionLambda = toggleReactionSuccess } val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ), liveTimeline = timeline, typingNoticeResult = { Result.success(Unit) }, ) - val presenter = createMessagesPresenter(joinedRoom = room, coroutineDispatchers = coroutineDispatchers) + val presenter = createMessagesPresenter( + timeline = timeline, + joinedRoom = room, + addRecentEmoji = AddRecentEmoji { addRecentEmoji(it) }, + coroutineDispatchers = coroutineDispatchers + ) presenter.testWithLifecycleOwner { val initialState = awaitItem() initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) @@ -235,6 +240,7 @@ class MessagesPresenterTest { listOf(value("👍"), value(AN_EVENT_ID.toEventOrTransactionId())), ) skipItems(1) + addRecentEmoji.assertions().isCalledOnce().with(value("👍")) } } @@ -272,11 +278,7 @@ class MessagesPresenterTest { val event = aMessageEvent() val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), eventPermalinkResult = { Result.success("a link") }, ), typingNoticeResult = { Result.success(Unit) }, @@ -295,7 +297,7 @@ class MessagesPresenterTest { @Test fun `present - handle action reply`() = runTest { - val composerRecorder = EventsRecorder() + val composerRecorder = EventsRecorder() val presenter = createMessagesPresenter( messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) }, ) @@ -304,7 +306,7 @@ class MessagesPresenterTest { initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent())) awaitItem() composerRecorder.assertSingle( - MessageComposerEvents.SetMode( + MessageComposerEvent.SetMode( composerMode = MessageComposerMode.Reply( replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID), hideImage = false, @@ -326,7 +328,7 @@ class MessagesPresenterTest { @Test fun `present - handle action reply to an image media message`() = runTest { - val composerRecorder = EventsRecorder() + val composerRecorder = EventsRecorder() val presenter = createMessagesPresenter( messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) }, ) @@ -355,7 +357,7 @@ class MessagesPresenterTest { initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage)) awaitItem() composerRecorder.assertSingle( - MessageComposerEvents.SetMode( + MessageComposerEvent.SetMode( composerMode = MessageComposerMode.Reply( replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID), hideImage = false, @@ -367,7 +369,7 @@ class MessagesPresenterTest { @Test fun `present - handle action reply to a video media message`() = runTest { - val composerRecorder = EventsRecorder() + val composerRecorder = EventsRecorder() val presenter = createMessagesPresenter( messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) }, ) @@ -397,7 +399,7 @@ class MessagesPresenterTest { initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage)) awaitItem() composerRecorder.assertSingle( - MessageComposerEvents.SetMode( + MessageComposerEvent.SetMode( composerMode = MessageComposerMode.Reply( replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID), hideImage = false, @@ -409,7 +411,7 @@ class MessagesPresenterTest { @Test fun `present - handle action reply to a file media message`() = runTest { - val composerRecorder = EventsRecorder() + val composerRecorder = EventsRecorder() val presenter = createMessagesPresenter( messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) }, ) @@ -432,7 +434,7 @@ class MessagesPresenterTest { initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage)) awaitItem() composerRecorder.assertSingle( - MessageComposerEvents.SetMode( + MessageComposerEvent.SetMode( composerMode = MessageComposerMode.Reply( replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID), hideImage = false, @@ -444,7 +446,7 @@ class MessagesPresenterTest { @Test fun `present - handle action edit`() = runTest { - val composerRecorder = EventsRecorder() + val composerRecorder = EventsRecorder() val presenter = createMessagesPresenter( messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) }, ) @@ -453,7 +455,7 @@ class MessagesPresenterTest { initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent())) awaitItem() composerRecorder.assertSingle( - MessageComposerEvents.SetMode( + MessageComposerEvent.SetMode( composerMode = MessageComposerMode.Edit( eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(), content = (aMessageEvent().content as TimelineItemTextContent).body @@ -498,11 +500,7 @@ class MessagesPresenterTest { val liveTimeline = FakeTimeline() val joinedRoom = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ), liveTimeline = liveTimeline, typingNoticeResult = { Result.success(Unit) }, @@ -511,6 +509,7 @@ class MessagesPresenterTest { val redactEventLambda = lambdaRecorder { _: EventOrTransactionId, _: String? -> Result.success(Unit) } liveTimeline.redactEventLambda = redactEventLambda val presenter = createMessagesPresenter( + timeline = liveTimeline, joinedRoom = joinedRoom, coroutineDispatchers = coroutineDispatchers, ) @@ -569,11 +568,7 @@ class MessagesPresenterTest { fun `present - shows prompt to reinvite users in DM`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ).apply { givenRoomInfo(aRoomInfo(isDirect = true, joinedMembersCount = 1, activeMembersCount = 1)) }, @@ -602,11 +597,7 @@ class MessagesPresenterTest { fun `present - doesn't show reinvite prompt in non-direct room`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ).apply { givenRoomInfo(aRoomInfo(isDirect = false, joinedMembersCount = 1, activeMembersCount = 1)) }, @@ -628,11 +619,7 @@ class MessagesPresenterTest { fun `present - doesn't show reinvite prompt if other party is present`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ).apply { givenRoomInfo(aRoomInfo(isDirect = true, joinedMembersCount = 2, activeMembersCount = 2)) }, @@ -655,11 +642,7 @@ class MessagesPresenterTest { val inviteUserResult = lambdaRecorder { _: UserId -> Result.success(Unit) } val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ), typingNoticeResult = { Result.success(Unit) }, inviteUserResult = inviteUserResult, @@ -690,11 +673,7 @@ class MessagesPresenterTest { val inviteUserResult = lambdaRecorder { _: UserId -> Result.success(Unit) } val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ), typingNoticeResult = { Result.success(Unit) }, inviteUserResult = inviteUserResult, @@ -727,11 +706,7 @@ class MessagesPresenterTest { fun `present - handle reinviting other user when memberlist is not ready`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ), typingNoticeResult = { Result.success(Unit) }, ) @@ -752,11 +727,7 @@ class MessagesPresenterTest { fun `present - handle reinviting other user when inviting fails`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ), typingNoticeResult = { Result.success(Unit) }, inviteUserResult = { Result.failure(RuntimeException("Oops!")) }, @@ -790,17 +761,7 @@ class MessagesPresenterTest { fun `present - permission to post`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, - canUserSendMessageResult = { _, messageEventType -> - when (messageEventType) { - MessageEventType.RoomMessage -> Result.success(true) - MessageEventType.Reaction -> Result.success(true) - else -> lambdaError() - } - }, + roomPermissions = roomPermissions(), ), typingNoticeResult = { Result.success(Unit) }, ) @@ -816,17 +777,9 @@ class MessagesPresenterTest { fun `present - no permission to post`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, - canUserSendMessageResult = { _, messageEventType -> - when (messageEventType) { - MessageEventType.RoomMessage -> Result.success(false) - MessageEventType.Reaction -> Result.success(false) - else -> lambdaError() - } - }, + roomPermissions = roomPermissions( + canSendMessage = false + ), ), typingNoticeResult = { Result.success(Unit) }, ) @@ -842,11 +795,9 @@ class MessagesPresenterTest { fun `present - permission to redact own`() = runTest { val joinedRoom = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canRedactOwnResult = { Result.success(true) }, - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOtherResult = { Result.success(false) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions( + canRedactOther = false + ), ), typingNoticeResult = { Result.success(Unit) }, ) @@ -863,11 +814,9 @@ class MessagesPresenterTest { fun `present - permission to redact other`() = runTest { val joinedRoom = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canRedactOtherResult = { Result.success(true) }, - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(false) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions( + canRedactOwn = false + ), ), typingNoticeResult = { Result.success(Unit) }, ) @@ -882,7 +831,7 @@ class MessagesPresenterTest { @Test fun `present - handle action reply to a poll`() = runTest { - val composerRecorder = EventsRecorder() + val composerRecorder = EventsRecorder() val presenter = createMessagesPresenter( messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) }, ) @@ -894,7 +843,7 @@ class MessagesPresenterTest { initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, poll)) skipItems(1) composerRecorder.assertSingle( - MessageComposerEvents.SetMode( + MessageComposerEvent.SetMode( composerMode = MessageComposerMode.Reply( replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID), hideImage = false, @@ -912,16 +861,13 @@ class MessagesPresenterTest { val timeline = FakeTimeline() val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ), liveTimeline = timeline, typingNoticeResult = { Result.success(Unit) }, ) val presenter = createMessagesPresenter( + timeline = timeline, joinedRoom = room, analyticsService = analyticsService, ) @@ -955,16 +901,16 @@ class MessagesPresenterTest { val analyticsService = FakeAnalyticsService() val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ), liveTimeline = timeline, typingNoticeResult = { Result.success(Unit) }, ) - val presenter = createMessagesPresenter(joinedRoom = room, analyticsService = analyticsService) + val presenter = createMessagesPresenter( + timeline = timeline, + joinedRoom = room, + analyticsService = analyticsService + ) presenter.testWithLifecycleOwner { val messageEvent = aMessageEvent( content = aTimelineItemTextContent() @@ -994,7 +940,7 @@ class MessagesPresenterTest { caption = A_CAPTION, ) ) - val composerRecorder = EventsRecorder() + val composerRecorder = EventsRecorder() val presenter = createMessagesPresenter( messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) }, ) @@ -1003,7 +949,7 @@ class MessagesPresenterTest { initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.EditCaption, messageEvent)) awaitItem() composerRecorder.assertSingle( - MessageComposerEvents.SetMode( + MessageComposerEvent.SetMode( composerMode = MessageComposerMode.EditCaption( eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(), content = A_CAPTION, @@ -1015,7 +961,7 @@ class MessagesPresenterTest { @Test fun `present - handle action add caption`() = runTest { - val composerRecorder = EventsRecorder() + val composerRecorder = EventsRecorder() val presenter = createMessagesPresenter( messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) }, ) @@ -1029,7 +975,7 @@ class MessagesPresenterTest { initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.AddCaption, messageEvent)) awaitItem() composerRecorder.assertSingle( - MessageComposerEvents.SetMode( + MessageComposerEvent.SetMode( composerMode = MessageComposerMode.EditCaption( eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(), content = "", @@ -1052,11 +998,7 @@ class MessagesPresenterTest { } val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ), liveTimeline = timeline, typingNoticeResult = { Result.success(Unit) }, @@ -1093,11 +1035,7 @@ class MessagesPresenterTest { val successorReason = "This room has been moved to a new location" val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), initialRoomInfo = aRoomInfo( successorRoom = SuccessorRoom( roomId = successorRoomId, @@ -1121,11 +1059,7 @@ class MessagesPresenterTest { fun `present - room without successor room has null successor info in state`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), initialRoomInfo = aRoomInfo(successorRoom = null) ), typingNoticeResult = { Result.success(Unit) }, @@ -1143,11 +1077,13 @@ class MessagesPresenterTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( sessionId = A_SESSION_ID, - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = FakeRoomPermissions( + canSendState = { true }, + canSendMessage = { true }, + canRedactOther = true, + canRedactOwn = true, + canPinUnpin = true, + ), initialRoomInfo = aRoomInfo(isDirect = true, isEncrypted = true) ).apply { givenRoomMembersState(RoomMembersState.Ready(persistentListOf(aRoomMember(userId = A_SESSION_ID), aRoomMember(userId = A_USER_ID_2)))) @@ -1181,10 +1117,12 @@ class MessagesPresenterTest { ) presenter.testWithLifecycleOwner { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.HandleAction( - action = TimelineItemAction.ReplyInThread, - event = aMessageEvent(threadInfo = TimelineItemThreadInfo.ThreadResponse(A_THREAD_ID)) - )) + initialState.eventSink( + MessagesEvents.HandleAction( + action = TimelineItemAction.ReplyInThread, + event = aMessageEvent(threadInfo = TimelineItemThreadInfo.ThreadResponse(A_THREAD_ID)) + ) + ) awaitItem() openThreadLambda.assertions().isCalledOnce().with(value(A_THREAD_ID), value(null)) } @@ -1201,14 +1139,16 @@ class MessagesPresenterTest { ) presenter.testWithLifecycleOwner { val initialState = awaitItem() - initialState.eventSink(MessagesEvents.HandleAction( - action = TimelineItemAction.ReplyInThread, - event = aMessageEvent( - // The event id will be used as the thread id instead - eventId = AN_EVENT_ID, - threadInfo = null, + initialState.eventSink( + MessagesEvents.HandleAction( + action = TimelineItemAction.ReplyInThread, + event = aMessageEvent( + // The event id will be used as the thread id instead + eventId = AN_EVENT_ID, + threadInfo = null, + ) ) - )) + ) awaitItem() openThreadLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID.toThreadId()), value(null)) } @@ -1216,7 +1156,7 @@ class MessagesPresenterTest { @Test fun `present - handle action reply in a thread with threads disabled`() = runTest { - val composerRecorder = EventsRecorder() + val composerRecorder = EventsRecorder() val presenter = createMessagesPresenter( featureFlagService = FakeFeatureFlagService( initialState = mapOf(FeatureFlags.Threads.key to false) @@ -1228,7 +1168,7 @@ class MessagesPresenterTest { initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.ReplyInThread, aMessageEvent())) awaitItem() composerRecorder.assertSingle( - MessageComposerEvents.SetMode( + MessageComposerEvent.SetMode( composerMode = MessageComposerMode.Reply( replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID), hideImage = false, @@ -1238,22 +1178,98 @@ class MessagesPresenterTest { } } + @Test + fun `present - handle MarkAsFullyReadAndExit marks the room as fully read and navigates up`() = runTest { + val markAsFullyReadRecorder = lambdaRecorder { _, _ -> } + val markAsFullyReadUseCase = FakeMarkAsFullyRead(markAsFullyReadRecorder) + val closeLambda = lambdaRecorder {} + val navigator = FakeMessagesNavigator(closeLambda = closeLambda) + + val presenter = createMessagesPresenter( + timeline = FakeTimeline(getLatestEventIdResult = { Result.success(AN_EVENT_ID) }), + markAsFullyRead = markAsFullyReadUseCase, + navigator = navigator, + ) + presenter.testWithLifecycleOwner { + val initialState = awaitItem() + initialState.eventSink(MessagesEvents.MarkAsFullyReadAndExit) + + runCurrent() + + markAsFullyReadRecorder.assertions().isCalledOnce() + closeLambda.assertions().isCalledOnce() + + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - handle MarkAsFullyReadAndExit still navigates up if marking as read fails`() = runTest { + val markAsFullyReadUseCase = FakeMarkAsFullyRead { _, _ -> error("boom") } + val closeLambda = lambdaRecorder {} + val navigator = FakeMessagesNavigator(closeLambda = closeLambda) + + val presenter = createMessagesPresenter( + timeline = FakeTimeline(getLatestEventIdResult = { Result.success(AN_EVENT_ID) }), + markAsFullyRead = markAsFullyReadUseCase, + navigator = navigator, + ) + presenter.testWithLifecycleOwner { + val initialState = awaitItem() + initialState.eventSink(MessagesEvents.MarkAsFullyReadAndExit) + + runCurrent() + + closeLambda.assertions().isCalledOnce() + + cancelAndIgnoreRemainingEvents() + } + } + + private fun roomPermissions( + canStartCall: Boolean = true, + canRedactOther: Boolean = true, + canRedactOwn: Boolean = true, + canSendMessage: Boolean = true, + canSendReaction: Boolean = true, + canPinUnpin: Boolean = true, + ) = FakeRoomPermissions( + canSendState = { type -> + when (type) { + StateEventType.CallMember -> canStartCall + else -> lambdaError() + } + }, + canSendMessage = { type -> + when (type) { + MessageEventType.RoomMessage -> canSendMessage + MessageEventType.Reaction -> canSendReaction + else -> lambdaError() + } + }, + canRedactOther = canRedactOther, + canRedactOwn = canRedactOwn, + canPinUnpin = canPinUnpin, + ) + private fun TestScope.createMessagesPresenter( coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(), + timeline: Timeline = FakeTimeline(), joinedRoom: FakeJoinedRoom = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = FakeRoomPermissions( + canSendState = { true }, + canSendMessage = { true }, + canRedactOther = true, + canRedactOwn = true, + canPinUnpin = true, + ), ).apply { givenRoomInfo(aRoomInfo(id = roomId, name = "")) }, - liveTimeline = FakeTimeline(), + liveTimeline = timeline, typingNoticeResult = { Result.success(Unit) }, ), - timeline: Timeline = joinedRoom.liveTimeline, navigator: FakeMessagesNavigator = FakeMessagesNavigator(), clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(), analyticsService: FakeAnalyticsService = FakeAnalyticsService(), @@ -1271,36 +1287,39 @@ class MessagesPresenterTest { encryptionService: FakeEncryptionService = FakeEncryptionService(), featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(), actionListEventSink: (ActionListEvents) -> Unit = {}, - addRecentEmoji: AddRecentEmoji = AddRecentEmoji(FakeMatrixClient(), testCoroutineDispatchers()), + addRecentEmoji: AddRecentEmoji = AddRecentEmoji { _ -> lambdaError() }, + markAsFullyRead: MarkAsFullyRead = FakeMarkAsFullyRead(), ): MessagesPresenter { return MessagesPresenter( + navigator = navigator, room = joinedRoom, composerPresenter = messageComposerPresenter, voiceMessageComposerPresenterFactory = FakeDefaultVoiceMessageComposerPresenterFactory(backgroundScope), timelinePresenter = { aTimelineState(eventSink = timelineEventSink) }, timelineProtectionPresenter = { aTimelineProtectionState() }, + identityChangeStatePresenter = { anIdentityChangeState() }, + historyVisibleStatePresenter = { aHistoryVisibleState() }, + linkPresenter = { aLinkState() }, actionListPresenter = { anActionListState(eventSink = actionListEventSink) }, customReactionPresenter = { aCustomReactionState() }, reactionSummaryPresenter = { aReactionSummaryState() }, readReceiptBottomSheetPresenter = { aReadReceiptBottomSheetState() }, - identityChangeStatePresenter = { anIdentityChangeState() }, - linkPresenter = { aLinkState() }, pinnedMessagesBannerPresenter = { aLoadedPinnedMessagesBannerState() }, roomCallStatePresenter = { aStandByCallState() }, roomMemberModerationPresenter = roomMemberModerationPresenter, - syncService = FakeSyncService(), snackbarDispatcher = SnackbarDispatcher(), - navigator = navigator, - clipboardHelper = clipboardHelper, - buildMeta = aBuildMeta(), dispatchers = coroutineDispatchers, + clipboardHelper = clipboardHelper, htmlConverterProvider = FakeHtmlConverterProvider(), + buildMeta = aBuildMeta(), timelineController = TimelineController(joinedRoom, timeline), permalinkParser = permalinkParser, - encryptionService = encryptionService, analyticsService = analyticsService, + encryptionService = encryptionService, featureFlagService = featureFlagService, addRecentEmoji = addRecentEmoji, + markAsFullyRead = markAsFullyRead, + sessionCoroutineScope = backgroundScope, ) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt index 85027eb75e..23665a043c 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt index 52118a400d..7c61d2b8b2 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -42,9 +43,11 @@ import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore +import io.element.android.libraries.recentemojis.api.GetRecentEmojis import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.test import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -54,6 +57,8 @@ class ActionListPresenterTest { @get:Rule val warmUpRule = WarmUpRule() + private val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏") + @Test fun `present - initial state`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = true) @@ -95,7 +100,7 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.ViewSource, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -137,7 +142,7 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.ViewSource, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -185,7 +190,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -232,7 +237,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -279,7 +284,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -328,7 +333,7 @@ class ActionListPresenterTest { TimelineItemAction.ReportContent, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -377,7 +382,7 @@ class ActionListPresenterTest { TimelineItemAction.ReportContent, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -425,7 +430,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -472,7 +477,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -519,7 +524,7 @@ class ActionListPresenterTest { TimelineItemAction.CopyText, TimelineItemAction.ViewSource, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -563,7 +568,7 @@ class ActionListPresenterTest { TimelineItemAction.CopyText, TimelineItemAction.ViewSource, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -611,7 +616,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -663,7 +668,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -713,7 +718,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -754,7 +759,7 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.ViewSource, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -828,7 +833,7 @@ class ActionListPresenterTest { TimelineItemAction.CopyText, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -875,7 +880,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -929,7 +934,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -1023,7 +1028,7 @@ class ActionListPresenterTest { TimelineItemAction.CopyText, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) } @@ -1068,7 +1073,7 @@ class ActionListPresenterTest { TimelineItemAction.Pin, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) } @@ -1112,7 +1117,7 @@ class ActionListPresenterTest { TimelineItemAction.Pin, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) } @@ -1155,7 +1160,7 @@ class ActionListPresenterTest { TimelineItemAction.Pin, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) } @@ -1201,7 +1206,7 @@ class ActionListPresenterTest { TimelineItemAction.Pin, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) } @@ -1239,7 +1244,7 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.ViewSource ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) } @@ -1317,7 +1322,7 @@ class ActionListPresenterTest { TimelineItemAction.Pin, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) } @@ -1371,7 +1376,7 @@ class ActionListPresenterTest { TimelineItemAction.Pin, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) } @@ -1426,7 +1431,7 @@ class ActionListPresenterTest { TimelineItemAction.Pin, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) } @@ -1478,11 +1483,56 @@ class ActionListPresenterTest { TimelineItemAction.Reply, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) } } + + @Test + fun `present - recentEmojis merges suggested and recent emojis`() = runTest { + val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏") + val otherEmojis = (0..100).map { it.toString() } + + val presenter = createActionListPresenter( + isDeveloperModeEnabled = false, + recentEmojis = GetRecentEmojis { Result.success((listOf("👍️", ":)", "❤️") + otherEmojis).toImmutableList()) }, + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val messageEvent = aMessageEvent( + eventId = null, + transactionId = A_TRANSACTION_ID, + isMine = true, + isEditable = false, + content = aTimelineItemVoiceContent( + caption = null, + ), + ) + + initialState.eventSink.invoke( + ActionListEvents.ComputeForMessage( + event = messageEvent, + userEventPermissions = aUserEventPermissions( + canRedactOwn = true, + canRedactOther = false, + canSendMessage = true, + canSendReaction = true, + canPinUnpin = true + ) + ) + ) + val successState = awaitItem() + assertThat(successState.target).isInstanceOf(ActionListState.Target.Success::class.java) + + // Check items are deduplicated between suggested and recent emojis and we take at most 100 items + val expectedEmojis = (suggestedEmojis + persistentListOf(":)") + otherEmojis).take(100) + assertThat((successState.target as ActionListState.Target.Success).recentEmojis) + .isEqualTo(expectedEmojis) + } + } } private fun createActionListPresenter( @@ -1490,6 +1540,7 @@ private fun createActionListPresenter( room: BaseRoom = FakeBaseRoom(), timelineMode: Timeline.Mode = Timeline.Mode.Live, featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(), + recentEmojis: GetRecentEmojis = GetRecentEmojis { Result.success(persistentListOf()) }, ): ActionListPresenter { val preferencesStore = InMemoryAppPreferencesStore(isDeveloperModeEnabled = isDeveloperModeEnabled) return DefaultActionListPresenter( @@ -1500,6 +1551,6 @@ private fun createActionListPresenter( dateFormatter = FakeDateFormatter(), timelineMode = timelineMode, featureFlagService = featureFlagService, - getRecentEmojis = { Result.success(persistentListOf()) }, + getRecentEmojis = recentEmojis, ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparatorTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparatorTest.kt index 834b212803..2209b63521 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparatorTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/model/TimelineItemActionComparatorTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt index 941a81d075..fe462fc988 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -40,8 +41,10 @@ import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig import io.element.android.libraries.mediaupload.api.MediaPreProcessor -import io.element.android.libraries.mediaupload.api.MediaSender +import io.element.android.libraries.mediaupload.api.MediaSenderFactory import io.element.android.libraries.mediaupload.api.MediaUploadInfo +import io.element.android.libraries.mediaupload.impl.DefaultMediaSender +import io.element.android.libraries.mediaupload.test.FakeMediaOptimizationConfigProvider import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.mediaviewer.api.aVideoMediaInfo import io.element.android.libraries.mediaviewer.api.anApkMediaInfo @@ -596,21 +599,20 @@ class AttachmentsPreviewPresenterTest { ) } ), + mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(), ): AttachmentsPreviewPresenter { return AttachmentsPreviewPresenter( attachment = aMediaAttachment(localMedia), onDoneListener = onDoneListener, - mediaSenderFactory = object : MediaSender.Factory { - override fun create(timelineMode: Timeline.Mode): MediaSender { - return MediaSender( - preProcessor = mediaPreProcessor, - room = room, - timelineMode = timelineMode, - mediaOptimizationConfigProvider = { - MediaOptimizationConfig(compressImages = true, videoCompressionPreset = VideoCompressionPreset.STANDARD) - } - ) - } + mediaSenderFactory = MediaSenderFactory { timelineMode -> + DefaultMediaSender( + preProcessor = mediaPreProcessor, + room = room, + timelineMode = timelineMode, + mediaOptimizationConfigProvider = { + MediaOptimizationConfig(compressImages = true, videoCompressionPreset = VideoCompressionPreset.STANDARD) + } + ) }, permalinkBuilder = permalinkBuilder, temporaryUriDeleter = temporaryUriDeleter, @@ -619,6 +621,7 @@ class AttachmentsPreviewPresenterTest { mediaOptimizationSelectorPresenterFactory = mediaOptimizationSelectorPresenterFactory, timelineMode = timelineMode, inReplyToEventId = null, + mediaOptimizationConfigProvider = mediaOptimizationConfigProvider, ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/SendActionStateTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/SendActionStateTest.kt index 7a6dc988a4..208d9cc7c0 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/SendActionStateTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/SendActionStateTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/video/DefaultMediaOptimizationSelectorPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/video/DefaultMediaOptimizationSelectorPresenterTest.kt index d095f0d58a..96cc93ea3d 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/video/DefaultMediaOptimizationSelectorPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/video/DefaultMediaOptimizationSelectorPresenterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -20,14 +21,13 @@ import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.test.AN_EXCEPTION -import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.mediaupload.api.MaxUploadSizeProvider +import io.element.android.libraries.mediaupload.test.FakeMediaOptimizationConfigProvider import io.element.android.libraries.mediaviewer.api.aVideoMediaInfo import io.element.android.libraries.mediaviewer.api.anImageMediaInfo import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.test.viewer.aLocalMedia import io.element.android.libraries.preferences.api.store.VideoCompressionPreset -import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.tests.testutils.WarmUpRule import io.mockk.mockk import kotlinx.coroutines.test.runTest @@ -205,7 +205,7 @@ class DefaultMediaOptimizationSelectorPresenterTest { @Test fun `present - max upload size will default to 100MB if we can't get it`() = runTest { val presenter = createDefaultMediaOptimizationSelectorPresenter( - maxUploadSizeProvider = MaxUploadSizeProvider(FakeMatrixClient(getMaxUploadSizeResult = { Result.failure(AN_EXCEPTION) })) + maxUploadSizeProvider = MaxUploadSizeProvider { Result.failure(AN_EXCEPTION) } ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -232,19 +232,17 @@ class DefaultMediaOptimizationSelectorPresenterTest { private fun createDefaultMediaOptimizationSelectorPresenter( localMedia: LocalMedia = aLocalMedia(mockMediaUrl, aVideoMediaInfo()), - maxUploadSizeProvider: MaxUploadSizeProvider = MaxUploadSizeProvider( - FakeMatrixClient(getMaxUploadSizeResult = { Result.success(1_000L) }), - ), - sessionPreferencesStore: InMemorySessionPreferencesStore = InMemorySessionPreferencesStore(), + maxUploadSizeProvider: MaxUploadSizeProvider = MaxUploadSizeProvider { Result.success(1_000L) }, featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SelectableMediaQuality.key to true)), mediaExtractorFactory: FakeVideoMetadataExtractorFactory = FakeVideoMetadataExtractorFactory(), + mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(), ): DefaultMediaOptimizationSelectorPresenter { return DefaultMediaOptimizationSelectorPresenter( localMedia = localMedia, maxUploadSizeProvider = maxUploadSizeProvider, - sessionPreferencesStore = sessionPreferencesStore, featureFlagService = featureFlagService, mediaExtractorFactory = mediaExtractorFactory, + mediaOptimizationConfigProvider = mediaOptimizationConfigProvider, ) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/FakeHistoryVisibleAcknowledgementRepository.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/FakeHistoryVisibleAcknowledgementRepository.kt new file mode 100644 index 0000000000..faf21720fa --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/FakeHistoryVisibleAcknowledgementRepository.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.messages.impl.crypto.historyvisible + +import io.element.android.libraries.matrix.api.core.RoomId +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow + +class FakeHistoryVisibleAcknowledgementRepository( + private val acknowledgements: MutableMap> = mutableMapOf() +) : HistoryVisibleAcknowledgementRepository { + override fun hasAcknowledged(roomId: RoomId): Flow { + return acknowledgements.getOrPut(roomId) { + MutableStateFlow(false) + } + } + + override suspend fun setAcknowledged(roomId: RoomId, value: Boolean) { + val flow = acknowledgements.getOrPut(roomId) { + MutableStateFlow(value) + } + flow.emit(value) + } + + companion object { + /** + * Create the repository with a pre-existing entry. + */ + fun withRoom(roomId: RoomId, acknowledged: Boolean = false): FakeHistoryVisibleAcknowledgementRepository { + return FakeHistoryVisibleAcknowledgementRepository( + mutableMapOf( + roomId to MutableStateFlow(acknowledged) + ) + ) + } + } +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStatePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStatePresenterTest.kt new file mode 100644 index 0000000000..afa1992cac --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/historyvisible/HistoryVisibleStatePresenterTest.kt @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.messages.impl.crypto.historyvisible + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService +import io.element.android.libraries.matrix.api.room.JoinedRoom +import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility +import io.element.android.libraries.matrix.test.room.FakeJoinedRoom +import io.element.android.libraries.matrix.test.room.aRoomInfo +import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.awaitLastSequentialItem +import io.element.android.tests.testutils.test +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test + +class HistoryVisibleStatePresenterTest { + @get:Rule + val warmUpRule = WarmUpRule() + + @Test + fun `present - not visible if feature disabled`() = runTest { + val room = FakeJoinedRoom() + room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = true)) + val presenter = createHistoryVisibleStatePresenter(room, enabled = false, acknowledged = false) + presenter.test { + assertThat(awaitLastSequentialItem().showAlert).isFalse() + } + } + + @Test + fun `present - initial with room shared, unencrypted`() = runTest { + val room = FakeJoinedRoom() + room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = false)) + val presenter = createHistoryVisibleStatePresenter(room) + presenter.test { + assertThat(awaitLastSequentialItem().showAlert).isFalse() + } + } + + @Test + fun `present - initial with room joined, encrypted`() = runTest { + val room = FakeJoinedRoom() + room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Joined, isEncrypted = true)) + val presenter = createHistoryVisibleStatePresenter(room) + presenter.test { + assertThat(awaitLastSequentialItem().showAlert).isFalse() + } + } + + @Test + fun `present - initial with room invited, encrypted`() = runTest { + val room = FakeJoinedRoom() + room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Invited, isEncrypted = true)) + val presenter = createHistoryVisibleStatePresenter(room) + presenter.test { + assertThat(awaitLastSequentialItem().showAlert).isFalse() + } + } + + @Test + fun `present - initial with room shared, encrypted, unacknowledged`() = runTest { + val room = FakeJoinedRoom() + room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = true)) + val presenter = createHistoryVisibleStatePresenter(room, acknowledged = false) + presenter.test { + val initialState = awaitItem() + assertThat(initialState.showAlert).isFalse() + val nextState = awaitItem() + assertThat(nextState.showAlert).isTrue() + } + } + + @Test + fun `present - initial with room shared, encrypted, acknowledged`() = runTest { + val room = FakeJoinedRoom() + room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = true)) + val presenter = createHistoryVisibleStatePresenter(room, acknowledged = true) + presenter.test { + assertThat(awaitLastSequentialItem().showAlert).isFalse() + } + } + + @Test + fun `present - transition from joined + unencrypted, to shared + encrypted`() = runTest { + val room = FakeJoinedRoom() + val featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.EnableKeyShareOnInvite.key to true)) + val repository = FakeHistoryVisibleAcknowledgementRepository() + + room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Joined, isEncrypted = false)) + + val presenter = HistoryVisibleStatePresenter( + featureFlagService, + repository, + room, + ) + + presenter.test { + // emitted by the feature flag service(?) + assertThat(awaitItem().showAlert).isFalse() + + // emitted state from room info assignment + assertThat(awaitItem().showAlert).isFalse() + + // room is marked as encrypted + room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Joined, isEncrypted = true)) + assertThat(awaitItem().showAlert).isFalse() + + // room history visibility is changed to shared + room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = true)) + assertThat(awaitItem().showAlert).isTrue() + + // alert is acknowledged + repository.setAcknowledged(room.roomId, true) + assertThat(awaitItem().showAlert).isFalse() + } + } + + private fun createHistoryVisibleStatePresenter( + room: JoinedRoom = FakeJoinedRoom(), + enabled: Boolean = true, + acknowledged: Boolean = false + ): HistoryVisibleStatePresenter { + return HistoryVisibleStatePresenter( + room = room, + featureFlagService = FakeFeatureFlagService(mapOf("feature.enableKeyShareOnInvite" to enabled)), + repository = FakeHistoryVisibleAcknowledgementRepository.withRoom(room.roomId, acknowledged) + ) + } +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt index 40da23ae29..6fb193eff5 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt index a96aa6c590..24779ba78a 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStateViewTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailurePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailurePresenterTest.kt index fd2a2fafc2..7f22de9159 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailurePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailurePresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureViewTest.kt index 6a83437758..df1311b6d4 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/draft/FakeComposerDraftService.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/draft/FakeComposerDraftService.kt index d87b11c951..2fd73d5b17 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/draft/FakeComposerDraftService.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/draft/FakeComposerDraftService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/draft/VolatileComposerDraftStoreTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/draft/VolatileComposerDraftStoreTest.kt index 9ec43f3621..5433aff318 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/draft/VolatileComposerDraftStoreTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/draft/VolatileComposerDraftStoreTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MediaAttachmentFixtures.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MediaAttachmentFixtures.kt index b0ce526af4..77207d6b52 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MediaAttachmentFixtures.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MediaAttachmentFixtures.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt index 703b4b0043..00babf4218 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MessageEventFixtures.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt index 539618df9f..c7eb7c0bce 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/DefaultLinkCheckerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/DefaultLinkCheckerTest.kt index 9604290e54..95adf0b5d5 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/DefaultLinkCheckerTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/DefaultLinkCheckerTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/FakeLinkChecker.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/FakeLinkChecker.kt index a50f9109f4..c3bed3a00c 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/FakeLinkChecker.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/FakeLinkChecker.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkPresenterTest.kt index 87ab37e63d..02292cf0ce 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkPresenterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkViewTest.kt index 2936cbb31d..54c95d83c7 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/link/LinkViewTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/DefaultRoomAliasSuggestionsDataSourceTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/DefaultRoomAliasSuggestionsDataSourceTest.kt index e1c22f4624..5cd3607701 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/DefaultRoomAliasSuggestionsDataSourceTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/DefaultRoomAliasSuggestionsDataSourceTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/FakeRoomAliasSuggestionsDataSource.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/FakeRoomAliasSuggestionsDataSource.kt index 18a96c8fac..d33f084ccb 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/FakeRoomAliasSuggestionsDataSource.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/FakeRoomAliasSuggestionsDataSource.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt index 735fdaaabe..2feafbc9e5 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -68,14 +69,16 @@ import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomMember +import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails import io.element.android.libraries.mediapickers.api.PickerProvider import io.element.android.libraries.mediapickers.test.FakePickerProvider import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig import io.element.android.libraries.mediaupload.api.MediaPreProcessor -import io.element.android.libraries.mediaupload.api.MediaSender +import io.element.android.libraries.mediaupload.api.MediaSenderFactory import io.element.android.libraries.mediaupload.api.MediaUploadInfo +import io.element.android.libraries.mediaupload.impl.DefaultMediaSender import io.element.android.libraries.mediaupload.test.FakeMediaOptimizationConfigProvider import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.mediaviewer.test.FakeLocalMediaFactory @@ -153,10 +156,10 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(MessageComposerEvents.ToggleFullScreenState) + initialState.eventSink.invoke(MessageComposerEvent.ToggleFullScreenState) val fullscreenState = awaitItem() assertThat(fullscreenState.isFullScreen).isTrue() - fullscreenState.eventSink.invoke(MessageComposerEvents.ToggleFullScreenState) + fullscreenState.eventSink.invoke(MessageComposerEvent.ToggleFullScreenState) val notFullscreenState = awaitItem() assertThat(notFullscreenState.isFullScreen).isFalse() } @@ -195,7 +198,7 @@ class MessageComposerPresenterTest { }.test { var state = awaitFirstItem() val mode = anEditMode(message = ANOTHER_MESSAGE) - state.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + state.eventSink.invoke(MessageComposerEvent.SetMode(mode)) state = awaitItem() assertThat(state.mode).isEqualTo(mode) assertThat(state.textEditorState.messageHtml()).isEqualTo(ANOTHER_MESSAGE) @@ -237,7 +240,7 @@ class MessageComposerPresenterTest { }.test { var state = awaitFirstItem() val mode = anEditCaptionMode(caption = A_CAPTION) - state.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + state.eventSink.invoke(MessageComposerEvent.SetMode(mode)) state = awaitItem() assertThat(state.mode).isEqualTo(mode) assertThat(state.textEditorState.messageHtml()).isEqualTo(A_CAPTION) @@ -279,11 +282,11 @@ class MessageComposerPresenterTest { presenter.test { var state = awaitFirstItem() val mode = anEditCaptionMode(caption = A_CAPTION) - state.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + state.eventSink.invoke(MessageComposerEvent.SetMode(mode)) state = awaitItem() assertThat(state.mode).isEqualTo(mode) assertThat(state.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_CAPTION) - state.eventSink.invoke(MessageComposerEvents.SendMessage) + state.eventSink.invoke(MessageComposerEvent.SendMessage) val messageSentState = awaitItem() assertThat(messageSentState.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo("") waitForPredicate { analyticsService.capturedEvents.size == 1 } @@ -320,13 +323,13 @@ class MessageComposerPresenterTest { }.test { var state = awaitFirstItem() val editMode = anEditMode(message = ANOTHER_MESSAGE) - state.eventSink.invoke(MessageComposerEvents.SetMode(editMode)) + state.eventSink.invoke(MessageComposerEvent.SetMode(editMode)) state = awaitItem() assertThat(state.mode).isEqualTo(editMode) assertThat(state.textEditorState.messageHtml()).isEqualTo(ANOTHER_MESSAGE) val replyMode = aReplyMode() - state.eventSink.invoke(MessageComposerEvents.SetMode(replyMode)) + state.eventSink.invoke(MessageComposerEvent.SetMode(replyMode)) state = awaitItem() assertThat(state.mode).isEqualTo(replyMode) assertThat(state.textEditorState.messageHtml()).isEmpty() @@ -349,7 +352,7 @@ class MessageComposerPresenterTest { }.test { var state = awaitFirstItem() val mode = aReplyMode() - state.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + state.eventSink.invoke(MessageComposerEvent.SetMode(mode)) state = awaitItem() assertThat(state.mode).isEqualTo(mode) assertThat(state.textEditorState.messageHtml()).isEqualTo("") @@ -365,7 +368,7 @@ class MessageComposerPresenterTest { }.test { var state = awaitFirstItem() val mode = aReplyMode() - state.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + state.eventSink.invoke(MessageComposerEvent.SetMode(mode)) state = awaitItem() assertThat(state.mode).isEqualTo(mode) state.textEditorState.setHtml(A_REPLY) @@ -394,7 +397,7 @@ class MessageComposerPresenterTest { initialState.textEditorState.setHtml(A_MESSAGE) val withMessageState = awaitItem() assertThat(withMessageState.textEditorState.messageHtml()).isEqualTo(A_MESSAGE) - withMessageState.eventSink.invoke(MessageComposerEvents.SendMessage) + withMessageState.eventSink.invoke(MessageComposerEvent.SendMessage) val messageSentState = awaitItem() assertThat(messageSentState.textEditorState.messageHtml()).isEqualTo("") waitForPredicate { analyticsService.capturedEvents.size == 1 } @@ -431,7 +434,7 @@ class MessageComposerPresenterTest { val withMessageState = awaitItem() assertThat(withMessageState.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_MESSAGE) assertThat(withMessageState.textEditorState.messageHtml()).isNull() - withMessageState.eventSink.invoke(MessageComposerEvents.SendMessage) + withMessageState.eventSink.invoke(MessageComposerEvent.SendMessage) val messageSentState = awaitItem() assertThat(messageSentState.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo("") waitForPredicate { analyticsService.capturedEvents.size == 1 } @@ -468,14 +471,14 @@ class MessageComposerPresenterTest { val initialState = awaitFirstItem() assertThat(initialState.textEditorState.messageHtml()).isEqualTo("") val mode = anEditMode() - initialState.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + initialState.eventSink.invoke(MessageComposerEvent.SetMode(mode)) val withMessageState = awaitItem() assertThat(withMessageState.mode).isEqualTo(mode) assertThat(withMessageState.textEditorState.messageHtml()).isEqualTo(A_MESSAGE) withMessageState.textEditorState.setHtml(ANOTHER_MESSAGE) val withEditedMessageState = awaitItem() assertThat(withEditedMessageState.textEditorState.messageHtml()).isEqualTo(ANOTHER_MESSAGE) - withEditedMessageState.eventSink.invoke(MessageComposerEvents.SendMessage) + withEditedMessageState.eventSink.invoke(MessageComposerEvent.SendMessage) skipItems(1) val messageSentState = awaitItem() assertThat(messageSentState.textEditorState.messageHtml()).isEqualTo("") @@ -523,14 +526,14 @@ class MessageComposerPresenterTest { val initialState = awaitFirstItem() assertThat(initialState.textEditorState.messageHtml()).isEqualTo("") val mode = anEditMode() - initialState.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + initialState.eventSink.invoke(MessageComposerEvent.SetMode(mode)) val withMessageState = awaitItem() assertThat(withMessageState.mode).isEqualTo(mode) assertThat(withMessageState.textEditorState.messageHtml()).isEqualTo(A_MESSAGE) withMessageState.textEditorState.setHtml(ANOTHER_MESSAGE) val withEditedMessageState = awaitItem() assertThat(withEditedMessageState.textEditorState.messageHtml()).isEqualTo(ANOTHER_MESSAGE) - withEditedMessageState.eventSink.invoke(MessageComposerEvents.SendMessage) + withEditedMessageState.eventSink.invoke(MessageComposerEvent.SendMessage) skipItems(1) val messageSentState = awaitItem() assertThat(messageSentState.textEditorState.messageHtml()).isEqualTo("") @@ -578,14 +581,14 @@ class MessageComposerPresenterTest { val initialState = awaitFirstItem() assertThat(initialState.textEditorState.messageHtml()).isEqualTo("") val mode = anEditMode(eventOrTransactionId = A_TRANSACTION_ID.toEventOrTransactionId()) - initialState.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + initialState.eventSink.invoke(MessageComposerEvent.SetMode(mode)) val withMessageState = awaitItem() assertThat(withMessageState.mode).isEqualTo(mode) assertThat(withMessageState.textEditorState.messageHtml()).isEqualTo(A_MESSAGE) withMessageState.textEditorState.setHtml(ANOTHER_MESSAGE) val withEditedMessageState = awaitItem() assertThat(withEditedMessageState.textEditorState.messageHtml()).isEqualTo(ANOTHER_MESSAGE) - withEditedMessageState.eventSink.invoke(MessageComposerEvents.SendMessage) + withEditedMessageState.eventSink.invoke(MessageComposerEvent.SendMessage) skipItems(1) val messageSentState = awaitItem() assertThat(messageSentState.textEditorState.messageHtml()).isEqualTo("") @@ -628,13 +631,13 @@ class MessageComposerPresenterTest { val initialState = awaitFirstItem() assertThat(initialState.textEditorState.messageHtml()).isEqualTo("") val mode = aReplyMode() - initialState.eventSink.invoke(MessageComposerEvents.SetMode(mode)) + initialState.eventSink.invoke(MessageComposerEvent.SetMode(mode)) val state = awaitItem() assertThat(state.mode).isEqualTo(mode) assertThat(state.textEditorState.messageHtml()).isEqualTo("") state.textEditorState.setHtml(A_REPLY) assertThat(state.textEditorState.messageHtml()).isEqualTo(A_REPLY) - state.eventSink.invoke(MessageComposerEvents.SendMessage) + state.eventSink.invoke(MessageComposerEvent.SendMessage) val messageSentState = awaitItem() assertThat(messageSentState.textEditorState.messageHtml()).isEqualTo("") @@ -663,7 +666,7 @@ class MessageComposerPresenterTest { }.test { val initialState = awaitFirstItem() assertThat(initialState.showAttachmentSourcePicker).isFalse() - initialState.eventSink(MessageComposerEvents.AddAttachment) + initialState.eventSink(MessageComposerEvent.AddAttachment) assertThat(awaitItem().showAttachmentSourcePicker).isTrue() } } @@ -675,10 +678,10 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.AddAttachment) + initialState.eventSink(MessageComposerEvent.AddAttachment) skipItems(1) - initialState.eventSink(MessageComposerEvents.DismissAttachmentMenu) + initialState.eventSink(MessageComposerEvent.DismissAttachmentMenu) assertThat(awaitItem().showAttachmentSourcePicker).isFalse() } } @@ -718,7 +721,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromGallery) onPreviewAttachmentLambda.assertions().isCalledOnce() } } @@ -759,7 +762,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromGallery) onPreviewAttachmentLambda.assertions().isCalledOnce() } } @@ -775,7 +778,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromGallery) // No crashes here, otherwise it fails } } @@ -797,7 +800,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromFiles) onPreviewAttachmentLambda.assertions().isCalledOnce() } } @@ -812,10 +815,10 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.AddAttachment) + initialState.eventSink(MessageComposerEvent.AddAttachment) val attachmentOpenState = awaitItem() assertThat(attachmentOpenState.showAttachmentSourcePicker).isTrue() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.Poll) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.Poll) val finalState = awaitItem() assertThat(finalState.showAttachmentSourcePicker).isFalse() } @@ -831,10 +834,10 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.AddAttachment) + initialState.eventSink(MessageComposerEvent.AddAttachment) val attachmentOpenState = awaitItem() assertThat(attachmentOpenState.showAttachmentSourcePicker).isTrue() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.Location) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.Location) val finalState = awaitItem() assertThat(finalState.showAttachmentSourcePicker).isFalse() } @@ -859,7 +862,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.PhotoFromCamera) onPreviewAttachmentLambda.assertions().isCalledOnce() } } @@ -883,7 +886,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.PhotoFromCamera) permissionPresenter.setPermissionGranted() onPreviewAttachmentLambda.assertions().isCalledOnce() cancelAndIgnoreRemainingEvents() @@ -909,7 +912,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.VideoFromCamera) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.VideoFromCamera) onPreviewAttachmentLambda.assertions().isCalledOnce() } } @@ -933,7 +936,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.PickAttachmentSource.VideoFromCamera) + initialState.eventSink(MessageComposerEvent.PickAttachmentSource.VideoFromCamera) val permissionState = awaitItem() assertThat(permissionState.showAttachmentSourcePicker).isFalse() permissionPresenter.setPermissionGranted() @@ -950,7 +953,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink(MessageComposerEvents.Error(testException)) + initialState.eventSink(MessageComposerEvent.Error(testException)) assertThat(analyticsService.trackedErrors).containsExactly(testException) } } @@ -963,10 +966,10 @@ class MessageComposerPresenterTest { }.test { val initialState = awaitFirstItem() assertThat(initialState.showTextFormatting).isFalse() - initialState.eventSink(MessageComposerEvents.AddAttachment) + initialState.eventSink(MessageComposerEvent.AddAttachment) val composerOptions = awaitItem() assertThat(composerOptions.showAttachmentSourcePicker).isTrue() - composerOptions.eventSink(MessageComposerEvents.ToggleTextFormatting(true)) + composerOptions.eventSink(MessageComposerEvent.ToggleTextFormatting(true)) skipItems(2) // composer options closed val showTextFormatting = awaitItem() assertThat(showTextFormatting.showAttachmentSourcePicker).isFalse() @@ -975,7 +978,7 @@ class MessageComposerPresenterTest { Interaction(index = null, interactionType = null, name = Interaction.Name.MobileRoomComposerFormattingEnabled) ) analyticsService.capturedEvents.clear() - showTextFormatting.eventSink(MessageComposerEvents.ToggleTextFormatting(false)) + showTextFormatting.eventSink(MessageComposerEvent.ToggleTextFormatting(false)) skipItems(1) val finished = awaitItem() assertThat(finished.showTextFormatting).isFalse() @@ -989,9 +992,12 @@ class MessageComposerPresenterTest { val invitedUser = aRoomMember(userId = A_USER_ID_3, membership = RoomMembershipState.INVITE) val bob = aRoomMember(userId = A_USER_ID_2, membership = RoomMembershipState.JOIN) val david = aRoomMember(userId = A_USER_ID_4, displayName = "Dave", membership = RoomMembershipState.JOIN) - var canUserTriggerRoomNotificationResult = true val room = FakeJoinedRoom( - baseRoom = FakeBaseRoom(canUserTriggerRoomNotificationResult = { Result.success(canUserTriggerRoomNotificationResult) }), + baseRoom = FakeBaseRoom( + roomPermissions = FakeRoomPermissions( + canTriggerRoomNotification = true, + ) + ), typingNoticeResult = { Result.success(Unit) } ).apply { givenRoomMembersState( @@ -1008,33 +1014,61 @@ class MessageComposerPresenterTest { val initialState = awaitItem() // A null suggestion (no suggestion was received) returns nothing - initialState.eventSink(MessageComposerEvents.SuggestionReceived(null)) + initialState.eventSink(MessageComposerEvent.SuggestionReceived(null)) assertThat(awaitItem().suggestions).isEmpty() // An empty suggestion returns the room and joined members that are not the current user - initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) + initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) assertThat(awaitItem().suggestions) .containsExactly(ResolvedSuggestion.AtRoom, ResolvedSuggestion.Member(bob), ResolvedSuggestion.Member(david)) // A suggestion containing a part of "room" will also return the room mention - initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "roo"))) + initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "roo"))) assertThat(awaitItem().suggestions).containsExactly(ResolvedSuggestion.AtRoom) // A non-empty suggestion will return those joined members whose user id matches it - initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "bob"))) + initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "bob"))) assertThat(awaitItem().suggestions).containsExactly(ResolvedSuggestion.Member(bob)) // A non-empty suggestion will return those joined members whose display name matches it - initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "dave"))) + initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "dave"))) assertThat(awaitItem().suggestions).containsExactly(ResolvedSuggestion.Member(david)) // If the suggestion isn't a mention, no suggestions are returned - initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Command, ""))) + initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Command, ""))) assertThat(awaitItem().suggestions).isEmpty() + } + } - // If user has no permission to send `@room` mentions, `RoomMemberSuggestion.Room` is not returned - canUserTriggerRoomNotificationResult = false - initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) + @Test + fun `present - room mention suggestions no permission`() = runTest { + val currentUser = aRoomMember(userId = A_USER_ID, membership = RoomMembershipState.JOIN) + val invitedUser = aRoomMember(userId = A_USER_ID_3, membership = RoomMembershipState.INVITE) + val bob = aRoomMember(userId = A_USER_ID_2, membership = RoomMembershipState.JOIN) + val david = aRoomMember(userId = A_USER_ID_4, displayName = "Dave", membership = RoomMembershipState.JOIN) + val room = FakeJoinedRoom( + baseRoom = FakeBaseRoom( + roomPermissions = FakeRoomPermissions( + canTriggerRoomNotification = false, + ) + ), + typingNoticeResult = { Result.success(Unit) } + ).apply { + givenRoomMembersState( + RoomMembersState.Ready( + persistentListOf(currentUser, invitedUser, bob, david), + ) + ) + givenRoomInfo(aRoomInfo(isDirect = false)) + } + val presenter = createPresenter(room) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + // An empty suggestion returns the joined members that are not the current user, but not the room + initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) + skipItems(1) assertThat(awaitItem().suggestions) .containsExactly(ResolvedSuggestion.Member(bob), ResolvedSuggestion.Member(david)) } @@ -1047,7 +1081,9 @@ class MessageComposerPresenterTest { val bob = aRoomMember(userId = A_USER_ID_2, membership = RoomMembershipState.JOIN) val david = aRoomMember(userId = A_USER_ID_4, displayName = "Dave", membership = RoomMembershipState.JOIN) val room = FakeJoinedRoom( - baseRoom = FakeBaseRoom(canUserTriggerRoomNotificationResult = { Result.success(true) }), + baseRoom = FakeBaseRoom( + roomPermissions = FakeRoomPermissions(canTriggerRoomNotification = true), + ), typingNoticeResult = { Result.success(Unit) } ).apply { givenRoomMembersState( @@ -1067,9 +1103,8 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitItem() - // An empty suggestion returns the joined members that are not the current user, but not the room - initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) + initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) skipItems(1) assertThat(awaitItem().suggestions) .containsExactly(ResolvedSuggestion.Member(bob), ResolvedSuggestion.Member(david)) @@ -1089,7 +1124,7 @@ class MessageComposerPresenterTest { }.test { val initialState = awaitFirstItem() initialState.textEditorState.setHtml("Hey @bo") - initialState.eventSink(MessageComposerEvents.InsertSuggestion(ResolvedSuggestion.Member(aRoomMember(userId = A_USER_ID_2)))) + initialState.eventSink(MessageComposerEvent.InsertSuggestion(ResolvedSuggestion.Member(aRoomMember(userId = A_USER_ID_2)))) assertThat(initialState.textEditorState.messageHtml()) .isEqualTo("Hey ${A_USER_ID_2.value}") @@ -1132,7 +1167,7 @@ class MessageComposerPresenterTest { hasAtRoomMention = false ) initialState.textEditorState.setHtml(A_MESSAGE) - initialState.eventSink(MessageComposerEvents.SendMessage) + initialState.eventSink(MessageComposerEvent.SendMessage) advanceUntilIdle() @@ -1140,7 +1175,7 @@ class MessageComposerPresenterTest { .with(value(A_MESSAGE), any(), value(listOf(IntentionalMention.User(A_USER_ID)))) // Check intentional mentions on reply sent - initialState.eventSink(MessageComposerEvents.SetMode(aReplyMode())) + initialState.eventSink(MessageComposerEvent.SetMode(aReplyMode())) val mentionUser2 = listOf(A_USER_ID_2.value) (awaitItem().textEditorState as? TextEditorState.Rich)?.richTextEditorState?.mentionsState = MentionsState( userIds = mentionUser2, @@ -1149,7 +1184,7 @@ class MessageComposerPresenterTest { hasAtRoomMention = false ) - initialState.eventSink(MessageComposerEvents.SendMessage) + initialState.eventSink(MessageComposerEvent.SendMessage) advanceUntilIdle() assert(replyMessageLambda) @@ -1158,7 +1193,7 @@ class MessageComposerPresenterTest { // Check intentional mentions on edit message skipItems(1) - initialState.eventSink(MessageComposerEvents.SetMode(anEditMode())) + initialState.eventSink(MessageComposerEvent.SetMode(anEditMode())) val mentionUser3 = listOf(A_USER_ID_3.value) (awaitItem().textEditorState as? TextEditorState.Rich)?.richTextEditorState?.mentionsState = MentionsState( userIds = mentionUser3, @@ -1167,7 +1202,7 @@ class MessageComposerPresenterTest { hasAtRoomMention = false ) - initialState.eventSink(MessageComposerEvents.SendMessage) + initialState.eventSink(MessageComposerEvent.SendMessage) advanceUntilIdle() assert(editMessageLambda) @@ -1195,7 +1230,7 @@ class MessageComposerPresenterTest { remember(state, state.textEditorState.messageHtml()) { state } }.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(MessageComposerEvents.SendUri(Uri.parse("content://uri"))) + initialState.eventSink.invoke(MessageComposerEvent.SendUri(Uri.parse("content://uri"))) waitForPredicate { mediaPreProcessor.processCallCount == 1 } } } @@ -1212,8 +1247,8 @@ class MessageComposerPresenterTest { }.test { val initialState = awaitFirstItem() typingNoticeResult.assertions().isNeverCalled() - initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(true)) - initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(false)) + initialState.eventSink.invoke(MessageComposerEvent.TypingNotice(true)) + initialState.eventSink.invoke(MessageComposerEvent.TypingNotice(false)) advanceUntilIdle() typingNoticeResult.assertions().isCalledExactly(2) .withSequence( @@ -1238,8 +1273,8 @@ class MessageComposerPresenterTest { }.test { val initialState = awaitFirstItem() typingNoticeResult.assertions().isNeverCalled() - initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(true)) - initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(false)) + initialState.eventSink.invoke(MessageComposerEvent.TypingNotice(true)) + initialState.eventSink.invoke(MessageComposerEvent.TypingNotice(false)) typingNoticeResult.assertions().isNeverCalled() } } @@ -1421,7 +1456,7 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - initialState.eventSink.invoke(MessageComposerEvents.SaveDraft) + initialState.eventSink.invoke(MessageComposerEvent.SaveDraft) advanceUntilIdle() assert(saveDraftLambda) .isCalledOnce() @@ -1451,26 +1486,26 @@ class MessageComposerPresenterTest { val withMessageState = awaitItem() assertThat(withMessageState.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_MESSAGE) - withMessageState.eventSink(MessageComposerEvents.SaveDraft) + withMessageState.eventSink(MessageComposerEvent.SaveDraft) advanceUntilIdle() - withMessageState.eventSink(MessageComposerEvents.ToggleTextFormatting(true)) + withMessageState.eventSink(MessageComposerEvent.ToggleTextFormatting(true)) skipItems(1) val withFormattingState = awaitItem() assertThat(withFormattingState.showTextFormatting).isTrue() - withFormattingState.eventSink(MessageComposerEvents.SaveDraft) + withFormattingState.eventSink(MessageComposerEvent.SaveDraft) advanceUntilIdle() - withFormattingState.eventSink(MessageComposerEvents.SetMode(anEditMode())) + withFormattingState.eventSink(MessageComposerEvent.SetMode(anEditMode())) val withEditModeState = awaitItem() assertThat(withEditModeState.mode).isEqualTo(anEditMode()) - withEditModeState.eventSink(MessageComposerEvents.SaveDraft) + withEditModeState.eventSink(MessageComposerEvent.SaveDraft) advanceUntilIdle() - withEditModeState.eventSink(MessageComposerEvents.SetMode(aReplyMode())) + withEditModeState.eventSink(MessageComposerEvent.SetMode(aReplyMode())) val withReplyModeState = awaitItem() assertThat(withReplyModeState.mode).isEqualTo(aReplyMode()) - withReplyModeState.eventSink(MessageComposerEvents.SaveDraft) + withReplyModeState.eventSink(MessageComposerEvent.SaveDraft) advanceUntilIdle() assert(saveDraftLambda) @@ -1513,7 +1548,7 @@ class MessageComposerPresenterTest { } private suspend fun ReceiveTurbine.backToNormalMode(state: MessageComposerState, skipCount: Int = 0): MessageComposerState { - state.eventSink.invoke(MessageComposerEvents.CloseSpecialMode) + state.eventSink.invoke(MessageComposerEvent.CloseSpecialMode) skipItems(skipCount) val normalState = awaitItem() assertThat(normalState.mode).isEqualTo(MessageComposerMode.Normal) @@ -1550,20 +1585,18 @@ class MessageComposerPresenterTest { mediaPickerProvider = pickerProvider, sessionPreferencesStore = sessionPreferencesStore, localMediaFactory = localMediaFactory, - mediaSenderFactory = object : MediaSender.Factory { - override fun create(timelineMode: Timeline.Mode): MediaSender { - return MediaSender( - preProcessor = mediaPreProcessor, - room = room, - timelineMode = timelineMode, - mediaOptimizationConfigProvider = { - MediaOptimizationConfig( + mediaSenderFactory = MediaSenderFactory { timelineMode -> + DefaultMediaSender( + preProcessor = mediaPreProcessor, + room = room, + timelineMode = timelineMode, + mediaOptimizationConfigProvider = { + MediaOptimizationConfig( compressImages = true, videoCompressionPreset = VideoCompressionPreset.STANDARD ) - } - ) - } + } + ) }, snackbarDispatcher = snackbarDispatcher, analyticsService = analyticsService, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/TestRichTextEditorStateFactory.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/TestRichTextEditorStateFactory.kt index 79615c8802..41acf8b664 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/TestRichTextEditorStateFactory.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/TestRichTextEditorStateFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessorTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessorTest.kt index 6d7b4f8c7c..daba41fb3c 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessorTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/suggestions/SuggestionsProcessorTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagesummary/FakeMessageSummaryFormatter.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagesummary/FakeMessageSummaryFormatter.kt index 06d1cca1c7..681bbf0d79 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagesummary/FakeMessageSummaryFormatter.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagesummary/FakeMessageSummaryFormatter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenterTest.kt index 38182dec1d..d8269f85d8 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenterTest.kt @@ -1,14 +1,15 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl.pinned.banner import com.google.common.truth.Truth.assertThat -import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider +import io.element.android.features.messages.impl.pinned.DefaultPinnedEventsTimelineProvider import io.element.android.libraries.eventformatter.test.FakePinnedMessagesBannerFormatter import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.sync.SyncService @@ -195,7 +196,7 @@ class PinnedMessagesBannerPresenterTest { internal fun TestScope.createPinnedEventsTimelineProvider( room: JoinedRoom = FakeJoinedRoom(), syncService: SyncService = FakeSyncService(), -) = PinnedEventsTimelineProvider( +) = DefaultPinnedEventsTimelineProvider( room = room, syncService = syncService, dispatchers = testCoroutineDispatchers(), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerViewTest.kt index 40af4c4a71..a7bbdf999a 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/FakePinnedMessagesListNavigator.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/FakePinnedMessagesListNavigator.kt index bf0a24dd5a..479139a45b 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/FakePinnedMessagesListNavigator.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/FakePinnedMessagesListNavigator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,17 +13,17 @@ import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugIn class FakePinnedMessagesListNavigator : PinnedMessagesListNavigator { var onViewInTimelineClickLambda: ((EventId) -> Unit)? = null - override fun onViewInTimelineClick(eventId: EventId) { + override fun viewInTimeline(eventId: EventId) { onViewInTimelineClickLambda?.invoke(eventId) } var onShowEventDebugInfoClickLambda: ((EventId?, TimelineItemDebugInfo) -> Unit)? = null - override fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { + override fun navigateToEventDebugInfo(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { onShowEventDebugInfoClickLambda?.invoke(eventId, debugInfo) } var onForwardEventClickLambda: ((EventId) -> Unit)? = null - override fun onForwardEventClick(eventId: EventId) { + override fun forwardEvent(eventId: EventId) { onForwardEventClickLambda?.invoke(eventId) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt index 07778ab381..351f841fcf 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,9 +14,10 @@ import io.element.android.features.messages.impl.actionlist.anActionListState import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryCreator import io.element.android.features.messages.impl.link.aLinkState -import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider +import io.element.android.features.messages.impl.pinned.DefaultPinnedEventsTimelineProvider import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState +import io.element.android.features.messages.test.timeline.FakeHtmlConverterProvider import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.core.EventId @@ -29,6 +31,7 @@ import io.element.android.libraries.matrix.test.A_UNIQUE_ID import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.aRoomInfo +import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.libraries.matrix.test.sync.FakeSyncService import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.matrix.test.timeline.aMessageContent @@ -53,9 +56,7 @@ class PinnedMessagesListPresenterTest { fun `present - initial state`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ).apply { givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID))) } @@ -72,9 +73,7 @@ class PinnedMessagesListPresenterTest { fun `present - timeline failure state`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ).apply { givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID))) }, @@ -93,9 +92,7 @@ class PinnedMessagesListPresenterTest { fun `present - empty state`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ).apply { givenRoomInfo(aRoomInfo(pinnedEventIds = listOf())) }, @@ -115,9 +112,7 @@ class PinnedMessagesListPresenterTest { val pinnedEventsTimeline = createPinnedMessagesTimeline() val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ).apply { givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID))) }, @@ -144,9 +139,7 @@ class PinnedMessagesListPresenterTest { val analyticsService = FakeAnalyticsService() val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ).apply { givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID))) }, @@ -192,9 +185,7 @@ class PinnedMessagesListPresenterTest { val pinnedEventsTimeline = createPinnedMessagesTimeline() val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ).apply { givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID))) }, @@ -223,9 +214,7 @@ class PinnedMessagesListPresenterTest { val pinnedEventsTimeline = createPinnedMessagesTimeline() val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ).apply { givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID))) }, @@ -254,9 +243,7 @@ class PinnedMessagesListPresenterTest { val pinnedEventsTimeline = createPinnedMessagesTimeline() val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canRedactOwnResult = { Result.success(true) }, - canRedactOtherResult = { Result.success(true) }, - canUserPinUnpinResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ).apply { givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID))) }, @@ -293,6 +280,16 @@ class PinnedMessagesListPresenterTest { ) } + private fun roomPermissions( + canRedactOther: Boolean = true, + canRedactOwn: Boolean = true, + canPinUnpin: Boolean = true, + ) = FakeRoomPermissions( + canRedactOther = canRedactOther, + canRedactOwn = canRedactOwn, + canPinUnpin = canPinUnpin, + ) + private fun TestScope.createPinnedMessagesListPresenter( navigator: PinnedMessagesListNavigator = FakePinnedMessagesListNavigator(), room: JoinedRoom = FakeJoinedRoom(), @@ -300,7 +297,7 @@ class PinnedMessagesListPresenterTest { analyticsService: AnalyticsService = FakeAnalyticsService(), featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(), ): PinnedMessagesListPresenter { - val timelineProvider = PinnedEventsTimelineProvider( + val timelineProvider = DefaultPinnedEventsTimelineProvider( room = room, syncService = syncService, dispatchers = testCoroutineDispatchers(), @@ -318,6 +315,7 @@ class PinnedMessagesListPresenterTest { analyticsService = analyticsService, featureFlagService = featureFlagService, sessionCoroutineScope = this, + htmlConverterProvider = FakeHtmlConverterProvider(), ) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListTimelineActionPostProcessorTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListTimelineActionPostProcessorTest.kt index ee728b1436..df79394f2c 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListTimelineActionPostProcessorTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListTimelineActionPostProcessorTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt index 5e9ad97f87..3f96f1dfca 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt index abaa6a5653..fd8d33d1b6 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt index d2d5914674..315d9c459c 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultMarkAsFullyReadTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultMarkAsFullyReadTest.kt index 6340ce56a5..395ed0107f 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultMarkAsFullyReadTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultMarkAsFullyReadTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,12 +10,15 @@ package io.element.android.features.messages.impl.timeline -import io.element.android.libraries.matrix.api.timeline.ReceiptType +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.FakeMatrixClient -import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value +import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -22,34 +26,30 @@ import org.junit.Test class DefaultMarkAsFullyReadTest { @Test - fun `When room is not found, then no exception is thrown`() = runTest { + fun `When marking as read fails, no exception is thrown`() = runTest { val markAsFullyRead = DefaultMarkAsFullyRead( - FakeMatrixClient( - sessionCoroutineScope = backgroundScope, + matrixClient = FakeMatrixClient( + markRoomAsFullyReadResult = { _, _ -> Result.failure(IllegalStateException("Room not found")) }, ).apply { givenGetRoomResult(A_ROOM_ID, null) - } + }, + coroutineDispatchers = testCoroutineDispatchers(), ) - markAsFullyRead.invoke(A_ROOM_ID) + assertThat(markAsFullyRead.invoke(A_ROOM_ID, AN_EVENT_ID).isFailure).isTrue() runCurrent() } @Test - fun `When room is found, the expected method is invoked`() = runTest { - val markAsReadResult = lambdaRecorder> { Result.success(Unit) } - val baseRoom = FakeBaseRoom( - markAsReadResult = markAsReadResult - ) + fun `When marking as read is successful, the expected method is invoked`() = runTest { + val markAsFullyReadResult = lambdaRecorder> { _, _ -> Result.success(Unit) } val markAsFullyRead = DefaultMarkAsFullyRead( - FakeMatrixClient( - sessionCoroutineScope = backgroundScope, - ).apply { - givenGetRoomResult(A_ROOM_ID, baseRoom) - } + matrixClient = FakeMatrixClient( + markRoomAsFullyReadResult = markAsFullyReadResult, + ), + coroutineDispatchers = testCoroutineDispatchers(), ) - markAsFullyRead.invoke(A_ROOM_ID) + assertThat(markAsFullyRead.invoke(A_ROOM_ID, AN_EVENT_ID).isSuccess).isTrue() runCurrent() - markAsReadResult.assertions().isCalledOnce().with(value(ReceiptType.FULLY_READ)) - baseRoom.assertDestroyed() + markAsFullyReadResult.assertions().isCalledOnce().with(value(A_ROOM_ID), value(AN_EVENT_ID)) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/FakeMarkAsFullyRead.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/FakeMarkAsFullyRead.kt index 895676a126..8dca151c2d 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/FakeMarkAsFullyRead.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/FakeMarkAsFullyRead.kt @@ -1,19 +1,22 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl.timeline +import io.element.android.libraries.core.extensions.runCatchingExceptions +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.tests.testutils.lambda.lambdaError class FakeMarkAsFullyRead( - private val invokeResult: (RoomId) -> Unit = { lambdaError() } + private val invokeResult: (RoomId, EventId) -> Unit = { _, _ -> lambdaError() }, ) : MarkAsFullyRead { - override fun invoke(roomId: RoomId) { - invokeResult(roomId) + override suspend fun invoke(roomId: RoomId, eventId: EventId): Result { + return runCatchingExceptions { invokeResult(roomId, eventId) } } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt index 524cb3e1e8..034c952f3d 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexerTest.kt index a922f29028..65a29f5422 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexerTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexerTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt index 8da614f67e..b84975c6a5 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt @@ -1,14 +1,13 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.messages.impl.timeline -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow import app.cash.turbine.ReceiveTurbine import app.cash.turbine.test import com.google.common.truth.Truth.assertThat @@ -34,6 +33,7 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.api.core.asEventId +import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.RoomMembersState import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem @@ -54,16 +54,19 @@ import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.aRoomMember +import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.matrix.test.timeline.aMessageContent import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import io.element.android.libraries.matrix.ui.components.aMatrixUserList import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore +import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.awaitLastSequentialItem import io.element.android.tests.testutils.consumeItemsUntilPredicate import io.element.android.tests.testutils.lambda.any import io.element.android.tests.testutils.lambda.assert +import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.test @@ -95,9 +98,7 @@ class TimelinePresenterTest { @Test fun `present - initial state`() = runTest { val presenter = createTimelinePresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() assertThat(initialState.timelineItems).isEmpty() assertThat(initialState.isLive).isTrue() @@ -116,9 +117,7 @@ class TimelinePresenterTest { this.paginateLambda = paginateLambda } val presenter = createTimelinePresenter(timeline = timeline) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() initialState.eventSink.invoke(TimelineEvents.LoadMore(Timeline.PaginationDirection.BACKWARDS)) initialState.eventSink.invoke(TimelineEvents.LoadMore(Timeline.PaginationDirection.FORWARDS)) @@ -151,20 +150,19 @@ class TimelinePresenterTest { isSendPublicReadReceiptsEnabled: Boolean, expectedReceiptType: ReceiptType, ) = runTest(StandardTestDispatcher()) { + val markAsReadResult = lambdaRecorder> { Result.success(Unit) } + val sendReadReceiptLambda = lambdaRecorder> { _, _ -> Result.success(Unit) } val timeline = FakeTimeline( timelineItems = flowOf( listOf( MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem()) ) - ) + ), + markAsReadResult = markAsReadResult, + sendReadReceiptLambda = sendReadReceiptLambda, ) - val markAsReadResult = lambdaRecorder> { Result.success(Unit) } val room = FakeJoinedRoom( liveTimeline = timeline, - baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - markAsReadResult = markAsReadResult, - ) ) val sessionPreferencesStore = InMemorySessionPreferencesStore(isSendPublicReadReceiptsEnabled = isSendPublicReadReceiptsEnabled) val presenter = createTimelinePresenter( @@ -172,9 +170,7 @@ class TimelinePresenterTest { room = room, sessionPreferencesStore = sessionPreferencesStore, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0)) runCurrent() @@ -185,25 +181,6 @@ class TimelinePresenterTest { } } - @Test - fun `present - once presenter is disposed, room is marked as fully read`() = runTest { - val invokeResult = lambdaRecorder { } - val presenter = createTimelinePresenter( - room = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, - ) - ), - markAsFullyRead = FakeMarkAsFullyRead( - invokeResult = invokeResult, - ) - ) - presenter.test { - awaitFirstItem() - } - invokeResult.assertions().isCalledOnce().with(value(A_ROOM_ID)) - } - @Test fun `present - on scroll finished send read receipt if an event is before the index`() = runTest { val sendReadReceiptsLambda = lambdaRecorder { _: EventId, _: ReceiptType -> @@ -226,9 +203,7 @@ class TimelinePresenterTest { this.sendReadReceiptLambda = sendReadReceiptsLambda } val presenter = createTimelinePresenter(timeline) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) awaitItem().run { eventSink.invoke(TimelineEvents.OnScrollFinished(1)) @@ -258,18 +233,16 @@ class TimelinePresenterTest { ) ) ) - ) - ).apply { - this.sendReadReceiptLambda = sendReadReceiptsLambda - } + ), + markAsReadResult = { Result.success(Unit) }, + sendReadReceiptLambda = sendReadReceiptsLambda, + ) val sessionPreferencesStore = InMemorySessionPreferencesStore(isSendPublicReadReceiptsEnabled = false) val presenter = createTimelinePresenter( timeline = timeline, sessionPreferencesStore = sessionPreferencesStore, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) awaitItem().run { eventSink.invoke(TimelineEvents.OnScrollFinished(0)) @@ -305,9 +278,7 @@ class TimelinePresenterTest { this.sendReadReceiptLambda = sendReadReceiptsLambda } val presenter = createTimelinePresenter(timeline) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) awaitItem().run { eventSink.invoke(TimelineEvents.OnScrollFinished(1)) @@ -335,9 +306,7 @@ class TimelinePresenterTest { this.sendReadReceiptLambda = sendReadReceiptsLambda } val presenter = createTimelinePresenter(timeline) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitFirstItem() initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(1)) @@ -349,11 +318,12 @@ class TimelinePresenterTest { @Test fun `present - covers newEventState scenarios`() = runTest { val timelineItems = MutableStateFlow(emptyList()) - val timeline = FakeTimeline(timelineItems = timelineItems) + val timeline = FakeTimeline( + timelineItems = timelineItems, + markAsReadResult = { Result.success(Unit) }, + ) val presenter = createTimelinePresenter(timeline) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() assertThat(initialState.newEventState).isEqualTo(NewEventState.None) assertThat(initialState.timelineItems.size).isEqualTo(0) @@ -402,9 +372,7 @@ class TimelinePresenterTest { timelineItems = timelineItems, ) val presenter = createTimelinePresenter(timeline) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() assertThat(initialState.newEventState).isEqualTo(NewEventState.None) assertThat(initialState.timelineItems.size).isEqualTo(0) @@ -458,9 +426,7 @@ class TimelinePresenterTest { val presenter = createTimelinePresenter( sendPollResponseAction = sendPollResponseAction, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink.invoke(TimelineEvents.SelectPollAnswer(AN_EVENT_ID, "anAnswerId")) } @@ -474,9 +440,7 @@ class TimelinePresenterTest { val presenter = createTimelinePresenter( endPollAction = endPollAction, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink.invoke(TimelineEvents.EndPoll(AN_EVENT_ID)) } @@ -493,9 +457,7 @@ class TimelinePresenterTest { val presenter = createTimelinePresenter( messagesNavigator = navigator, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { awaitFirstItem().eventSink(TimelineEvents.EditPoll(AN_EVENT_ID)) onEditPollClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID)) } @@ -512,9 +474,7 @@ class TimelinePresenterTest { ), redactedVoiceMessageManager = redactedVoiceMessageManager, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { assertThat(redactedVoiceMessageManager.invocations.size).isEqualTo(0) skipItems(2) assertThat(redactedVoiceMessageManager.invocations.size).isEqualTo(1) @@ -540,16 +500,14 @@ class TimelinePresenterTest { liveTimeline = liveTimeline, createTimelineResult = { Result.success(detachedTimeline) }, baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), threadRootIdForEventResult = { _ -> Result.success(null) }, ), ) val presenter = createTimelinePresenter( room = room, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) awaitItem().also { state -> @@ -577,9 +535,7 @@ class TimelinePresenterTest { @Test fun `present - focus on known event retrieves the event from cache`() = runTest { - val timelineItemIndexer = TimelineItemIndexer().apply { - process(listOf(aMessageEvent(eventId = AN_EVENT_ID))) - } + val timelineItemIndexer = TimelineItemIndexer() val presenter = createTimelinePresenter( room = FakeJoinedRoom( liveTimeline = FakeTimeline( @@ -592,15 +548,25 @@ class TimelinePresenterTest { ) ) ), - baseRoom = FakeBaseRoom(canUserSendMessageResult = { _, _ -> Result.success(true) }), + baseRoom = FakeBaseRoom( + roomPermissions = roomPermissions(), + threadRootIdForEventResult = { Result.success(null) }, + ), ), timelineItemIndexer = timelineItemIndexer, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() + + advanceUntilIdle() + + // Pre-populate the indexer after the first items have been retrieved + timelineItemIndexer.process(listOf(aMessageEvent(eventId = AN_EVENT_ID))) + initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) + + advanceUntilIdle() + awaitItem().also { state -> assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID) assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Requested(AN_EVENT_ID, Duration.ZERO)) @@ -621,14 +587,12 @@ class TimelinePresenterTest { ), createTimelineResult = { Result.failure(RuntimeException("An error")) }, baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), threadRootIdForEventResult = { _ -> Result.success(null) }, ), ) ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) awaitItem().also { state -> @@ -670,7 +634,7 @@ class TimelinePresenterTest { liveTimeline = liveTimeline, createTimelineResult = { Result.success(detachedTimeline) }, baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), threadRootIdForEventResult = { _ -> Result.success(threadId) }, ), ) @@ -681,9 +645,7 @@ class TimelinePresenterTest { timeline = liveTimeline, messagesNavigator = navigator, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) @@ -731,7 +693,7 @@ class TimelinePresenterTest { liveTimeline = liveTimeline, createTimelineResult = { Result.success(detachedTimeline) }, baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), threadRootIdForEventResult = { _ -> Result.success(threadId) }, ), ) @@ -742,9 +704,7 @@ class TimelinePresenterTest { timeline = liveTimeline, messagesNavigator = navigator, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) @@ -787,7 +747,7 @@ class TimelinePresenterTest { liveTimeline = liveTimeline, createTimelineResult = { Result.success(detachedTimeline) }, baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), // Use a different thread id threadRootIdForEventResult = { _ -> Result.success(A_THREAD_ID_2) }, ), @@ -799,9 +759,7 @@ class TimelinePresenterTest { timeline = liveTimeline, messagesNavigator = navigator, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) @@ -848,7 +806,7 @@ class TimelinePresenterTest { liveTimeline = liveTimeline, createTimelineResult = { Result.success(detachedTimeline) }, baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), // The event is in the main timeline, not in a thread threadRootIdForEventResult = { _ -> Result.success(null) }, ), @@ -860,9 +818,7 @@ class TimelinePresenterTest { timeline = liveTimeline, messagesNavigator = navigator, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) @@ -893,9 +849,7 @@ class TimelinePresenterTest { fun `present - show shield hide shield`() = runTest { val presenter = createTimelinePresenter() val shield = aCriticalShield() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() assertThat(initialState.messageShield).isNull() initialState.eventSink(TimelineEvents.ShowShieldDialog(shield)) @@ -931,7 +885,9 @@ class TimelinePresenterTest { ) val room = FakeJoinedRoom( liveTimeline = timeline, - baseRoom = FakeBaseRoom(canUserSendMessageResult = { _, _ -> Result.success(true) }), + baseRoom = FakeBaseRoom( + roomPermissions = roomPermissions(), + ), ).apply { givenRoomMembersState(RoomMembersState.Unknown) } @@ -939,9 +895,7 @@ class TimelinePresenterTest { val avatarUrl = "https://domain.com/avatar.jpg" val presenter = createTimelinePresenter(timeline, room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = consumeItemsUntilPredicate(30.seconds) { it.timelineItems.isNotEmpty() }.last() val event = initialState.timelineItems.first() as TimelineItem.Event assertThat(event.senderAvatar.url).isNull() @@ -965,15 +919,13 @@ class TimelinePresenterTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), predecessorRoomResult = { predecessorRoom } ), ) val presenter = createTimelinePresenter(room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() assertThat(initialState.timelineRoomInfo.predecessorRoom).isNotNull() assertThat(initialState.timelineRoomInfo.predecessorRoom?.roomId).isEqualTo(predecessorRoomId) @@ -984,14 +936,12 @@ class TimelinePresenterTest { fun `present - timeline room info no predecessor`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), predecessorRoomResult = { null } ), ) val presenter = createTimelinePresenter(room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitFirstItem() assertThat(initialState.timelineRoomInfo.predecessorRoom).isNull() } @@ -1001,7 +951,7 @@ class TimelinePresenterTest { fun `present - timeline event navigate to room`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserSendMessageResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), ), ) val onNavigateToRoomLambda = lambdaRecorder, Unit> { _, _, _ -> } @@ -1027,11 +977,32 @@ class TimelinePresenterTest { return awaitItem() } + private fun roomPermissions( + canRedactOther: Boolean = false, + canRedactOwn: Boolean = true, + canSendMessage: Boolean = true, + canSendReaction: Boolean = true, + canPinUnpin: Boolean = false, + ) = FakeRoomPermissions( + canSendMessage = { type -> + when (type) { + MessageEventType.RoomMessage -> canSendMessage + MessageEventType.Reaction -> canSendReaction + else -> lambdaError() + } + }, + canRedactOther = canRedactOther, + canRedactOwn = canRedactOwn, + canPinUnpin = canPinUnpin, + ) + private fun TestScope.createTimelinePresenter( timeline: Timeline = FakeTimeline(), room: FakeJoinedRoom = FakeJoinedRoom( liveTimeline = timeline, - baseRoom = FakeBaseRoom(canUserSendMessageResult = { _, _ -> Result.success(true) }), + baseRoom = FakeBaseRoom( + roomPermissions = roomPermissions(), + ), ), redactedVoiceMessageManager: RedactedVoiceMessageManager = FakeRedactedVoiceMessageManager(), messagesNavigator: FakeMessagesNavigator = FakeMessagesNavigator(), @@ -1039,7 +1010,6 @@ class TimelinePresenterTest { sendPollResponseAction: SendPollResponseAction = FakeSendPollResponseAction(), sessionPreferencesStore: InMemorySessionPreferencesStore = InMemorySessionPreferencesStore(), timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(), - markAsFullyRead: MarkAsFullyRead = FakeMarkAsFullyRead(), featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(), ): TimelinePresenter { return TimelinePresenter( @@ -1057,8 +1027,8 @@ class TimelinePresenterTest { resolveVerifiedUserSendFailurePresenter = { aResolveVerifiedUserSendFailureState() }, typingNotificationPresenter = { aTypingNotificationState() }, roomCallStatePresenter = { aStandByCallState() }, - markAsFullyRead = markAsFullyRead, featureFlagService = featureFlagService, + analyticsService = FakeAnalyticsService(), ) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt index 451f5bddcc..cdc0a6c9d0 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenterTest.kt index e34bbdcbef..a43b4121bb 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,7 +15,9 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.recentemojis.test.FakeEmojibaseProvider import io.element.android.tests.testutils.WarmUpRule +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -25,7 +28,7 @@ class CustomReactionPresenterTest { private val presenter = CustomReactionPresenter( emojibaseProvider = FakeEmojibaseProvider(), - getRecentEmojis = { Result.success(emptyList()) }, + getRecentEmojis = { Result.success(persistentListOf()) }, ) @Test diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/FakeEmojibaseProvider.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/FakeEmojibaseProvider.kt deleted file mode 100644 index 498027bae6..0000000000 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/FakeEmojibaseProvider.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.messages.impl.timeline.components.customreaction - -import io.element.android.emojibasebindings.EmojibaseStore -import kotlinx.collections.immutable.persistentMapOf - -class FakeEmojibaseProvider : EmojibaseProvider { - override val emojibaseStore: EmojibaseStore - get() = EmojibaseStore(persistentMapOf()) -} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerPresenterTest.kt index cf4930b389..aa177b07e2 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/picker/EmojiPickerPresenterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollViewTest.kt index 3935d6d615..f8b416a302 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt index ee52aff578..154225aa7a 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineTextViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenterTest.kt index 115c8a9690..a05b7da5f5 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenterTest.kt index ab083d56f3..d39500fec2 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/receipt/bottomsheet/ReadReceiptBottomSheetPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt index fb13103694..9f23388ecb 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -66,6 +67,7 @@ import io.element.android.libraries.mediaviewer.test.util.FileExtensionExtractor import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.test.runTest +import org.jsoup.nodes.Document import org.junit.Assert.fail import org.junit.Test import org.junit.runner.RunWith @@ -186,7 +188,7 @@ class TimelineItemContentMessageFactoryTest { } }.toSpannable() val sut = createTimelineItemContentMessageFactory( - htmlConverterTransform = { expected } + domConverterTransform = { expected } ) val result = sut.create( content = createMessageContent( @@ -678,7 +680,7 @@ class TimelineItemContentMessageFactoryTest { } }.toSpannable() val sut = createTimelineItemContentMessageFactory( - htmlConverterTransform = { expectedSpanned }, + domConverterTransform = { expectedSpanned }, permalinkParser = FakePermalinkParser { PermalinkData.FallbackLink(Uri.EMPTY) } ) val result = sut.create( @@ -764,11 +766,12 @@ class TimelineItemContentMessageFactoryTest { private fun createTimelineItemContentMessageFactory( htmlConverterTransform: (String) -> CharSequence = { it }, + domConverterTransform: (Document) -> CharSequence = { it.body().html() }, permalinkParser: FakePermalinkParser = FakePermalinkParser(), ) = TimelineItemContentMessageFactory( fileSizeFormatter = FakeFileSizeFormatter(), fileExtensionExtractor = FileExtensionExtractorWithoutValidation(), - htmlConverterProvider = FakeHtmlConverterProvider(htmlConverterTransform), + htmlConverterProvider = FakeHtmlConverterProvider(htmlConverterTransform, domConverterTransform), permalinkParser = permalinkParser, textPillificationHelper = FakeTextPillificationHelper(), ) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt index 1bdc43ebef..2a31d9063e 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/groups/TimelineItemGrouperTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionTest.kt index c7b1d718b2..2cc690a329 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/model/AggregatedReactionTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedViewTest.kt index ec0ddf943d..af3acee6a2 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/ProtectedViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenterTest.kt index 49f30716a8..d6af8ebd68 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionStateTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionStateTest.kt index 978cc1089b..645039b30c 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionStateTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionStateTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt index 5b1f7f8654..896c529ed1 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/DefaultTextPillificationHelperTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/DefaultTextPillificationHelperTest.kt index 0d571a2459..16c8c922ca 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/DefaultTextPillificationHelperTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/DefaultTextPillificationHelperTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/EmojiTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/EmojiTest.kt index 9714f11a1c..5c979dbb7c 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/EmojiTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/EmojiTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/FakeMentionSpanFormatter.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/FakeMentionSpanFormatter.kt index a8128bf622..6ef91ac010 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/FakeMentionSpanFormatter.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/FakeMentionSpanFormatter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/FakeTextPillificationHelper.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/FakeTextPillificationHelper.kt index bbc7c7a728..54faa030b1 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/FakeTextPillificationHelper.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/utils/FakeTextPillificationHelper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/DefaultVoiceMessageComposerPresenterTest.kt similarity index 70% rename from features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt rename to features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/DefaultVoiceMessageComposerPresenterTest.kt index 0d6a96d75d..48348f5e9a 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/DefaultVoiceMessageComposerPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,13 +12,10 @@ package io.element.android.features.messages.impl.voicemessages.composer import android.Manifest import androidx.lifecycle.Lifecycle -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow import app.cash.turbine.TurbineTestContext -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.Composer -import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerEvents +import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerEvent import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerState import io.element.android.features.messages.impl.messagecomposer.aReplyMode import io.element.android.features.messages.test.FakeMessageComposerContext @@ -29,7 +27,7 @@ import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig -import io.element.android.libraries.mediaupload.api.MediaSender +import io.element.android.libraries.mediaupload.impl.DefaultMediaSender import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.api.aPermissionsState @@ -41,10 +39,12 @@ import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent import io.element.android.libraries.textcomposer.model.VoiceMessageState import io.element.android.libraries.voiceplayer.api.VoiceMessageException +import io.element.android.libraries.voicerecorder.api.VoiceRecorder import io.element.android.libraries.voicerecorder.test.FakeVoiceRecorder import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.test import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope @@ -56,7 +56,7 @@ import java.io.File import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds -class VoiceMessageComposerPresenterTest { +class DefaultVoiceMessageComposerPresenterTest { @get:Rule val warmUpRule = WarmUpRule() @@ -74,7 +74,7 @@ class VoiceMessageComposerPresenterTest { }, ) private val mediaPreProcessor = FakeMediaPreProcessor().apply { givenAudioResult() } - private val mediaSender = MediaSender( + private val mediaSender = DefaultMediaSender( preProcessor = mediaPreProcessor, room = joinedRoom, timelineMode = Timeline.Mode.Live, @@ -90,9 +90,7 @@ class VoiceMessageComposerPresenterTest { @Test fun `present - initial state`() = runTest { val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) voiceRecorder.assertCalls(started = 0) @@ -104,10 +102,8 @@ class VoiceMessageComposerPresenterTest { @Test fun `present - recording state`() = runTest { val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + presenter.test { + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE) @@ -117,20 +113,42 @@ class VoiceMessageComposerPresenterTest { } } + @Test + fun `present - recording state - number of levels is limited`() = runTest { + val numberOfLevels = 200 + val levels = List(numberOfLevels) { it / numberOfLevels.toFloat() } + val voiceRecorder = FakeVoiceRecorder( + levels = levels, + recordingDuration = RECORDING_DURATION, + ) + val presenter = createDefaultVoiceMessageComposerPresenter( + voiceRecorder = voiceRecorder, + ) + presenter.test { + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) + skipItems(numberOfLevels / 2 - 1) + val finalState = awaitItem() + assertThat(finalState.voiceMessageState).isInstanceOf(VoiceMessageState.Recording::class.java) + val recordingState = finalState.voiceMessageState as VoiceMessageState.Recording + // The number of levels should be limited to 128 items + assertThat(recordingState.levels.size).isEqualTo(128) + assertThat(recordingState.levels).isEqualTo(levels.takeLast(128)) + testPauseAndDestroy(finalState) + } + } + @Test fun `present - recording keeps screen on`() = runTest { val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { awaitItem().apply { - eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) assertThat(keepScreenOn).isFalse() } awaitItem().apply { assertThat(keepScreenOn).isTrue() - eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) + eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Stop)) } val finalState = awaitItem().apply { @@ -144,11 +162,9 @@ class VoiceMessageComposerPresenterTest { @Test fun `present - abort recording`() = runTest { val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Cancel)) + presenter.test { + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Cancel)) val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1) @@ -159,11 +175,9 @@ class VoiceMessageComposerPresenterTest { @Test fun `present - finish recording`() = runTest { val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) + presenter.test { + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Stop)) val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(aPreviewState()) @@ -176,12 +190,10 @@ class VoiceMessageComposerPresenterTest { @Test fun `present - play recording before it is ready`() = runTest { val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + presenter.test { + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) val finalState = awaitItem().apply { - this.eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play)) + this.eventSink(VoiceMessageComposerEvent.PlayerEvent(VoiceMessagePlayerEvent.Play)) } // Nothing should happen @@ -195,12 +207,10 @@ class VoiceMessageComposerPresenterTest { @Test fun `present - play recording`() = runTest { val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) - awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play)) + presenter.test { + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Stop)) + awaitItem().eventSink(VoiceMessageComposerEvent.PlayerEvent(VoiceMessagePlayerEvent.Play)) val finalState = awaitItem().also { assertThat(it.voiceMessageState).isEqualTo(aPlayingState()) } @@ -213,13 +223,11 @@ class VoiceMessageComposerPresenterTest { @Test fun `present - pause recording`() = runTest { val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) - awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play)) - awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Pause)) + presenter.test { + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Stop)) + awaitItem().eventSink(VoiceMessageComposerEvent.PlayerEvent(VoiceMessagePlayerEvent.Play)) + awaitItem().eventSink(VoiceMessageComposerEvent.PlayerEvent(VoiceMessagePlayerEvent.Pause)) val finalState = awaitItem().also { assertThat(it.voiceMessageState).isEqualTo(aPausedState()) } @@ -232,18 +240,16 @@ class VoiceMessageComposerPresenterTest { @Test fun `present - seek recording`() = runTest { val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) - awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Seek(0.5f))) + presenter.test { + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Stop)) + awaitItem().eventSink(VoiceMessageComposerEvent.PlayerEvent(VoiceMessagePlayerEvent.Seek(0.5f))) awaitItem().apply { assertThat(voiceMessageState).isEqualTo(aPreviewState(playbackProgress = 0.5f, time = 0.seconds, showCursor = true)) } awaitItem().apply { assertThat(voiceMessageState).isEqualTo(aPreviewState(playbackProgress = 0.5f, time = 5.seconds, showCursor = true)) - eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Seek(0.2f))) + eventSink(VoiceMessageComposerEvent.PlayerEvent(VoiceMessagePlayerEvent.Seek(0.2f))) } awaitItem().apply { assertThat(voiceMessageState).isEqualTo(aPreviewState(playbackProgress = 0.2f, time = 5.seconds, showCursor = true)) @@ -259,12 +265,10 @@ class VoiceMessageComposerPresenterTest { @Test fun `present - delete recording`() = runTest { val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) - awaitItem().eventSink(VoiceMessageComposerEvents.DeleteVoiceMessage) + presenter.test { + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Stop)) + awaitItem().eventSink(VoiceMessageComposerEvent.DeleteVoiceMessage) val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) @@ -277,13 +281,11 @@ class VoiceMessageComposerPresenterTest { @Test fun `present - delete while playing`() = runTest { val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) - awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play)) - awaitItem().eventSink(VoiceMessageComposerEvents.DeleteVoiceMessage) + presenter.test { + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Stop)) + awaitItem().eventSink(VoiceMessageComposerEvent.PlayerEvent(VoiceMessagePlayerEvent.Play)) + awaitItem().eventSink(VoiceMessageComposerEvent.DeleteVoiceMessage) awaitItem().apply { assertThat(voiceMessageState).isEqualTo(aPausedState()) } @@ -299,12 +301,10 @@ class VoiceMessageComposerPresenterTest { @Test fun `present - send recording`() = runTest { val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) - awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage) + presenter.test { + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Stop)) + awaitItem().eventSink(VoiceMessageComposerEvent.SendVoiceMessage) assertThat(awaitItem().voiceMessageState).isEqualTo(aPreviewState().toSendingState()) val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) @@ -318,21 +318,19 @@ class VoiceMessageComposerPresenterTest { @Test fun `present - sending is tracked`() = runTest { val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { // Send a normal voice message messageComposerContext.composerMode = MessageComposerMode.Normal - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) - awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Stop)) + awaitItem().eventSink(VoiceMessageComposerEvent.SendVoiceMessage) skipItems(1) // Sending state advanceUntilIdle() // Now reply with a voice message messageComposerContext.composerMode = aReplyMode() - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) - awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Stop)) + awaitItem().eventSink(VoiceMessageComposerEvent.SendVoiceMessage) val finalState = awaitItem() // Sending state assertThat(analyticsService.capturedEvents).containsExactly( @@ -347,13 +345,11 @@ class VoiceMessageComposerPresenterTest { @Test fun `present - send while playing`() = runTest { val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) - awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play)) - awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage) + presenter.test { + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Stop)) + awaitItem().eventSink(VoiceMessageComposerEvent.PlayerEvent(VoiceMessagePlayerEvent.Play)) + awaitItem().eventSink(VoiceMessageComposerEvent.SendVoiceMessage) assertThat(awaitItem().voiceMessageState).isEqualTo(aPlayingState().toSendingState()) skipItems(1) // Duplicate sending state @@ -369,14 +365,12 @@ class VoiceMessageComposerPresenterTest { @Test fun `present - send recording before previous completed, waits`() = runTest { val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) + presenter.test { + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Stop)) awaitItem().run { - eventSink(VoiceMessageComposerEvents.SendVoiceMessage) - eventSink(VoiceMessageComposerEvents.SendVoiceMessage) + eventSink(VoiceMessageComposerEvent.SendVoiceMessage) + eventSink(VoiceMessageComposerEvent.SendVoiceMessage) } assertThat(awaitItem().voiceMessageState).isEqualTo(aPreviewState().toSendingState()) @@ -394,20 +388,18 @@ class VoiceMessageComposerPresenterTest { // Let sending fail due to media preprocessing error mediaPreProcessor.givenResult(Result.failure(Exception())) val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) + presenter.test { + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Stop)) awaitItem().apply { assertThat(voiceMessageState).isEqualTo(aPreviewState()) - eventSink(VoiceMessageComposerEvents.SendVoiceMessage) + eventSink(VoiceMessageComposerEvent.SendVoiceMessage) } val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(aPreviewState(isSending = true)) sendVoiceMessageResult.assertions().isNeverCalled() - assertThat(analyticsService.trackedErrors).hasSize(0) + assertThat(analyticsService.trackedErrors).isEmpty() voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 0) testPauseAndDestroy(finalState) @@ -418,15 +410,13 @@ class VoiceMessageComposerPresenterTest { fun `present - send failures can be retried`() = runTest { // Let sending fail due to media preprocessing error val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { mediaPreProcessor.givenResult(Result.failure(Exception())) - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Stop)) val previewState = awaitItem() - previewState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage) + previewState.eventSink(VoiceMessageComposerEvent.SendVoiceMessage) assertThat(awaitItem().voiceMessageState).isEqualTo(aPreviewState().toSendingState()) ensureAllEventsConsumed() @@ -434,7 +424,7 @@ class VoiceMessageComposerPresenterTest { sendVoiceMessageResult.assertions().isNeverCalled() mediaPreProcessor.givenAudioResult() - previewState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage) + previewState.eventSink(VoiceMessageComposerEvent.SendVoiceMessage) val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) sendVoiceMessageResult.assertions().isCalledOnce() @@ -447,14 +437,12 @@ class VoiceMessageComposerPresenterTest { @Test fun `present - send failures are displayed as an error dialog`() = runTest { val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { // Let sending fail due to media preprocessing error mediaPreProcessor.givenResult(Result.failure(Exception())) - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) - awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Stop)) + awaitItem().eventSink(VoiceMessageComposerEvent.SendVoiceMessage) assertThat(awaitItem().voiceMessageState).isEqualTo(aPreviewState().toSendingState()) @@ -466,7 +454,7 @@ class VoiceMessageComposerPresenterTest { awaitItem().apply { assertThat(voiceMessageState).isEqualTo(aPreviewState()) assertThat(showSendFailureDialog).isTrue() - eventSink(VoiceMessageComposerEvents.DismissSendFailureDialog) + eventSink(VoiceMessageComposerEvent.DismissSendFailureDialog) } val finalState = awaitItem().apply { @@ -482,12 +470,10 @@ class VoiceMessageComposerPresenterTest { @Test fun `present - send error - missing recording is tracked`() = runTest { val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() // Send the message before recording anything - initialState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage) + initialState.eventSink(VoiceMessageComposerEvent.SendVoiceMessage) assertThat(initialState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) sendVoiceMessageResult.assertions().isNeverCalled() @@ -503,11 +489,9 @@ class VoiceMessageComposerPresenterTest { val exception = SecurityException("") voiceRecorder.givenThrowsSecurityException(exception) val presenter = createDefaultVoiceMessageComposerPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() - initialState.eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + initialState.eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) sendVoiceMessageResult.assertions().isNeverCalled() assertThat(analyticsService.trackedErrors).containsExactly( @@ -527,19 +511,17 @@ class VoiceMessageComposerPresenterTest { val presenter = createDefaultVoiceMessageComposerPresenter( permissionsPresenter = permissionsPresenter, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() - initialState.eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + initialState.eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) assertThat(awaitItem().voiceMessageState).isEqualTo(VoiceMessageState.Idle) - initialState.eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop)) + initialState.eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Stop)) voiceRecorder.assertCalls(stopped = 1) permissionsPresenter.setPermissionGranted() - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE) voiceRecorder.assertCalls(stopped = 1, started = 1) @@ -556,16 +538,14 @@ class VoiceMessageComposerPresenterTest { val presenter = createDefaultVoiceMessageComposerPresenter( permissionsPresenter = permissionsPresenter, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + presenter.test { + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) // See the dialog and accept it awaitItem().also { assertThat(it.voiceMessageState).isEqualTo(VoiceMessageState.Idle) assertThat(it.showPermissionRationaleDialog).isTrue() - it.eventSink(VoiceMessageComposerEvents.AcceptPermissionRationale) + it.eventSink(VoiceMessageComposerEvent.AcceptPermissionRationale) } // Dialog is hidden, user accepts permissions @@ -573,7 +553,7 @@ class VoiceMessageComposerPresenterTest { permissionsPresenter.setPermissionGranted() - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE) voiceRecorder.assertCalls(started = 1) @@ -590,22 +570,20 @@ class VoiceMessageComposerPresenterTest { val presenter = createDefaultVoiceMessageComposerPresenter( permissionsPresenter = permissionsPresenter, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + presenter.test { + awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) // See the dialog and accept it awaitItem().also { assertThat(it.voiceMessageState).isEqualTo(VoiceMessageState.Idle) assertThat(it.showPermissionRationaleDialog).isTrue() - it.eventSink(VoiceMessageComposerEvents.DismissPermissionsRationale) + it.eventSink(VoiceMessageComposerEvent.DismissPermissionsRationale) } // Dialog is hidden, user tries to record again awaitItem().also { assertThat(it.showPermissionRationaleDialog).isFalse() - it.eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) + it.eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) } // Dialog is shown once again @@ -623,7 +601,7 @@ class VoiceMessageComposerPresenterTest { mostRecentState: VoiceMessageComposerState, ) { mostRecentState.eventSink( - VoiceMessageComposerEvents.LifecycleEvent(event = Lifecycle.Event.ON_PAUSE) + VoiceMessageComposerEvent.LifecycleEvent(event = Lifecycle.Event.ON_PAUSE) ) val onPauseState = when (val state = mostRecentState.voiceMessageState) { @@ -644,7 +622,7 @@ class VoiceMessageComposerPresenterTest { } onPauseState.eventSink( - VoiceMessageComposerEvents.LifecycleEvent(event = Lifecycle.Event.ON_DESTROY) + VoiceMessageComposerEvent.LifecycleEvent(event = Lifecycle.Event.ON_DESTROY) ) when (val state = onPauseState.voiceMessageState) { @@ -661,17 +639,14 @@ class VoiceMessageComposerPresenterTest { private fun TestScope.createDefaultVoiceMessageComposerPresenter( permissionsPresenter: PermissionsPresenter = createFakePermissionsPresenter(), + voiceRecorder: VoiceRecorder = this@DefaultVoiceMessageComposerPresenterTest.voiceRecorder, ): DefaultVoiceMessageComposerPresenter { return DefaultVoiceMessageComposerPresenter( sessionCoroutineScope = backgroundScope, timelineMode = Timeline.Mode.Live, voiceRecorder = voiceRecorder, analyticsService = analyticsService, - mediaSenderFactory = object : MediaSender.Factory { - override fun create(timelineMode: Timeline.Mode): MediaSender { - return mediaSender - } - }, + mediaSenderFactory = { mediaSender }, player = VoiceMessageComposerPlayer(FakeMediaPlayer(), this), messageComposerContext = messageComposerContext, permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/FakeRedactedVoiceMessageManager.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/FakeRedactedVoiceMessageManager.kt index b36e839d8f..39355a2d4e 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/FakeRedactedVoiceMessageManager.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/FakeRedactedVoiceMessageManager.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt index da349d7a9c..8bced93023 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,7 +15,7 @@ import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem -import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EVENT_ID_2 @@ -85,7 +86,7 @@ fun aRedactedMatrixTimeline(eventId: EventId) = listOf( reactions = persistentListOf(), receipts = persistentListOf(), sender = A_USER_ID, - senderProfile = ProfileTimelineDetails.Unavailable, + senderProfile = ProfileDetails.Unavailable, timestamp = 9442, content = RedactedContent, origin = null, diff --git a/features/messages/test/build.gradle.kts b/features/messages/test/build.gradle.kts index 93f5166f29..09d357357d 100644 --- a/features/messages/test/build.gradle.kts +++ b/features/messages/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -23,4 +24,6 @@ dependencies { implementation(projects.libraries.preferences.api) implementation(projects.libraries.voicerecorder.test) implementation(projects.services.analytics.test) + implementation(projects.tests.testutils) + implementation(projects.libraries.mediaupload.impl) } diff --git a/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/FakeMessageComposerContext.kt b/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/FakeMessageComposerContext.kt index 02bb713142..25978f2db0 100644 --- a/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/FakeMessageComposerContext.kt +++ b/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/FakeMessageComposerContext.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/FakeMessagesEntryPoint.kt b/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/FakeMessagesEntryPoint.kt new file mode 100644 index 0000000000..491e1ff48f --- /dev/null +++ b/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/FakeMessagesEntryPoint.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.messages.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.messages.api.MessagesEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeMessagesEntryPoint : MessagesEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: MessagesEntryPoint.Params, + callback: MessagesEntryPoint.Callback, + ): Node = lambdaError() +} diff --git a/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/attachments/video/FakeMediaOptimizationSelectorPresenterFactory.kt b/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/attachments/video/FakeMediaOptimizationSelectorPresenterFactory.kt index 780638a205..02a6918ad8 100644 --- a/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/attachments/video/FakeMediaOptimizationSelectorPresenterFactory.kt +++ b/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/attachments/video/FakeMediaOptimizationSelectorPresenterFactory.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/attachments/video/FakeVideoMetadataExtractor.kt b/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/attachments/video/FakeVideoMetadataExtractor.kt index 45d3e31dfb..6a815c48fe 100644 --- a/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/attachments/video/FakeVideoMetadataExtractor.kt +++ b/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/attachments/video/FakeVideoMetadataExtractor.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/timeline/FakeHtmlConverterProvider.kt b/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/timeline/FakeHtmlConverterProvider.kt index b9ea0d2416..1277783f6a 100644 --- a/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/timeline/FakeHtmlConverterProvider.kt +++ b/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/timeline/FakeHtmlConverterProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,9 +11,11 @@ package io.element.android.features.messages.test.timeline import androidx.compose.runtime.Composable import io.element.android.features.messages.api.timeline.HtmlConverterProvider import io.element.android.wysiwyg.utils.HtmlConverter +import org.jsoup.nodes.Document class FakeHtmlConverterProvider( private val transform: (String) -> CharSequence = { it }, + private val transformDom: (Document) -> CharSequence = { it.html() }, ) : HtmlConverterProvider { @Composable override fun Update() = Unit @@ -22,6 +25,10 @@ class FakeHtmlConverterProvider( override fun fromHtmlToSpans(html: String): CharSequence { return transform(html) } + + override fun fromDocumentToSpans(dom: Document): CharSequence { + return transformDom(dom) + } } } } diff --git a/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/timeline/voicemessages/composer/FakeDefaultVoiceMessageComposerPresenterFactory.kt b/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/timeline/voicemessages/composer/FakeDefaultVoiceMessageComposerPresenterFactory.kt index da83fa69e2..0de1b69d78 100644 --- a/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/timeline/voicemessages/composer/FakeDefaultVoiceMessageComposerPresenterFactory.kt +++ b/features/messages/test/src/main/kotlin/io/element/android/features/messages/test/timeline/voicemessages/composer/FakeDefaultVoiceMessageComposerPresenterFactory.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,6 +15,7 @@ import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer import io.element.android.libraries.mediaupload.api.MediaSender +import io.element.android.libraries.mediaupload.impl.DefaultMediaSender import io.element.android.libraries.mediaupload.test.FakeMediaOptimizationConfigProvider import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory @@ -23,7 +25,7 @@ import kotlinx.coroutines.CoroutineScope class FakeDefaultVoiceMessageComposerPresenterFactory( private val sessionCoroutineScope: CoroutineScope, - private val mediaSender: MediaSender = MediaSender( + private val mediaSender: MediaSender = DefaultMediaSender( preProcessor = FakeMediaPreProcessor(), room = FakeJoinedRoom(), timelineMode = Timeline.Mode.Live, @@ -36,11 +38,7 @@ class FakeDefaultVoiceMessageComposerPresenterFactory( timelineMode = timelineMode, voiceRecorder = FakeVoiceRecorder(), analyticsService = FakeAnalyticsService(), - mediaSenderFactory = object : MediaSender.Factory { - override fun create(timelineMode: Timeline.Mode): MediaSender { - return mediaSender - } - }, + mediaSenderFactory = { mediaSender }, player = VoiceMessageComposerPlayer( mediaPlayer = FakeMediaPlayer(), sessionCoroutineScope = sessionCoroutineScope, diff --git a/features/migration/api/build.gradle.kts b/features/migration/api/build.gradle.kts index 594522b86a..23cb041842 100644 --- a/features/migration/api/build.gradle.kts +++ b/features/migration/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/api/src/main/kotlin/io/element/android/features/api/MigrationEntryPoint.kt b/features/migration/api/src/main/kotlin/io/element/android/features/api/MigrationEntryPoint.kt index 6ef7d07068..471ee30d22 100644 --- a/features/migration/api/src/main/kotlin/io/element/android/features/api/MigrationEntryPoint.kt +++ b/features/migration/api/src/main/kotlin/io/element/android/features/api/MigrationEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/api/src/main/kotlin/io/element/android/features/api/MigrationState.kt b/features/migration/api/src/main/kotlin/io/element/android/features/api/MigrationState.kt index 44ddca7d2c..000d898a6a 100644 --- a/features/migration/api/src/main/kotlin/io/element/android/features/api/MigrationState.kt +++ b/features/migration/api/src/main/kotlin/io/element/android/features/api/MigrationState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/build.gradle.kts b/features/migration/impl/build.gradle.kts index 8926f258bd..eb22d06399 100644 --- a/features/migration/impl/build.gradle.kts +++ b/features/migration/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/DefaultMigrationEntryPoint.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/DefaultMigrationEntryPoint.kt index 68c4c7ce3a..edefa7ce75 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/DefaultMigrationEntryPoint.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/DefaultMigrationEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,12 +12,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.api.MigrationEntryPoint import io.element.android.features.api.MigrationState @ContributesBinding(AppScope::class) -@Inject class DefaultMigrationEntryPoint( private val migrationPresenter: MigrationPresenter, ) : MigrationEntryPoint { diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/DefaultMigrationStore.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/DefaultMigrationStore.kt index 78883a4e03..fe73a6a607 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/DefaultMigrationStore.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/DefaultMigrationStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,6 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.intPreferencesKey import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -19,7 +19,6 @@ import kotlinx.coroutines.flow.map private val applicationMigrationVersion = intPreferencesKey("applicationMigrationVersion") @ContributesBinding(AppScope::class) -@Inject class DefaultMigrationStore( preferenceDataStoreFactory: PreferenceDataStoreFactory, ) : MigrationStore { diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt index d4e6d6dc37..2d78e5f706 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationStateProvider.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationStateProvider.kt index b9179c4ca2..8f738d3207 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationStateProvider.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationStore.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationStore.kt index 30d637070f..7fdade821d 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationStore.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationView.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationView.kt index 58b9177267..a294c32534 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationView.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration.kt index f14ec89dbe..dcac76c3e0 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01.kt index 88fdb16b9e..60969dcb1d 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02.kt index 0ba2712427..64682749d8 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration03.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration03.kt index e24e18a205..62674cf583 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration03.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration03.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration04.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration04.kt index f1f16b7a97..8ac4146640 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration04.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration04.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration05.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration05.kt index 2a60822c5d..094248f5a0 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration05.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration05.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration06.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration06.kt index be3050f919..0fbda43207 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration06.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration06.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration07.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration07.kt index 5367323b75..d562be793b 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration07.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration07.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration08.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration08.kt index 12355b4bc4..0f3b33a66d 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration08.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration08.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/InMemoryMigrationStore.kt b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/InMemoryMigrationStore.kt index 6107ee8d86..d9724dbb4e 100644 --- a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/InMemoryMigrationStore.kt +++ b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/InMemoryMigrationStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/MigrationPresenterTest.kt b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/MigrationPresenterTest.kt index e5898ea0dd..f3f7e27c74 100644 --- a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/MigrationPresenterTest.kt +++ b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/MigrationPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01Test.kt b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01Test.kt index 6e1b663e3c..9f87670d01 100644 --- a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01Test.kt +++ b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration01Test.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02Test.kt b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02Test.kt index 08c10160b8..c326d80736 100644 --- a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02Test.kt +++ b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration02Test.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration03Test.kt b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration03Test.kt index 0f4db78aa2..ad69f03a15 100644 --- a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration03Test.kt +++ b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration03Test.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration04Test.kt b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration04Test.kt index 68cbfde689..7b954abf86 100644 --- a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration04Test.kt +++ b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration04Test.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration05Test.kt b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration05Test.kt index ff5b8cd40a..fee0a2e717 100644 --- a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration05Test.kt +++ b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration05Test.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration06Test.kt b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration06Test.kt index e7e15ba821..f5410229ab 100644 --- a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration06Test.kt +++ b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration06Test.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration07Test.kt b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration07Test.kt index a2575b32df..2f5027d3d7 100644 --- a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration07Test.kt +++ b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration07Test.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration08Test.kt b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration08Test.kt index 75ffa28882..46bde271c4 100644 --- a/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration08Test.kt +++ b/features/migration/impl/src/test/kotlin/io/element/android/features/migration/impl/migrations/AppMigration08Test.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/networkmonitor/api/build.gradle.kts b/features/networkmonitor/api/build.gradle.kts index 61bd3ae457..1cc9869fc2 100644 --- a/features/networkmonitor/api/build.gradle.kts +++ b/features/networkmonitor/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkMonitor.kt b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkMonitor.kt index 196eb89310..484a210380 100644 --- a/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkMonitor.kt +++ b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkMonitor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkStatus.kt b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkStatus.kt index aebcb5a1d1..f96f8666e3 100644 --- a/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkStatus.kt +++ b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkStatus.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/Indicator.kt b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicator.kt similarity index 86% rename from features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/Indicator.kt rename to features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicator.kt index 95c46d3fd0..c79f2a1ff5 100644 --- a/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/Indicator.kt +++ b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -20,6 +21,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import io.element.android.compound.theme.ElementTheme @@ -32,7 +34,8 @@ import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.ui.strings.CommonStrings @Composable -internal fun Indicator( +internal fun ConnectivityIndicator( + verticalPadding: Dp, modifier: Modifier = Modifier, ) { Row( @@ -40,7 +43,7 @@ internal fun Indicator( .fillMaxWidth() .background(ElementTheme.colors.bgSubtlePrimary) .statusBarsPadding() - .padding(vertical = 6.dp), + .padding(vertical = verticalPadding), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, ) { @@ -61,6 +64,6 @@ internal fun Indicator( @PreviewsDayNight @Composable -internal fun IndicatorPreview() = ElementPreview { - Indicator() +internal fun ConnectivityIndicatorPreview() = ElementPreview { + ConnectivityIndicator(verticalPadding = 6.dp) } diff --git a/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorContainer.kt b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorContainer.kt index 6079cb7bbe..7c3ff4a988 100644 --- a/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorContainer.kt +++ b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorContainer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,55 +17,58 @@ import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.statusBars import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalInspectionMode -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +private val INDICATOR_VERTICAL_PADDING = 6.dp + /** - * A view that displays a connectivity indicator when the device is offline, passing the padding - * needed to make sure the status bar is not overlapped to its content views. + * A view that displays a connectivity indicator when the device is offline. */ @Composable fun ConnectivityIndicatorContainer( isOnline: Boolean, modifier: Modifier = Modifier, - content: @Composable (topPadding: Dp) -> Unit = {}, + content: @Composable (Modifier) -> Unit = {}, ) { val isIndicatorVisible = remember { MutableTransitionState(!isOnline) }.apply { targetState = !isOnline } - - val statusBarTopPadding = if (LocalInspectionMode.current) { - // Needed to get valid UI previews - 24.dp - } else { - WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + 6.dp - } - val target = remember(isIndicatorVisible.targetState, statusBarTopPadding) { - if (!isIndicatorVisible.targetState) 0.dp else statusBarTopPadding - } - val animationStateOffset by animateDpAsState( - targetValue = target, - animationSpec = spring( - stiffness = Spring.StiffnessMediumLow, - visibilityThreshold = 1.dp, - ), - label = "insets-animation", - ) - - content(animationStateOffset) - - // Display the network indicator with an animation - AnimatedVisibility( - visibleState = isIndicatorVisible, - enter = fadeIn() + expandVertically(), - exit = fadeOut() + shrinkVertically(), - ) { - Indicator(modifier) + Column(modifier = modifier) { + val statusBarTopPadding = if (LocalInspectionMode.current) { + // Needed to get valid UI previews + 24.dp + } else { + WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + INDICATOR_VERTICAL_PADDING + } + val target = if (isIndicatorVisible.targetState) statusBarTopPadding else 0.dp + val topWindowInset by animateDpAsState( + targetValue = target, + animationSpec = spring( + stiffness = Spring.StiffnessMediumLow, + visibilityThreshold = 1.dp, + ), + label = "insets-animation", + ) + // Display the network indicator with an animation + AnimatedVisibility( + visibleState = isIndicatorVisible, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically(), + ) { + ConnectivityIndicator(verticalPadding = INDICATOR_VERTICAL_PADDING) + } + // Consume the window insets to avoid double padding. + content( + Modifier.consumeWindowInsets(PaddingValues(top = topWindowInset)) + ) } } 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 deleted file mode 100644 index 3e18046878..0000000000 --- a/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorView.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.networkmonitor.api.ui - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.MutableTransitionState -import androidx.compose.animation.expandVertically -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.shrinkVertically -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.preview.PreviewsDayNight - -/** - * A view that displays a connectivity indicator when the device is offline, adding a default - * padding to make sure the status bar is not overlapped. - */ -@Composable -fun ConnectivityIndicatorView( - isOnline: Boolean, -) { - val isIndicatorVisible = remember { MutableTransitionState(!isOnline) }.apply { targetState = !isOnline } - val isStatusBarPaddingVisible = remember { MutableTransitionState(isOnline) }.apply { targetState = isOnline } - - // Display the network indicator with an animation - AnimatedVisibility( - visibleState = isIndicatorVisible, - enter = fadeIn() + expandVertically(), - exit = fadeOut() + shrinkVertically(), - ) { - Indicator() - } - - // Show missing status bar padding when the indicator is not visible - AnimatedVisibility( - visibleState = isStatusBarPaddingVisible, - enter = fadeIn() + expandVertically(), - exit = fadeOut() + shrinkVertically(), - ) { - StatusBarPaddingSpacer() - } -} - -@Composable -private fun StatusBarPaddingSpacer(modifier: Modifier = Modifier) { - Spacer(modifier = modifier.statusBarsPadding()) -} - -@PreviewsDayNight -@Composable -internal fun ConnectivityIndicatorViewPreview() { - ElementPreview { - ConnectivityIndicatorView(isOnline = false) - } -} diff --git a/features/networkmonitor/impl/build.gradle.kts b/features/networkmonitor/impl/build.gradle.kts index 1c070e66df..ba754ec1db 100644 --- a/features/networkmonitor/impl/build.gradle.kts +++ b/features/networkmonitor/impl/build.gradle.kts @@ -1,9 +1,10 @@ import extension.setupDependencyInjection /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/networkmonitor/impl/src/main/AndroidManifest.xml b/features/networkmonitor/impl/src/main/AndroidManifest.xml index e37080298d..f13a0993df 100644 --- a/features/networkmonitor/impl/src/main/AndroidManifest.xml +++ b/features/networkmonitor/impl/src/main/AndroidManifest.xml @@ -1,7 +1,8 @@ diff --git a/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/DefaultNetworkMonitor.kt b/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/DefaultNetworkMonitor.kt index 76525ff657..8e10ceeabf 100644 --- a/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/DefaultNetworkMonitor.kt +++ b/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/DefaultNetworkMonitor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,7 +16,6 @@ import android.net.Network import android.net.NetworkRequest import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkStatus @@ -37,7 +37,6 @@ import java.util.concurrent.atomic.AtomicInteger @ContributesBinding(scope = AppScope::class) @SingleIn(AppScope::class) -@Inject class DefaultNetworkMonitor( @ApplicationContext context: Context, @AppCoroutineScope diff --git a/features/networkmonitor/test/build.gradle.kts b/features/networkmonitor/test/build.gradle.kts index 970a102ae7..5a6eff9984 100644 --- a/features/networkmonitor/test/build.gradle.kts +++ b/features/networkmonitor/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/networkmonitor/test/src/main/kotlin/io/element/android/features/networkmonitor/test/FakeNetworkMonitor.kt b/features/networkmonitor/test/src/main/kotlin/io/element/android/features/networkmonitor/test/FakeNetworkMonitor.kt index 298e06aa5b..37d569dbc6 100644 --- a/features/networkmonitor/test/src/main/kotlin/io/element/android/features/networkmonitor/test/FakeNetworkMonitor.kt +++ b/features/networkmonitor/test/src/main/kotlin/io/element/android/features/networkmonitor/test/FakeNetworkMonitor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/api/build.gradle.kts b/features/poll/api/build.gradle.kts index 360c2f16c2..fb7534a947 100644 --- a/features/poll/api/build.gradle.kts +++ b/features/poll/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/actions/EndPollAction.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/actions/EndPollAction.kt index 8e5b5c6ba3..a46a2db781 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/actions/EndPollAction.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/actions/EndPollAction.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/actions/SendPollResponseAction.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/actions/SendPollResponseAction.kt index 49da04829b..00b60ed5ae 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/actions/SendPollResponseAction.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/actions/SendPollResponseAction.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/create/CreatePollEntryPoint.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/create/CreatePollEntryPoint.kt index 348f2f6c60..1bdb20b8b9 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/create/CreatePollEntryPoint.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/create/CreatePollEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,10 +19,9 @@ interface CreatePollEntryPoint : FeatureEntryPoint { val mode: CreatePollMode, ) - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - - interface NodeBuilder { - fun params(params: Params): NodeBuilder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: Params, + ): Node } diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/create/CreatePollMode.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/create/CreatePollMode.kt index 3701b7c92f..28431096e6 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/create/CreatePollMode.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/create/CreatePollMode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/history/PollHistoryEntryPoint.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/history/PollHistoryEntryPoint.kt index 49f5f92b87..15bc15691c 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/history/PollHistoryEntryPoint.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/history/PollHistoryEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerItem.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerItem.kt index f50ba22b41..1fa7eca0f5 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerItem.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerView.kt index 59d13fed1e..489a8d4137 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollAnswerView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentState.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentState.kt index 0c43baab39..1689408098 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentState.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentStateFactory.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentStateFactory.kt index e42bb98bef..c9bd31ea4f 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentStateFactory.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentStateFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentStateFixtures.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentStateFixtures.kt index a54ebcca51..7f15c7d513 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentStateFixtures.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentStateFixtures.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentView.kt index 3f32f90678..e1df373218 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollContentView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollTitleView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollTitleView.kt index 297a00a914..06eb75941d 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollTitleView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/pollcontent/PollTitleView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/api/src/main/res/values-hr/translations.xml b/features/poll/api/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..117ed51c6b --- /dev/null +++ b/features/poll/api/src/main/res/values-hr/translations.xml @@ -0,0 +1,10 @@ + + + + "%1$d posto ukupnog broja glasova" + "%1$d posto ukupnog broja glasova" + "%1$d posto ukupnog broja glasova" + + "Uklonit će prethodni odabir" + "Ovo je pobjednički odgovor" + diff --git a/features/poll/api/src/main/res/values-uz/translations.xml b/features/poll/api/src/main/res/values-uz/translations.xml index 6e1a16a013..8446135f20 100644 --- a/features/poll/api/src/main/res/values-uz/translations.xml +++ b/features/poll/api/src/main/res/values-uz/translations.xml @@ -1,5 +1,9 @@ + + "Jami ovozlarning %1$d foizi" + "Jami ovozlarning %1$d foizi" + "Oldingi tanlov olib tashlanadi" "Bu g\'alaba qozongan javob" diff --git a/features/poll/impl/build.gradle.kts b/features/poll/impl/build.gradle.kts index 99ebefa71c..175f7b4471 100644 --- a/features/poll/impl/build.gradle.kts +++ b/features/poll/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/PollConstants.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/PollConstants.kt index d7f5fed75a..c3579dc447 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/PollConstants.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/PollConstants.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/actions/DefaultEndPollAction.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/actions/DefaultEndPollAction.kt index e028209ae8..a483930fb2 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/actions/DefaultEndPollAction.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/actions/DefaultEndPollAction.kt @@ -1,14 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.poll.impl.actions import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import im.vector.app.features.analytics.plan.PollEnd import io.element.android.features.poll.api.actions.EndPollAction import io.element.android.libraries.di.RoomScope @@ -17,7 +17,6 @@ import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.services.analytics.api.AnalyticsService @ContributesBinding(RoomScope::class) -@Inject class DefaultEndPollAction( private val analyticsService: AnalyticsService, ) : EndPollAction { diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/actions/DefaultSendPollResponseAction.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/actions/DefaultSendPollResponseAction.kt index a067757357..f48604b21d 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/actions/DefaultSendPollResponseAction.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/actions/DefaultSendPollResponseAction.kt @@ -1,14 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.poll.impl.actions import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import im.vector.app.features.analytics.plan.PollVote import io.element.android.features.poll.api.actions.SendPollResponseAction import io.element.android.libraries.di.RoomScope @@ -17,7 +17,6 @@ import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.services.analytics.api.AnalyticsService @ContributesBinding(RoomScope::class) -@Inject class DefaultSendPollResponseAction( private val analyticsService: AnalyticsService, ) : SendPollResponseAction { diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollEvent.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollEvent.kt new file mode 100644 index 0000000000..b98ab899b9 --- /dev/null +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollEvent.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.poll.impl.create + +import io.element.android.libraries.matrix.api.poll.PollKind + +sealed interface CreatePollEvent { + data object Save : CreatePollEvent + data class Delete(val confirmed: Boolean) : CreatePollEvent + data class SetQuestion(val question: String) : CreatePollEvent + data class SetAnswer(val index: Int, val text: String) : CreatePollEvent + data object AddAnswer : CreatePollEvent + data class RemoveAnswer(val index: Int) : CreatePollEvent + data class SetPollKind(val pollKind: PollKind) : CreatePollEvent + data object NavBack : CreatePollEvent + data object ConfirmNavBack : CreatePollEvent + data object HideConfirmation : CreatePollEvent +} diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollEvents.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollEvents.kt deleted file mode 100644 index 527c91b011..0000000000 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollEvents.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.poll.impl.create - -import io.element.android.libraries.matrix.api.poll.PollKind - -sealed interface CreatePollEvents { - data object Save : CreatePollEvents - data class Delete(val confirmed: Boolean) : CreatePollEvents - data class SetQuestion(val question: String) : CreatePollEvents - data class SetAnswer(val index: Int, val text: String) : CreatePollEvents - data object AddAnswer : CreatePollEvents - data class RemoveAnswer(val index: Int) : CreatePollEvents - data class SetPollKind(val pollKind: PollKind) : CreatePollEvents - data object NavBack : CreatePollEvents - data object ConfirmNavBack : CreatePollEvents - data object HideConfirmation : CreatePollEvents -} diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollException.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollException.kt index 987a876be5..e5bb32ef42 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollException.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollException.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollNode.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollNode.kt index 992fde1c4e..9e08b58839 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollNode.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt index 5136571acd..6138bab2ae 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -96,9 +97,9 @@ class CreatePollPresenter( val scope = rememberCoroutineScope() - fun handleEvents(event: CreatePollEvents) { + fun handleEvent(event: CreatePollEvent) { when (event) { - is CreatePollEvents.Save -> scope.launch { + is CreatePollEvent.Save -> scope.launch { if (canSave) { repository.savePoll( existingPollId = when (mode) { @@ -122,7 +123,7 @@ class CreatePollPresenter( Timber.d("Cannot create poll") } } - is CreatePollEvents.Delete -> { + is CreatePollEvent.Delete -> { if (mode !is CreatePollMode.EditPoll) { return } @@ -138,25 +139,25 @@ class CreatePollPresenter( navigateUp() } } - is CreatePollEvents.AddAnswer -> { + is CreatePollEvent.AddAnswer -> { poll = poll.withNewAnswer() } - is CreatePollEvents.RemoveAnswer -> { + is CreatePollEvent.RemoveAnswer -> { poll = poll.withAnswerRemoved(event.index) } - is CreatePollEvents.SetAnswer -> { + is CreatePollEvent.SetAnswer -> { poll = poll.withAnswerChanged(event.index, event.text) } - is CreatePollEvents.SetPollKind -> { + is CreatePollEvent.SetPollKind -> { poll = poll.copy(isDisclosed = event.pollKind.isDisclosed) } - is CreatePollEvents.SetQuestion -> { + is CreatePollEvent.SetQuestion -> { poll = poll.copy(question = event.question) } - is CreatePollEvents.NavBack -> { + is CreatePollEvent.NavBack -> { navigateUp() } - CreatePollEvents.ConfirmNavBack -> { + CreatePollEvent.ConfirmNavBack -> { val shouldConfirm = isDirty if (shouldConfirm) { showBackConfirmation = true @@ -164,7 +165,7 @@ class CreatePollPresenter( navigateUp() } } - is CreatePollEvents.HideConfirmation -> { + is CreatePollEvent.HideConfirmation -> { showBackConfirmation = false showDeleteConfirmation = false } @@ -183,7 +184,7 @@ class CreatePollPresenter( pollKind = poll.pollKind, showBackConfirmation = showBackConfirmation, showDeleteConfirmation = showDeleteConfirmation, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollState.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollState.kt index 2b4f232871..80aa7dc4b0 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollState.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -19,7 +20,7 @@ data class CreatePollState( val pollKind: PollKind, val showBackConfirmation: Boolean, val showDeleteConfirmation: Boolean, - val eventSink: (CreatePollEvents) -> Unit, + val eventSink: (CreatePollEvent) -> Unit, ) { enum class Mode { New, diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollStateProvider.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollStateProvider.kt index f1f345b468..20d9f0d557 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollStateProvider.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt index ce6a8682b6..3abf3718af 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -35,6 +36,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.poll.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.components.dialogs.SaveChangesDialog import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -60,21 +62,21 @@ fun CreatePollView( ) { val coroutineScope = rememberCoroutineScope() - val navBack = { state.eventSink(CreatePollEvents.ConfirmNavBack) } + val navBack = { state.eventSink(CreatePollEvent.ConfirmNavBack) } BackHandler(onBack = navBack) if (state.showBackConfirmation) { - ConfirmationDialog( - content = stringResource(id = R.string.screen_create_poll_cancel_confirmation_content_android), - onSubmitClick = { state.eventSink(CreatePollEvents.NavBack) }, - onDismiss = { state.eventSink(CreatePollEvents.HideConfirmation) } + SaveChangesDialog( + onSaveClick = { state.eventSink(CreatePollEvent.Save) }, + onDiscardClick = { state.eventSink(CreatePollEvent.NavBack) }, + onDismiss = { state.eventSink(CreatePollEvent.HideConfirmation) }, ) } if (state.showDeleteConfirmation) { ConfirmationDialog( title = stringResource(id = R.string.screen_edit_poll_delete_confirmation_title), content = stringResource(id = R.string.screen_edit_poll_delete_confirmation), - onSubmitClick = { state.eventSink(CreatePollEvents.Delete(confirmed = true)) }, - onDismiss = { state.eventSink(CreatePollEvents.HideConfirmation) } + onSubmitClick = { state.eventSink(CreatePollEvent.Delete(confirmed = true)) }, + onDismiss = { state.eventSink(CreatePollEvent.HideConfirmation) } ) } val questionFocusRequester = remember { FocusRequester() } @@ -89,7 +91,7 @@ fun CreatePollView( mode = state.mode, saveEnabled = state.canSave, onBackClick = navBack, - onSaveClick = { state.eventSink(CreatePollEvents.Save) } + onSaveClick = { state.eventSink(CreatePollEvent.Save) } ) }, ) { paddingValues -> @@ -110,7 +112,7 @@ fun CreatePollView( label = stringResource(id = R.string.screen_create_poll_question_desc), value = state.question, onValueChange = { - state.eventSink(CreatePollEvents.SetQuestion(it)) + state.eventSink(CreatePollEvent.SetQuestion(it)) }, modifier = Modifier .focusRequester(questionFocusRequester) @@ -129,7 +131,7 @@ fun CreatePollView( TextField( value = answer.text, onValueChange = { - state.eventSink(CreatePollEvents.SetAnswer(index, it)) + state.eventSink(CreatePollEvent.SetAnswer(index, it)) }, modifier = Modifier .then(if (isLastItem) Modifier.focusRequester(answerFocusRequester) else Modifier) @@ -143,7 +145,7 @@ fun CreatePollView( imageVector = CompoundIcons.Delete(), contentDescription = stringResource(R.string.screen_create_poll_delete_option_a11y, answer.text), modifier = Modifier.clickable(answer.canDelete) { - state.eventSink(CreatePollEvents.RemoveAnswer(index)) + state.eventSink(CreatePollEvent.RemoveAnswer(index)) }, ) }, @@ -159,7 +161,7 @@ fun CreatePollView( ), style = ListItemStyle.Primary, onClick = { - state.eventSink(CreatePollEvents.AddAnswer) + state.eventSink(CreatePollEvent.AddAnswer) coroutineScope.launch(Dispatchers.Main) { lazyListState.animateScrollToItem(state.answers.size + 1) answerFocusRequester.requestFocus() @@ -179,7 +181,7 @@ fun CreatePollView( ), onClick = { state.eventSink( - CreatePollEvents.SetPollKind( + CreatePollEvent.SetPollKind( if (state.pollKind == PollKind.Disclosed) PollKind.Undisclosed else PollKind.Disclosed ) ) @@ -189,7 +191,7 @@ fun CreatePollView( ListItem( headlineContent = { Text(text = stringResource(id = CommonStrings.action_delete_poll)) }, style = ListItemStyle.Destructive, - onClick = { state.eventSink(CreatePollEvents.Delete(confirmed = false)) }, + onClick = { state.eventSink(CreatePollEvent.Delete(confirmed = false)) }, ) } } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/DefaultCreatePollEntryPoint.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/DefaultCreatePollEntryPoint.kt index 3c76aad7c8..7566a5dcf0 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/DefaultCreatePollEntryPoint.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/DefaultCreatePollEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,28 +10,21 @@ package io.element.android.features.poll.impl.create import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.poll.api.create.CreatePollEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultCreatePollEntryPoint : CreatePollEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): CreatePollEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : CreatePollEntryPoint.NodeBuilder { - override fun params(params: CreatePollEntryPoint.Params): CreatePollEntryPoint.NodeBuilder { - plugins += CreatePollNode.Inputs(timelineMode = params.timelineMode, mode = params.mode) - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: CreatePollEntryPoint.Params, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf(CreatePollNode.Inputs(timelineMode = params.timelineMode, mode = params.mode)) + ) } } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/PollFormState.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/PollFormState.kt index 34b9d6206b..46045d711d 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/PollFormState.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/PollFormState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt index ee53ca7826..d3f177736a 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/DefaultPollHistoryEntryPoint.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/DefaultPollHistoryEntryPoint.kt index 8c89a47c65..920e2db79f 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/DefaultPollHistoryEntryPoint.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/DefaultPollHistoryEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,12 +12,10 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.poll.api.history.PollHistoryEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultPollHistoryEntryPoint : PollHistoryEntryPoint { override fun createNode(parentNode: Node, buildContext: BuildContext): Node { return parentNode.createNode(buildContext) diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryEvents.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryEvents.kt index 1e063887a5..14ce13d67a 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryEvents.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryFlowNode.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryFlowNode.kt index 19142508a1..a71edeb859 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryFlowNode.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -53,18 +54,18 @@ class PollHistoryFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { is NavTarget.EditPoll -> { - createPollEntryPoint.nodeBuilder(this, buildContext) - .params( - CreatePollEntryPoint.Params( + createPollEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = CreatePollEntryPoint.Params( timelineMode = Timeline.Mode.Live, mode = CreatePollMode.EditPoll(eventId = navTarget.pollStartEventId) - ) ) - .build() + ) } NavTarget.Root -> { val callback = object : PollHistoryNode.Callback { - override fun onEditPoll(pollStartEventId: EventId) { + override fun navigateToEditPoll(pollStartEventId: EventId) { backstack.push(NavTarget.EditPoll(pollStartEventId)) } } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryNode.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryNode.kt index 3fdfdb921f..ba1018e65f 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryNode.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,10 +13,10 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.EventId @@ -30,19 +31,17 @@ class PollHistoryNode( plugins = plugins, ) { interface Callback : Plugin { - fun onEditPoll(pollStartEventId: EventId) + fun navigateToEditPoll(pollStartEventId: EventId) } - private fun onEditPoll(pollStartEventId: EventId) { - plugins().forEach { it.onEditPoll(pollStartEventId) } - } + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { PollHistoryView( state = presenter.present(), modifier = modifier, - onEditPoll = ::onEditPoll, + onEditPoll = callback::navigateToEditPoll, goBack = this::navigateUp, ) } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt index 15bc803f51..434e2ed18c 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -62,7 +63,7 @@ class PollHistoryPresenter( } } val coroutineScope = rememberCoroutineScope() - fun handleEvents(event: PollHistoryEvents) { + fun handleEvent(event: PollHistoryEvents) { when (event) { is PollHistoryEvents.LoadMore -> { coroutineScope.loadMore(timeline) @@ -88,7 +89,7 @@ class PollHistoryPresenter( hasMoreToLoad = paginationState.hasMoreToLoad, pollHistoryItems = pollHistoryItems, activeFilter = activeFilter, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryState.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryState.kt index 85d1324bcd..bddfd153f6 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryState.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryStateProvider.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryStateProvider.kt index 2d126ce7cf..a08550f2a1 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryStateProvider.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryView.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryView.kt index 0ff1ef57a5..9a654c2abb 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryView.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryFilter.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryFilter.kt index d8eb122785..51449a7bd5 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryFilter.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryFilter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItem.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItem.kt index 949ba5fb3c..b688000789 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItem.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItems.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItems.kt index 28ca67ac40..c1df275a9e 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItems.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItems.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItemsFactory.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItemsFactory.kt index c895a2c16c..5edd99c0c6 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItemsFactory.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/model/PollHistoryItemsFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/model/DefaultPollContentStateFactory.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/model/DefaultPollContentStateFactory.kt index 8647d80f23..f781b9474d 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/model/DefaultPollContentStateFactory.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/model/DefaultPollContentStateFactory.kt @@ -1,14 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.poll.impl.model import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.poll.api.pollcontent.PollAnswerItem import io.element.android.features.poll.api.pollcontent.PollContentState import io.element.android.features.poll.api.pollcontent.PollContentStateFactory @@ -20,7 +20,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.PollContent import kotlinx.collections.immutable.toImmutableList @ContributesBinding(RoomScope::class) -@Inject class DefaultPollContentStateFactory( private val matrixClient: MatrixClient, ) : PollContentStateFactory { diff --git a/features/poll/impl/src/main/res/values-hr/translations.xml b/features/poll/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..adc11a1be7 --- /dev/null +++ b/features/poll/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,20 @@ + + + "Dodaj mogućnost" + "Prikaži rezultate tek nakon završetka ankete" + "Sakrij glasove" + "Mogućnost %1$d" + "Vaše promjene nisu spremljene. Jeste li sigurni da se želite vratiti?" + "Izbriši mogućnost %1$s" + "Pitanje ili tema" + "O čemu se radi u anketi?" + "Izradi anketu" + "Jeste li sigurni da želite izbrisati ovu anketu?" + "Izbriši anketu" + "Uredi anketu" + "Ne mogu pronaći nijednu tekuću anketu." + "Ne mogu pronaći nijednu prijašnju anketu." + "Tekuće" + "Prijašnje" + "Ankete" + diff --git a/features/poll/impl/src/main/res/values-uz/translations.xml b/features/poll/impl/src/main/res/values-uz/translations.xml index 8ea1c4b827..28f496f3dd 100644 --- a/features/poll/impl/src/main/res/values-uz/translations.xml +++ b/features/poll/impl/src/main/res/values-uz/translations.xml @@ -5,6 +5,7 @@ "Ovozlarni yashirish" "Variant%1$d" "Oʻzgarishlar saqlanmadi. Haqiqatan ham orqaga qaytmoqchimisiz?" + "%1$s variantini o‘chirish" "Savol yoki mavzu" "So\'rovnoma nima haqida?" "So‘rovnoma yaratish" diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/PollFixtures.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/PollFixtures.kt index 182e7cd86b..db6c8e849f 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/PollFixtures.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/PollFixtures.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt index 6bec98f1b9..dee0268c1c 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -103,15 +104,15 @@ class CreatePollPresenterTest { val initial = awaitItem() assertThat(initial.canSave).isFalse() - initial.eventSink(CreatePollEvents.SetQuestion("A question?")) + initial.eventSink(CreatePollEvent.SetQuestion("A question?")) val questionSet = awaitItem() assertThat(questionSet.canSave).isFalse() - questionSet.eventSink(CreatePollEvents.SetAnswer(0, "Answer 1")) + questionSet.eventSink(CreatePollEvent.SetAnswer(0, "Answer 1")) val answer1Set = awaitItem() assertThat(answer1Set.canSave).isFalse() - answer1Set.eventSink(CreatePollEvents.SetAnswer(1, "Answer 2")) + answer1Set.eventSink(CreatePollEvent.SetAnswer(1, "Answer 2")) val answer2Set = awaitItem() assertThat(answer2Set.canSave).isTrue() } @@ -132,11 +133,11 @@ class CreatePollPresenterTest { presenter.present() }.test { val initial = awaitItem() - initial.eventSink(CreatePollEvents.SetQuestion("A question?")) - initial.eventSink(CreatePollEvents.SetAnswer(0, "Answer 1")) - initial.eventSink(CreatePollEvents.SetAnswer(1, "Answer 2")) + initial.eventSink(CreatePollEvent.SetQuestion("A question?")) + initial.eventSink(CreatePollEvent.SetAnswer(0, "Answer 1")) + initial.eventSink(CreatePollEvent.SetAnswer(1, "Answer 2")) skipItems(3) - initial.eventSink(CreatePollEvents.Save) + initial.eventSink(CreatePollEvent.Save) delay(1) // Wait for the coroutine to finish createPollResult.assertions().isCalledOnce() .with( @@ -181,10 +182,10 @@ class CreatePollPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - awaitDefaultItem().eventSink(CreatePollEvents.SetQuestion("A question?")) - awaitItem().eventSink(CreatePollEvents.SetAnswer(0, "Answer 1")) - awaitItem().eventSink(CreatePollEvents.SetAnswer(1, "Answer 2")) - awaitItem().eventSink(CreatePollEvents.Save) + awaitDefaultItem().eventSink(CreatePollEvent.SetQuestion("A question?")) + awaitItem().eventSink(CreatePollEvent.SetAnswer(0, "Answer 1")) + awaitItem().eventSink(CreatePollEvent.SetAnswer(1, "Answer 2")) + awaitItem().eventSink(CreatePollEvent.Save) delay(1) // Wait for the coroutine to finish createPollResult.assertions().isCalledOnce() assertThat(fakeAnalyticsService.capturedEvents).isEmpty() @@ -209,20 +210,20 @@ class CreatePollPresenterTest { }.test { awaitDefaultItem() awaitPollLoaded().apply { - eventSink(CreatePollEvents.SetQuestion("Changed question")) + eventSink(CreatePollEvent.SetQuestion("Changed question")) } awaitItem().apply { - eventSink(CreatePollEvents.SetAnswer(0, "Changed answer 1")) + eventSink(CreatePollEvent.SetAnswer(0, "Changed answer 1")) } awaitItem().apply { - eventSink(CreatePollEvents.SetAnswer(1, "Changed answer 2")) + eventSink(CreatePollEvent.SetAnswer(1, "Changed answer 2")) } awaitPollLoaded( newQuestion = "Changed question", newAnswer1 = "Changed answer 1", newAnswer2 = "Changed answer 2", ).apply { - eventSink(CreatePollEvents.Save) + eventSink(CreatePollEvent.Save) } advanceUntilIdle() // Wait for the coroutine to finish @@ -274,8 +275,8 @@ class CreatePollPresenterTest { presenter.present() }.test { awaitDefaultItem() - awaitPollLoaded().eventSink(CreatePollEvents.SetAnswer(0, "A")) - awaitPollLoaded(newAnswer1 = "A").eventSink(CreatePollEvents.Save) + awaitPollLoaded().eventSink(CreatePollEvent.SetAnswer(0, "A")) + awaitPollLoaded(newAnswer1 = "A").eventSink(CreatePollEvent.Save) advanceUntilIdle() // Wait for the coroutine to finish editPollLambda.assertions().isCalledOnce() assertThat(fakeAnalyticsService.capturedEvents).isEmpty() @@ -295,12 +296,12 @@ class CreatePollPresenterTest { val initial = awaitItem() assertThat(initial.answers.size).isEqualTo(2) - initial.eventSink(CreatePollEvents.AddAnswer) + initial.eventSink(CreatePollEvent.AddAnswer) val answerAdded = awaitItem() assertThat(answerAdded.answers.size).isEqualTo(3) assertThat(answerAdded.answers[2].text).isEmpty() - initial.eventSink(CreatePollEvents.RemoveAnswer(2)) + initial.eventSink(CreatePollEvent.RemoveAnswer(2)) val answerRemoved = awaitItem() assertThat(answerRemoved.answers.size).isEqualTo(2) } @@ -313,7 +314,7 @@ class CreatePollPresenterTest { presenter.present() }.test { val initial = awaitItem() - initial.eventSink(CreatePollEvents.SetQuestion("A question?")) + initial.eventSink(CreatePollEvent.SetQuestion("A question?")) val questionSet = awaitItem() assertThat(questionSet.question).isEqualTo("A question?") } @@ -326,7 +327,7 @@ class CreatePollPresenterTest { presenter.present() }.test { val initial = awaitItem() - initial.eventSink(CreatePollEvents.SetAnswer(0, "This is answer 1")) + initial.eventSink(CreatePollEvent.SetAnswer(0, "This is answer 1")) val answerSet = awaitItem() assertThat(answerSet.answers.first().text).isEqualTo("This is answer 1") } @@ -339,7 +340,7 @@ class CreatePollPresenterTest { presenter.present() }.test { val initial = awaitItem() - initial.eventSink(CreatePollEvents.SetPollKind(PollKind.Undisclosed)) + initial.eventSink(CreatePollEvent.SetPollKind(PollKind.Undisclosed)) val kindSet = awaitItem() assertThat(kindSet.pollKind).isEqualTo(PollKind.Undisclosed) } @@ -354,10 +355,10 @@ class CreatePollPresenterTest { val initial = awaitItem() assertThat(initial.canAddAnswer).isTrue() repeat(17) { - initial.eventSink(CreatePollEvents.AddAnswer) + initial.eventSink(CreatePollEvent.AddAnswer) assertThat(awaitItem().canAddAnswer).isTrue() } - initial.eventSink(CreatePollEvents.AddAnswer) + initial.eventSink(CreatePollEvent.AddAnswer) assertThat(awaitItem().canAddAnswer).isFalse() } } @@ -370,7 +371,7 @@ class CreatePollPresenterTest { }.test { val initial = awaitItem() assertThat(initial.answers.all { it.canDelete }).isFalse() - initial.eventSink(CreatePollEvents.AddAnswer) + initial.eventSink(CreatePollEvent.AddAnswer) assertThat(awaitItem().answers.all { it.canDelete }).isTrue() } } @@ -382,7 +383,7 @@ class CreatePollPresenterTest { presenter.present() }.test { val initial = awaitItem() - initial.eventSink(CreatePollEvents.SetAnswer(0, "A".repeat(241))) + initial.eventSink(CreatePollEvent.SetAnswer(0, "A".repeat(241))) assertThat(awaitItem().answers.first().text.length).isEqualTo(240) } } @@ -395,7 +396,7 @@ class CreatePollPresenterTest { }.test { val initial = awaitItem() assertThat(navUpInvocationsCount).isEqualTo(0) - initial.eventSink(CreatePollEvents.NavBack) + initial.eventSink(CreatePollEvent.NavBack) assertThat(navUpInvocationsCount).isEqualTo(1) } } @@ -409,7 +410,7 @@ class CreatePollPresenterTest { val initial = awaitItem() assertThat(navUpInvocationsCount).isEqualTo(0) assertThat(initial.showBackConfirmation).isFalse() - initial.eventSink(CreatePollEvents.ConfirmNavBack) + initial.eventSink(CreatePollEvent.ConfirmNavBack) assertThat(navUpInvocationsCount).isEqualTo(1) } } @@ -421,11 +422,11 @@ class CreatePollPresenterTest { presenter.present() }.test { val initial = awaitItem() - initial.eventSink(CreatePollEvents.SetQuestion("Non blank")) + initial.eventSink(CreatePollEvent.SetQuestion("Non blank")) assertThat(awaitItem().showBackConfirmation).isFalse() - initial.eventSink(CreatePollEvents.ConfirmNavBack) + initial.eventSink(CreatePollEvent.ConfirmNavBack) assertThat(awaitItem().showBackConfirmation).isTrue() - initial.eventSink(CreatePollEvents.HideConfirmation) + initial.eventSink(CreatePollEvent.HideConfirmation) assertThat(awaitItem().showBackConfirmation).isFalse() assertThat(navUpInvocationsCount).isEqualTo(0) } @@ -441,7 +442,7 @@ class CreatePollPresenterTest { val loaded = awaitPollLoaded() assertThat(navUpInvocationsCount).isEqualTo(0) assertThat(loaded.showBackConfirmation).isFalse() - loaded.eventSink(CreatePollEvents.ConfirmNavBack) + loaded.eventSink(CreatePollEvent.ConfirmNavBack) assertThat(navUpInvocationsCount).isEqualTo(1) } } @@ -454,11 +455,11 @@ class CreatePollPresenterTest { }.test { awaitDefaultItem() val loaded = awaitPollLoaded() - loaded.eventSink(CreatePollEvents.SetQuestion("CHANGED")) + loaded.eventSink(CreatePollEvent.SetQuestion("CHANGED")) assertThat(awaitItem().showBackConfirmation).isFalse() - loaded.eventSink(CreatePollEvents.ConfirmNavBack) + loaded.eventSink(CreatePollEvent.ConfirmNavBack) assertThat(awaitItem().showBackConfirmation).isTrue() - loaded.eventSink(CreatePollEvents.HideConfirmation) + loaded.eventSink(CreatePollEvent.HideConfirmation) assertThat(awaitItem().showBackConfirmation).isFalse() assertThat(navUpInvocationsCount).isEqualTo(0) } @@ -473,7 +474,7 @@ class CreatePollPresenterTest { presenter.present() }.test { awaitDefaultItem() - awaitPollLoaded().eventSink(CreatePollEvents.Delete(confirmed = false)) + awaitPollLoaded().eventSink(CreatePollEvent.Delete(confirmed = false)) awaitDeleteConfirmation() assert(redactEventLambda).isNeverCalled() } @@ -488,8 +489,8 @@ class CreatePollPresenterTest { presenter.present() }.test { awaitDefaultItem() - awaitPollLoaded().eventSink(CreatePollEvents.Delete(confirmed = false)) - awaitDeleteConfirmation().eventSink(CreatePollEvents.HideConfirmation) + awaitPollLoaded().eventSink(CreatePollEvent.Delete(confirmed = false)) + awaitDeleteConfirmation().eventSink(CreatePollEvent.HideConfirmation) awaitPollLoaded().apply { assertThat(showDeleteConfirmation).isFalse() } @@ -506,8 +507,8 @@ class CreatePollPresenterTest { presenter.present() }.test { awaitDefaultItem() - awaitPollLoaded().eventSink(CreatePollEvents.Delete(confirmed = false)) - awaitDeleteConfirmation().eventSink(CreatePollEvents.Delete(confirmed = true)) + awaitPollLoaded().eventSink(CreatePollEvent.Delete(confirmed = false)) + awaitDeleteConfirmation().eventSink(CreatePollEvent.Delete(confirmed = true)) awaitPollLoaded().apply { assertThat(showDeleteConfirmation).isFalse() } diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/DefaultCreatePollEntryPointTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/DefaultCreatePollEntryPointTest.kt index bf77cb4535..573c7b1950 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/DefaultCreatePollEntryPointTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/DefaultCreatePollEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -53,9 +54,11 @@ class DefaultCreatePollEntryPointTest { timelineMode = Timeline.Mode.Live, mode = CreatePollMode.NewPoll, ) - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .params(params) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + params = params, + ) assertThat(result).isInstanceOf(CreatePollNode::class.java) assertThat(result.plugins).contains(CreatePollNode.Inputs(timelineMode = params.timelineMode, mode = params.mode)) } diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/PollFormStateSaverTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/PollFormStateSaverTest.kt index bab6171477..86e4b28676 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/PollFormStateSaverTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/PollFormStateSaverTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/PollFormStateTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/PollFormStateTest.kt index 9594903c69..8f6bc32ea9 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/PollFormStateTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/PollFormStateTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/DefaultPollHistoryEntryPointTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/DefaultPollHistoryEntryPointTest.kt index dfab33e837..d0d0490e44 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/DefaultPollHistoryEntryPointTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/DefaultPollHistoryEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,11 +10,9 @@ package io.element.android.features.poll.impl.history import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node import com.bumble.appyx.testing.junit4.util.MainDispatcherRule import com.google.common.truth.Truth.assertThat -import io.element.android.features.poll.api.create.CreatePollEntryPoint -import io.element.android.tests.testutils.lambda.lambdaError +import io.element.android.features.poll.test.create.FakeCreatePollEntryPoint import io.element.android.tests.testutils.node.TestParentNode import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -33,9 +32,7 @@ class DefaultPollHistoryEntryPointTest { PollHistoryFlowNode( buildContext = buildContext, plugins = plugins, - createPollEntryPoint = object : CreatePollEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext) = lambdaError() - } + createPollEntryPoint = FakeCreatePollEntryPoint(), ) } val result = entryPoint.createNode(parentNode, BuildContext.root(null)) diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt index cac5fae135..8d4cedc6a8 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryViewTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryViewTest.kt index 54df066bbe..1ff25a0a81 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryViewTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/history/PollHistoryViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/pollcontent/PollContentStateFactoryTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/pollcontent/PollContentStateFactoryTest.kt index 4e56c4f48d..277508681e 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/pollcontent/PollContentStateFactoryTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/pollcontent/PollContentStateFactoryTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -219,6 +220,7 @@ class PollContentStateFactoryTest { votes = votes, endTime = endTime, isEdited = false, + threadInfo = null, ) private fun aPollContentState( diff --git a/features/poll/test/build.gradle.kts b/features/poll/test/build.gradle.kts index 150a0bd3fc..a3779809d7 100644 --- a/features/poll/test/build.gradle.kts +++ b/features/poll/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,4 +18,5 @@ dependencies { implementation(projects.libraries.matrix.api) api(projects.features.poll.api) implementation(libs.kotlinx.collections.immutable) + implementation(projects.tests.testutils) } diff --git a/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/actions/FakeEndPollAction.kt b/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/actions/FakeEndPollAction.kt index 872110d0b6..1a0b8b29d3 100644 --- a/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/actions/FakeEndPollAction.kt +++ b/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/actions/FakeEndPollAction.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/actions/FakeSendPollResponseAction.kt b/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/actions/FakeSendPollResponseAction.kt index f77d974178..1a779d5cab 100644 --- a/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/actions/FakeSendPollResponseAction.kt +++ b/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/actions/FakeSendPollResponseAction.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/create/FakeCreatePollEntryPoint.kt b/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/create/FakeCreatePollEntryPoint.kt new file mode 100644 index 0000000000..bad968d157 --- /dev/null +++ b/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/create/FakeCreatePollEntryPoint.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.poll.test.create + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.poll.api.create.CreatePollEntryPoint +import io.element.android.features.poll.api.create.CreatePollEntryPoint.Params +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeCreatePollEntryPoint : CreatePollEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: Params, + ): Node = lambdaError() +} diff --git a/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/history/FakePollHistoryEntryPoint.kt b/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/history/FakePollHistoryEntryPoint.kt new file mode 100644 index 0000000000..2c211c8b71 --- /dev/null +++ b/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/history/FakePollHistoryEntryPoint.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.poll.test.history + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.poll.api.history.PollHistoryEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakePollHistoryEntryPoint : PollHistoryEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + ): Node = lambdaError() +} diff --git a/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/pollcontent/FakePollContentStateFactory.kt b/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/pollcontent/FakePollContentStateFactory.kt index 701d2e4640..87755afaf0 100644 --- a/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/pollcontent/FakePollContentStateFactory.kt +++ b/features/poll/test/src/main/kotlin/io/element/android/features/poll/test/pollcontent/FakePollContentStateFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/api/build.gradle.kts b/features/preferences/api/build.gradle.kts index 91538d5709..b5a08b9435 100644 --- a/features/preferences/api/build.gradle.kts +++ b/features/preferences/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/CacheService.kt b/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/CacheService.kt index f18a77a2b5..82fcd1053f 100644 --- a/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/CacheService.kt +++ b/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/CacheService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt b/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt index c0affde2df..5a59d9be8a 100644 --- a/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt +++ b/features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -31,19 +32,19 @@ interface PreferencesEntryPoint : FeatureEntryPoint { data class Params(val initialElement: InitialTarget) : NodeInputs - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - - interface NodeBuilder { - fun params(params: Params): NodeBuilder - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: Params, + callback: Callback, + ): Node interface Callback : Plugin { - fun onAddAccount() - fun onOpenBugReport() - fun onSecureBackupClick() - fun onOpenRoomNotificationSettings(roomId: RoomId) - fun navigateTo(roomId: RoomId, eventId: EventId) + fun navigateToAddAccount() + fun navigateToLinkNewDevice() + fun navigateToBugReport() + fun navigateToSecureBackup() + fun navigateToRoomNotificationSettings(roomId: RoomId) + fun navigateToEvent(roomId: RoomId, eventId: EventId) } } diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts index eb057a9d53..ad28c90966 100644 --- a/features/preferences/impl/build.gradle.kts +++ b/features/preferences/impl/build.gradle.kts @@ -4,9 +4,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -72,6 +73,7 @@ dependencies { implementation(projects.features.rageshake.api) implementation(projects.features.lockscreen.api) implementation(projects.features.analytics.api) + implementation(projects.features.enterprise.api) implementation(projects.features.licenses.api) implementation(projects.features.logout.api) implementation(projects.features.deactivation.api) @@ -83,6 +85,7 @@ dependencies { implementation(projects.services.toolbox.api) implementation(libs.datetime) implementation(libs.coil.compose) + implementation(libs.color.picker) implementation(libs.androidx.browser) implementation(libs.androidx.datastore.preferences) api(projects.features.preferences.api) @@ -100,12 +103,19 @@ dependencies { testImplementation(projects.libraries.preferences.test) testImplementation(projects.libraries.push.test) testImplementation(projects.libraries.pushstore.test) + testImplementation(projects.libraries.roomselect.test) + testImplementation(projects.libraries.troubleshoot.test) + testImplementation(projects.features.deactivation.test) + testImplementation(projects.features.enterprise.test) testImplementation(projects.features.invite.test) + testImplementation(projects.features.licenses.test) + testImplementation(projects.features.lockscreen.test) testImplementation(projects.features.rageshake.test) testImplementation(projects.features.logout.test) testImplementation(projects.libraries.indicator.test) testImplementation(projects.libraries.pushproviders.test) testImplementation(projects.libraries.sessionStorage.test) + testImplementation(projects.services.appnavstate.impl) testImplementation(projects.services.analytics.test) testImplementation(projects.services.toolbox.test) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultCacheService.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultCacheService.kt index da42cf87dd..34ea62864c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultCacheService.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultCacheService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.features.preferences.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.features.preferences.api.CacheService import io.element.android.libraries.matrix.api.core.SessionId @@ -18,7 +18,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DefaultCacheService : CacheService { private val _clearedCacheEventFlow = MutableSharedFlow(0) override val clearedCacheEventFlow: Flow = _clearedCacheEventFlow diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultPreferencesEntryPoint.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultPreferencesEntryPoint.kt index d0efc2107e..4348b33756 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultPreferencesEntryPoint.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/DefaultPreferencesEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,34 +10,23 @@ package io.element.android.features.preferences.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.preferences.api.PreferencesEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultPreferencesEntryPoint : PreferencesEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): PreferencesEntryPoint.NodeBuilder { - return object : PreferencesEntryPoint.NodeBuilder { - val plugins = ArrayList() - - override fun params(params: PreferencesEntryPoint.Params): PreferencesEntryPoint.NodeBuilder { - plugins += params - return this - } - - override fun callback(callback: PreferencesEntryPoint.Callback): PreferencesEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: PreferencesEntryPoint.Params, + callback: PreferencesEntryPoint.Callback, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf(params, callback) + ) } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt index e4ba87c43a..c646923c77 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,6 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push @@ -38,6 +38,7 @@ import io.element.android.features.preferences.impl.user.editprofile.EditUserPro import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.appyx.canPop +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.EventId @@ -116,63 +117,69 @@ class PreferencesFlowNode( data object OssLicenses : NavTarget } + private val callback: PreferencesEntryPoint.Callback = callback() + override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Root -> { val callback = object : PreferencesRootNode.Callback { - override fun onAddAccount() { - plugins().forEach { it.onAddAccount() } + override fun navigateToAddAccount() { + callback.navigateToAddAccount() } - override fun onOpenBugReport() { - plugins().forEach { it.onOpenBugReport() } + override fun navigateToBugReport() { + callback.navigateToBugReport() } - override fun onSecureBackupClick() { - plugins().forEach { it.onSecureBackupClick() } + override fun navigateToSecureBackup() { + callback.navigateToSecureBackup() } - override fun onOpenAnalytics() { + override fun navigateToAnalyticsSettings() { backstack.push(NavTarget.AnalyticsSettings) } - override fun onOpenAbout() { + override fun navigateToAbout() { backstack.push(NavTarget.About) } - override fun onOpenDeveloperSettings() { + override fun navigateToDeveloperSettings() { backstack.push(NavTarget.DeveloperSettings) } - override fun onOpenNotificationSettings() { + override fun navigateToNotificationSettings() { backstack.push(NavTarget.NotificationSettings) } - override fun onOpenLockScreenSettings() { + override fun navigateToLockScreenSettings() { backstack.push(NavTarget.LockScreenSettings) } - override fun onOpenAdvancedSettings() { + override fun navigateToAdvancedSettings() { backstack.push(NavTarget.AdvancedSettings) } - override fun onOpenLabs() { + override fun navigateToLabs() { backstack.push(NavTarget.Labs) } - override fun onOpenUserProfile(matrixUser: MatrixUser) { + override fun navigateToLinkNewDevice() { + callback.navigateToLinkNewDevice() + } + + override fun navigateToUserProfile(matrixUser: MatrixUser) { backstack.push(NavTarget.UserProfile(matrixUser)) } - override fun onOpenBlockedUsers() { + override fun navigateToBlockedUsers() { backstack.push(NavTarget.BlockedUsers) } - override fun onSignOutClick() { + override fun startSignOutFlow() { backstack.push(NavTarget.SignOut) } - override fun onOpenAccountDeactivation() { + override fun startAccountDeactivationFlow() { backstack.push(NavTarget.AccountDeactivation) } } @@ -180,18 +187,27 @@ class PreferencesFlowNode( } NavTarget.DeveloperSettings -> { val developerSettingsCallback = object : DeveloperSettingsNode.Callback { - override fun onPushHistoryClick() { + override fun navigateToPushHistory() { backstack.push(NavTarget.PushHistory) } + + override fun onDone() { + backstack.pop() + } } createNode(buildContext, listOf(developerSettingsCallback)) } NavTarget.Labs -> { - createNode(buildContext) + val callback = object : LabsNode.Callback { + override fun onDone() { + backstack.pop() + } + } + createNode(buildContext, listOf(callback)) } NavTarget.About -> { val callback = object : AboutNode.Callback { - override fun openOssLicenses() { + override fun navigateToOssLicenses() { backstack.push(NavTarget.OssLicenses) } } @@ -202,19 +218,21 @@ class PreferencesFlowNode( } NavTarget.NotificationSettings -> { val notificationSettingsCallback = object : NotificationSettingsNode.Callback { - override fun editDefaultNotificationMode(isOneToOne: Boolean) { + override fun navigateToEditDefaultNotificationSetting(isOneToOne: Boolean) { backstack.push(NavTarget.EditDefaultNotificationSetting(isOneToOne)) } - override fun onTroubleshootNotificationsClick() { + override fun navigateToTroubleshootNotifications() { backstack.push(NavTarget.TroubleshootNotifications) } } createNode(buildContext, listOf(notificationSettingsCallback)) } NavTarget.TroubleshootNotifications -> { - notificationTroubleShootEntryPoint.nodeBuilder(this, buildContext) - .callback(object : NotificationTroubleShootEntryPoint.Callback { + notificationTroubleShootEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + callback = object : NotificationTroubleShootEntryPoint.Callback { override fun onDone() { if (backstack.canPop()) { backstack.pop() @@ -223,15 +241,17 @@ class PreferencesFlowNode( } } - override fun openIgnoredUsers() { + override fun navigateToBlockedUsers() { backstack.push(NavTarget.BlockedUsers) } - }) - .build() + }, + ) } NavTarget.PushHistory -> { - pushHistoryEntryPoint.nodeBuilder(this, buildContext) - .callback(object : PushHistoryEntryPoint.Callback { + pushHistoryEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + callback = object : PushHistoryEntryPoint.Callback { override fun onDone() { if (backstack.canPop()) { backstack.pop() @@ -240,16 +260,16 @@ class PreferencesFlowNode( } } - override fun navigateTo(roomId: RoomId, eventId: EventId) { - plugins().forEach { it.navigateTo(roomId, eventId) } + override fun navigateToEvent(roomId: RoomId, eventId: EventId) { + callback.navigateToEvent(roomId, eventId) } - }) - .build() + }, + ) } is NavTarget.EditDefaultNotificationSetting -> { val callback = object : EditDefaultNotificationSettingNode.Callback { - override fun openRoomNotificationSettings(roomId: RoomId) { - plugins().forEach { it.onOpenRoomNotificationSettings(roomId) } + override fun navigateToRoomNotificationSettings(roomId: RoomId) { + callback.navigateToRoomNotificationSettings(roomId) } } val input = EditDefaultNotificationSettingNode.Inputs(navTarget.isOneToOne) @@ -260,23 +280,39 @@ class PreferencesFlowNode( } is NavTarget.UserProfile -> { val inputs = EditUserProfileNode.Inputs(navTarget.matrixUser) - createNode(buildContext, listOf(inputs)) + val callback = object : EditUserProfileNode.Callback { + override fun onDone() { + backstack.pop() + } + } + createNode(buildContext, listOf(inputs, callback)) } NavTarget.LockScreenSettings -> { - lockScreenEntryPoint.nodeBuilder(this, buildContext, LockScreenEntryPoint.Target.Settings).build() + lockScreenEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + navTarget = LockScreenEntryPoint.Target.Settings, + callback = object : LockScreenEntryPoint.Callback { + override fun onSetupDone() { + // No op + } + } + ) } NavTarget.BlockedUsers -> { createNode(buildContext) } NavTarget.SignOut -> { val callBack: LogoutEntryPoint.Callback = object : LogoutEntryPoint.Callback { - override fun onChangeRecoveryKeyClick() { - plugins().forEach { it.onSecureBackupClick() } + override fun navigateToSecureBackup() { + callback.navigateToSecureBackup() } } - logoutEntryPoint.nodeBuilder(this, buildContext) - .callback(callBack) - .build() + logoutEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + callback = callBack, + ) } is NavTarget.OssLicenses -> { openSourceLicensesEntryPoint.createNode(this, buildContext) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutNode.kt index 37de32bab7..4ece133bcd 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -19,6 +20,7 @@ import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.SessionScope @ContributesNode(SessionScope::class) @@ -29,9 +31,11 @@ class AboutNode( private val presenter: AboutPresenter, ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun openOssLicenses() + fun navigateToOssLicenses() } + private val callback: Callback = callback() + private fun onElementLegalClick( activity: Activity, darkTheme: Boolean, @@ -51,9 +55,7 @@ class AboutNode( onElementLegalClick = { elementLegal -> onElementLegalClick(activity, isDark, elementLegal) }, - onOpenSourceLicensesClick = { - plugins.filterIsInstance().forEach { it.openOssLicenses() } - }, + onOpenSourceLicensesClick = callback::navigateToOssLicenses, modifier = modifier ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutPresenter.kt index f36dc50543..6abf896f1f 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutState.kt index 2c46bf2419..165585bd87 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutState.kt @@ -1,12 +1,15 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.preferences.impl.about +import kotlinx.collections.immutable.ImmutableList + data class AboutState( - val elementLegals: List, + val elementLegals: ImmutableList, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutStateProvider.kt index 6d6a4813df..561fe53a49 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutStateProvider.kt @@ -1,13 +1,15 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.preferences.impl.about import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import kotlinx.collections.immutable.toImmutableList open class AboutStateProvider : PreviewParameterProvider { override val values: Sequence @@ -19,5 +21,5 @@ open class AboutStateProvider : PreviewParameterProvider { fun anAboutState( elementLegals: List = getAllLegals(), ) = AboutState( - elementLegals = elementLegals, + elementLegals = elementLegals.toImmutableList(), ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutView.kt index 9d2018f4a7..b71db181e8 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/about/AboutView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 a5de31f05d..0f3c25afea 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 @@ -1,7 +1,8 @@ /* - * Copyright 2021-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2021-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,6 +11,8 @@ package io.element.android.features.preferences.impl.about import androidx.annotation.StringRes import io.element.android.features.preferences.impl.BuildConfig import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf private const val COPYRIGHT_URL = BuildConfig.URL_COPYRIGHT private const val USE_POLICY_URL = BuildConfig.URL_ACCEPTABLE_USE @@ -24,8 +27,8 @@ sealed class ElementLegal( data object PrivacyPolicy : ElementLegal(CommonStrings.common_privacy_policy, PRIVACY_URL) } -fun getAllLegals(): List { - return listOf( +fun getAllLegals(): ImmutableList { + return persistentListOf( ElementLegal.Copyright, ElementLegal.AcceptableUsePolicy, ElementLegal.PrivacyPolicy, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt index d685402055..b3fb68fe05 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsNode.kt index adcf12cf42..e58706e9fe 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt index 6378b3038d..c2871e0be8 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -82,7 +83,7 @@ class AdvancedSettingsPresenter( }.collect() } - fun handleEvents(event: AdvancedSettingsEvents) { + fun handleEvent(event: AdvancedSettingsEvents) { when (event) { is AdvancedSettingsEvents.SetDeveloperModeEnabled -> sessionCoroutineScope.launch { appPreferencesStore.setDeveloperModeEnabled(event.enabled) @@ -117,7 +118,7 @@ class AdvancedSettingsPresenter( mediaOptimizationState = mediaOptimizationState, theme = themeOption, mediaPreviewConfigState = mediaPreviewConfigState, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt index 1251723f1c..6eb7414a29 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt index 055edd20d2..6cbe6e5c51 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt index e1c6ce259a..c2b51973d0 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -233,7 +234,7 @@ private fun VideoQualitySelectorDialog( supportingContent = { Text( text = subtitle, - style = ElementTheme.materialTypography.bodyMedium, + style = ElementTheme.typography.fontBodyMdRegular, color = ElementTheme.colors.textSecondary, ) }, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/MediaPreviewConfigStateStore.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/MediaPreviewConfigStateStore.kt index b2d7243ccc..d23168e29a 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/MediaPreviewConfigStateStore.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/MediaPreviewConfigStateStore.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.features.preferences.impl.advanced import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.runUpdatingState @@ -45,7 +45,6 @@ interface MediaPreviewConfigStateStore { @ContributesBinding(SessionScope::class) @SingleIn(SessionScope::class) -@Inject class DefaultMediaPreviewConfigStateStore( @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsNode.kt index 7f96e8f181..8931f7f6a8 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenter.kt index 4f833b783d..3affad55c4 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsState.kt index 7f0cbb4672..ba2b9dfb7e 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.features.preferences.impl.analytics import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesState -// Do not use default value, so no member get forgotten in the presenters. data class AnalyticsSettingsState( val analyticsPreferencesState: AnalyticsPreferencesState, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsStateProvider.kt index cbbf9839b9..9e15ea2f35 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsView.kt index d8cf70e3eb..ef84600e2e 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersEvents.kt index e3311862b8..0db5c4c576 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersNode.kt index 4c94a8dfb4..44e6a155b1 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt index 5fbbe3e797..fc150d0bfc 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -64,7 +65,7 @@ class BlockedUsersPresenter( } } - fun handleEvents(event: BlockedUsersEvents) { + fun handleEvent(event: BlockedUsersEvents) { when (event) { is BlockedUsersEvents.Unblock -> { pendingUserToUnblock = event.userId @@ -85,7 +86,7 @@ class BlockedUsersPresenter( return BlockedUsersState( blockedUsers = ignoredMatrixUser.toImmutableList(), unblockUserAction = unblockUserAction.value, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersState.kt index 79b0b0b1e8..8c478371ad 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersStateProvider.kt index ac344ba838..92cc8a0936 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersView.kt index 56b5dd709e..40e58022bd 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt index ced7b8d2b4..1804d7e070 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsEvents.kt @@ -1,12 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.preferences.impl.developer +import androidx.compose.ui.graphics.Color import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem import io.element.android.libraries.featureflag.ui.model.FeatureUiModel import io.element.android.libraries.matrix.api.tracing.TraceLogPack @@ -16,5 +18,8 @@ sealed interface DeveloperSettingsEvents { data class SetCustomElementCallBaseUrl(val baseUrl: String?) : DeveloperSettingsEvents data class SetTracingLogLevel(val logLevel: LogLevelItem) : DeveloperSettingsEvents data class ToggleTracingLogPack(val logPack: TraceLogPack, val enabled: Boolean) : DeveloperSettingsEvents + data class SetShowColorPicker(val show: Boolean) : DeveloperSettingsEvents + data class ChangeBrandColor(val color: Color?) : DeveloperSettingsEvents data object ClearCache : DeveloperSettingsEvents + data object VacuumStores : DeveloperSettingsEvents } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt index 6208d0123e..98c7d89633 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,10 +15,10 @@ import com.airbnb.android.showkase.models.Showkase import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.designsystem.showkase.getBrowserIntent import io.element.android.libraries.di.SessionScope @@ -29,14 +30,11 @@ class DeveloperSettingsNode( private val presenter: DeveloperSettingsPresenter, ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun onPushHistoryClick() + fun navigateToPushHistory() + fun onDone() } - private val callbacks = plugins() - - private fun onPushHistoryClick() { - callbacks.forEach { it.onPushHistoryClick() } - } + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { @@ -51,8 +49,8 @@ class DeveloperSettingsNode( state = state, modifier = modifier, onOpenShowkase = ::openShowkase, - onPushHistoryClick = ::onPushHistoryClick, - onBackClick = ::navigateUp + onPushHistoryClick = callback::navigateToPushHistory, + onBackClick = callback::onDone, ) } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt index fe7a3461b8..a0d96be540 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,33 +14,43 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.key -import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.snapshots.SnapshotStateMap +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshots.SnapshotStateList +import androidx.compose.ui.graphics.toArgb import dev.zacsweers.metro.Inject +import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.preferences.impl.developer.tracing.toLogLevel import io.element.android.features.preferences.impl.developer.tracing.toLogLevelItem +import io.element.android.features.preferences.impl.model.EnabledFeature import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase import io.element.android.features.preferences.impl.tasks.ComputeCacheSizeUseCase +import io.element.android.features.preferences.impl.tasks.VacuumStoresUseCase import io.element.android.features.rageshake.api.preferences.RageshakePreferencesState +import io.element.android.libraries.androidutils.filesize.FileSizeFormatter import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState -import io.element.android.libraries.core.bool.orFalse +import io.element.android.libraries.core.data.ByteUnit import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildType -import io.element.android.libraries.featureflag.api.Feature import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.ui.model.FeatureUiModel +import io.element.android.libraries.matrix.api.analytics.GetDatabaseSizesUseCase +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.preferences.api.store.AppPreferencesStore +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.map @@ -48,29 +59,36 @@ import java.net.URL @Inject class DeveloperSettingsPresenter( + private val sessionId: SessionId, private val featureFlagService: FeatureFlagService, private val computeCacheSizeUseCase: ComputeCacheSizeUseCase, private val clearCacheUseCase: ClearCacheUseCase, private val rageshakePresenter: Presenter, private val appPreferencesStore: AppPreferencesStore, private val buildMeta: BuildMeta, + private val enterpriseService: EnterpriseService, + private val vacuumStoresUseCase: VacuumStoresUseCase, + private val databaseSizesUseCase: GetDatabaseSizesUseCase, + private val fileSizeFormatter: FileSizeFormatter, ) : Presenter { @Composable override fun present(): DeveloperSettingsState { val rageshakeState = rageshakePresenter.present() - - val features = remember { - mutableStateMapOf() - } val enabledFeatures = remember { - mutableStateMapOf() + mutableStateListOf() } val cacheSize = remember { mutableStateOf>(AsyncData.Uninitialized) } + val databaseSizes = remember { + mutableStateOf>>(AsyncData.Uninitialized) + } val clearCacheAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } + var showColorPicker by remember { + mutableStateOf(false) + } val customElementCallBaseUrl by remember { appPreferencesStore .getCustomElementCallBaseUrlFlow() @@ -83,13 +101,13 @@ class DeveloperSettingsPresenter( val tracingLogPacks by produceState(persistentListOf()) { appPreferencesStore.getTracingLogPacksFlow() // Sort the entries alphabetically by its title - .map { it.sortedBy { it.title } } + .map { it.sortedBy { pack -> pack.title } } .collectLatest { value = it.toImmutableList() } } LaunchedEffect(Unit) { + computeDatabaseSizes(databaseSizes) featureFlagService.getAvailableFeatures() - .filter { it.isInLabs.not() && it.isFinished.not() } .run { // Never display room directory search in release builds for Play Store if (buildMeta.flavorDescription == "GooglePlay" && buildMeta.buildType == BuildType.RELEASE) { @@ -99,25 +117,23 @@ class DeveloperSettingsPresenter( } } .forEach { feature -> - features[feature.key] = feature - enabledFeatures[feature.key] = featureFlagService.isFeatureEnabled(feature) + enabledFeatures.add(EnabledFeature(feature, featureFlagService.isFeatureEnabled(feature))) } } - val featureUiModels = createUiModels(features, enabledFeatures) + val featureUiModels = createUiModels(enabledFeatures) val coroutineScope = rememberCoroutineScope() // Compute cache size each time the clear cache action value is changed LaunchedEffect(clearCacheAction.value.isSuccess()) { computeCacheSize(cacheSize) } - fun handleEvents(event: DeveloperSettingsEvents) { + fun handleEvent(event: DeveloperSettingsEvents) { when (event) { is DeveloperSettingsEvents.UpdateEnabledFeature -> coroutineScope.updateEnabledFeature( - features, - enabledFeatures, - event.feature, - event.isEnabled, - triggerClearCache = { handleEvents(DeveloperSettingsEvents.ClearCache) } + enabledFeatures = enabledFeatures, + featureKey = event.feature.key, + enabled = event.isEnabled, + triggerClearCache = { handleEvent(DeveloperSettingsEvents.ClearCache) } ) is DeveloperSettingsEvents.SetCustomElementCallBaseUrl -> coroutineScope.launch { val urlToSave = event.baseUrl.takeIf { !it.isNullOrEmpty() } @@ -136,12 +152,28 @@ class DeveloperSettingsPresenter( } appPreferencesStore.setTracingLogPacks(currentPacks) } + is DeveloperSettingsEvents.ChangeBrandColor -> coroutineScope.launch { + showColorPicker = false + val color = event.color + ?.toArgb() + ?.toHexString(HexFormat.UpperCase) + ?.substring(2, 8) + ?.padStart(7, '#') + enterpriseService.overrideBrandColor(sessionId, color) + } + is DeveloperSettingsEvents.SetShowColorPicker -> { + showColorPicker = event.show + } + DeveloperSettingsEvents.VacuumStores -> coroutineScope.launch { + vacuumStoresUseCase() + } } } return DeveloperSettingsState( - features = featureUiModels.toImmutableList(), + features = featureUiModels, cacheSize = cacheSize.value, + databaseSizes = databaseSizes.value, clearCacheAction = clearCacheAction.value, rageshakeState = rageshakeState, customElementCallBaseUrlState = CustomElementCallBaseUrlState( @@ -150,41 +182,41 @@ class DeveloperSettingsPresenter( ), tracingLogLevel = tracingLogLevel, tracingLogPacks = tracingLogPacks, - eventSink = ::handleEvents + isEnterpriseBuild = enterpriseService.isEnterpriseBuild, + showColorPicker = showColorPicker, + eventSink = ::handleEvent, ) } @Composable private fun createUiModels( - features: SnapshotStateMap, - enabledFeatures: SnapshotStateMap - ): List { - return features.values.map { feature -> - key(feature.key) { - val isEnabled = enabledFeatures[feature.key].orFalse() - remember(feature, isEnabled) { + enabledFeatures: SnapshotStateList, + ): ImmutableList { + return enabledFeatures.map { enabledFeature -> + key(enabledFeature.feature.key) { + remember(enabledFeature) { FeatureUiModel( - key = feature.key, - title = feature.title, - description = feature.description, + key = enabledFeature.feature.key, + title = enabledFeature.feature.title, + description = enabledFeature.feature.description, icon = null, - isEnabled = isEnabled + isEnabled = enabledFeature.isEnabled ) } } - } + }.toImmutableList() } private fun CoroutineScope.updateEnabledFeature( - features: SnapshotStateMap, - enabledFeatures: SnapshotStateMap, - featureUiModel: FeatureUiModel, + enabledFeatures: SnapshotStateList, + featureKey: String, enabled: Boolean, @Suppress("UNUSED_PARAMETER") triggerClearCache: () -> Unit, ) = launch { - val feature = features[featureUiModel.key] ?: return@launch + val featureIndex = enabledFeatures.indexOfFirst { it.feature.key == featureKey }.takeIf { it != -1 } ?: return@launch + val feature = enabledFeatures[featureIndex].feature if (featureFlagService.setFeatureEnabled(feature, enabled)) { - enabledFeatures[featureUiModel.key] = enabled + enabledFeatures[featureIndex] = enabledFeatures[featureIndex].copy(isEnabled = enabled) } } @@ -194,6 +226,27 @@ class DeveloperSettingsPresenter( }.runCatchingUpdatingState(cacheSize) } + private fun CoroutineScope.computeDatabaseSizes(databaseSizes: MutableState>>) = launch { + suspend { + databaseSizesUseCase(sessionId).getOrThrow().let { sizes -> + buildMap { + sizes.stateStore?.let { stateStoreSize -> + put("State store", fileSizeFormatter.format(stateStoreSize.into(ByteUnit.BYTES), useShortFormat = true)) + } + sizes.eventCacheStore?.let { eventCacheStoreSize -> + put("Event cache store", fileSizeFormatter.format(eventCacheStoreSize.into(ByteUnit.BYTES), useShortFormat = true)) + } + sizes.mediaStore?.let { mediaStoreSize -> + put("Media store", fileSizeFormatter.format(mediaStoreSize.into(ByteUnit.BYTES), useShortFormat = true)) + } + sizes.cryptoStore?.let { cryptoStoreSize -> + put("Crypto store", fileSizeFormatter.format(cryptoStoreSize.into(ByteUnit.BYTES), useShortFormat = true)) + } + } + }.toImmutableMap() + }.runCatchingUpdatingState(databaseSizes) + } + private fun CoroutineScope.clearCache(clearCacheAction: MutableState>) = launch { suspend { clearCacheUseCase() diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt index 93e7b9ae7b..920c8ec95c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,17 +15,23 @@ import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.featureflag.ui.model.FeatureUiModel import io.element.android.libraries.matrix.api.tracing.TraceLogPack import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableMap data class DeveloperSettingsState( val features: ImmutableList, val cacheSize: AsyncData, + val databaseSizes: AsyncData>, val rageshakeState: RageshakePreferencesState, val clearCacheAction: AsyncAction, val customElementCallBaseUrlState: CustomElementCallBaseUrlState, val tracingLogLevel: AsyncData, val tracingLogPacks: ImmutableList, + val isEnterpriseBuild: Boolean, + val showColorPicker: Boolean, val eventSink: (DeveloperSettingsEvents) -> Unit -) +) { + val showLoader = clearCacheAction is AsyncAction.Loading +} data class CustomElementCallBaseUrlState( val baseUrl: String?, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt index 6ccd857552..9ac4fdfcc8 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,6 +15,7 @@ import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.featureflag.ui.model.aFeatureUiModelList import io.element.android.libraries.matrix.api.tracing.TraceLogPack +import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toImmutableList open class DeveloperSettingsStateProvider : PreviewParameterProvider { @@ -28,6 +30,10 @@ open class DeveloperSettingsStateProvider : PreviewParameterProvider = AsyncAction.Uninitialized, customElementCallBaseUrlState: CustomElementCallBaseUrlState = aCustomElementCallBaseUrlState(), traceLogPacks: List = emptyList(), + isEnterpriseBuild: Boolean = false, + showColorPicker: Boolean = false, eventSink: (DeveloperSettingsEvents) -> Unit = {}, ) = DeveloperSettingsState( features = aFeatureUiModelList(), rageshakeState = aRageshakePreferencesState(), cacheSize = AsyncData.Success("1.2 MB"), + databaseSizes = AsyncData.Success(persistentMapOf("state_store" to "1.2MB")), clearCacheAction = clearCacheAction, customElementCallBaseUrlState = customElementCallBaseUrlState, tracingLogLevel = AsyncData.Success(LogLevelItem.INFO), tracingLogPacks = traceLogPacks.toImmutableList(), + isEnterpriseBuild = isEnterpriseBuild, + showColorPicker = showColorPicker, eventSink = eventSink, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsView.kt index 97f08f7a16..444a391d43 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 @@ -1,17 +1,21 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.preferences.impl.developer +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.progressSemantics import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType @@ -21,6 +25,7 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.features.preferences.impl.R import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem import io.element.android.features.rageshake.api.preferences.RageshakePreferencesView +import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferenceDropdown @@ -36,8 +41,11 @@ import io.element.android.libraries.featureflag.ui.FeatureListView import io.element.android.libraries.featureflag.ui.model.FeatureUiModel import io.element.android.libraries.matrix.api.tracing.TraceLogPack import io.element.android.libraries.ui.strings.CommonStrings +import io.mhssn.colorpicker.ColorPickerDialog +import io.mhssn.colorpicker.ColorPickerType import kotlinx.collections.immutable.toImmutableList +@OptIn(ExperimentalComposeUiApi::class) @Composable fun DeveloperSettingsView( state: DeveloperSettingsState, @@ -46,15 +54,25 @@ fun DeveloperSettingsView( onBackClick: () -> Unit, modifier: Modifier = Modifier, ) { + if (state.showLoader) { + ProgressDialog() + } + BackHandler( + enabled = !state.showLoader, + onBack = onBackClick, + ) PreferencePage( modifier = modifier, - onBackClick = onBackClick, + onBackClick = { + if (!state.showLoader) { + onBackClick() + } + }, title = stringResource(id = CommonStrings.common_developer_options) ) { // Note: this is OK to hardcode strings in this debug screen. PreferenceCategory( title = "Feature flags", - showTopDivider = true, ) { FeatureListContent(state) } @@ -99,7 +117,27 @@ fun DeveloperSettingsView( RageshakePreferencesView( state = state.rageshakeState, ) - PreferenceCategory(title = "Crash", showTopDivider = false) { + if (state.isEnterpriseBuild) { + PreferenceCategory(title = "Theme") { + ListItem( + headlineContent = { + Text("Change brand color") + }, + onClick = { + state.eventSink(DeveloperSettingsEvents.SetShowColorPicker(true)) + } + ) + ListItem( + headlineContent = { + Text("Reset brand color") + }, + onClick = { + state.eventSink(DeveloperSettingsEvents.ChangeBrandColor(null)) + } + ) + } + } + PreferenceCategory(title = "Crash") { ListItem( headlineContent = { Text("Crash the app 💥") @@ -108,7 +146,34 @@ fun DeveloperSettingsView( ) } val cache = state.cacheSize - PreferenceCategory(title = "Cache", showTopDivider = false) { + PreferenceCategory(title = "Cache") { + ListItem( + headlineContent = { Text("Database sizes") }, + supportingContent = { + if (state.databaseSizes.isLoading()) { + Text("Computing...") + } else { + val dbSizes = state.databaseSizes.dataOrNull() + if (dbSizes != null && dbSizes.isNotEmpty()) { + Column { + for ((dbName, size) in dbSizes) { + Text("$dbName: $size") + } + } + } else { + Text("Unknown") + } + } + } + ) + ListItem( + headlineContent = { + Text("Vacuum stores") + }, + onClick = { + state.eventSink(DeveloperSettingsEvents.VacuumStores) + } + ) ListItem( headlineContent = { Text("Clear cache") @@ -133,13 +198,25 @@ fun DeveloperSettingsView( ) } } + ColorPickerDialog( + show = state.showColorPicker, + type = ColorPickerType.Classic( + showAlphaBar = false, + ), + onDismissRequest = { + state.eventSink(DeveloperSettingsEvents.SetShowColorPicker(false)) + }, + onPickedColor = { + state.eventSink(DeveloperSettingsEvents.ChangeBrandColor(it)) + }, + ) } @Composable private fun ElementCallCategory( state: DeveloperSettingsState, ) { - PreferenceCategory(title = "Element Call", showTopDivider = true) { + PreferenceCategory(title = "Element Call") { val callUrlState = state.customElementCallBaseUrlState val supportingText = if (callUrlState.baseUrl.isNullOrEmpty()) { @@ -189,7 +266,9 @@ private fun FeatureListContent( @PreviewsDayNight @Composable -internal fun DeveloperSettingsViewPreview(@PreviewParameter(DeveloperSettingsStateProvider::class) state: DeveloperSettingsState) = ElementPreview { +internal fun DeveloperSettingsViewPreview( + @PreviewParameter(DeveloperSettingsStateProvider::class) state: DeveloperSettingsState +) = ElementPreview { DeveloperSettingsView( state = state, onOpenShowkase = {}, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/LogLevelItem.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/LogLevelItem.kt index 7148cff57f..ca8fe007aa 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/LogLevelItem.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/LogLevelItem.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/LogLevelMapper.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/LogLevelMapper.kt index fabfffa2cc..c271499aa0 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/LogLevelMapper.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/tracing/LogLevelMapper.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsEvents.kt index 0f652a5c5c..bc948da09c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsNode.kt index 5c2b5ed0e3..b7ba73c9af 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsNode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,6 +16,7 @@ import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.SessionScope @ContributesNode(SessionScope::class) @@ -24,9 +26,18 @@ class LabsNode( @Assisted plugins: List, private val presenter: LabsPresenter, ) : Node(buildContext, plugins = plugins) { + interface Callback : Plugin { + fun onDone() + } + + val callback: Callback = callback() + @Composable override fun View(modifier: Modifier) { val state = presenter.present() - LabsView(state = state, onBack = ::navigateUp) + LabsView( + state = state, + onBack = callback::onDone, + ) } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsPresenter.kt index 5e74454d75..3b7d499ecf 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsPresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,20 +12,19 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.key -import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import androidx.compose.runtime.snapshots.SnapshotStateMap +import androidx.compose.runtime.snapshots.SnapshotStateList import dev.zacsweers.metro.Inject import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.preferences.impl.R +import io.element.android.features.preferences.impl.model.EnabledFeature import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.designsystem.theme.components.IconSource -import io.element.android.libraries.featureflag.api.Feature import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.ui.model.FeatureUiModel @@ -42,46 +42,38 @@ class LabsPresenter( @Composable override fun present(): LabsState { val coroutineScope = rememberCoroutineScope() - val features = remember { - val entries = featureFlagService.getAvailableFeatures() - .filter { it.isInLabs && !it.isFinished } - .map { it.key to it } - mutableStateMapOf(*entries.toTypedArray()) - } val enabledFeatures = remember { - mutableStateMapOf() + mutableStateListOf() } - LaunchedEffect(Unit) { - for (feature in features.values) { - val isEnabled = featureFlagService.isFeatureEnabled(feature) - enabledFeatures[feature.key] = isEnabled - } + featureFlagService.getAvailableFeatures(isInLabs = true) + .forEach { feature -> + enabledFeatures.add(EnabledFeature(feature, featureFlagService.isFeatureEnabled(feature))) + } } - var isApplyingChanges by remember { mutableStateOf(false) } - - val featureUiModels = createUiModels(features, enabledFeatures) + val featureUiModels = createUiModels(enabledFeatures) fun handleEvent(event: LabsEvents) { when (event) { is LabsEvents.ToggleFeature -> coroutineScope.launch { - val feature = features[event.feature.key] ?: return@launch - val isEnabled = featureFlagService.isFeatureEnabled(feature) - featureFlagService.setFeatureEnabled(feature = feature, enabled = !isEnabled) - enabledFeatures[feature.key] = !isEnabled - - when (feature.key) { - FeatureFlags.Threads.key -> { - // Threads require a cache clear to recreate the event cache - clearCacheUseCase() - isApplyingChanges = true + val featureIndex = enabledFeatures.indexOfFirst { it.feature.key == event.feature.key }.takeIf { it != -1 } ?: return@launch + val enabledFeature = enabledFeatures[featureIndex] + val feature = enabledFeature.feature + val newValue = enabledFeature.isEnabled.not() + if (featureFlagService.setFeatureEnabled(feature, newValue)) { + enabledFeatures[featureIndex] = enabledFeatures[featureIndex].copy(isEnabled = newValue) + when (feature.key) { + FeatureFlags.Threads.key -> { + // Threads require a cache clear to recreate the event cache + clearCacheUseCase() + isApplyingChanges = true + } } } } } } - return LabsState( features = featureUiModels, isApplyingChanges = isApplyingChanges, @@ -91,31 +83,29 @@ class LabsPresenter( @Composable private fun createUiModels( - features: SnapshotStateMap, - enabledFeatures: SnapshotStateMap + enabledFeatures: SnapshotStateList, ): ImmutableList { - return features.values.map { feature -> - key(feature.key) { - val isEnabled = enabledFeatures[feature.key].orFalse() - val title = when (feature) { + return enabledFeatures.map { enabledFeature -> + key(enabledFeature.feature.key) { + val title = when (enabledFeature.feature) { FeatureFlags.Threads -> stringProvider.getString(R.string.screen_labs_enable_threads) - else -> feature.title + else -> enabledFeature.feature.title } - val description = when (feature) { + val description = when (enabledFeature.feature) { FeatureFlags.Threads -> stringProvider.getString(R.string.screen_labs_enable_threads_description) - else -> feature.description + else -> enabledFeature.feature.description } - val icon = when (feature) { + val icon = when (enabledFeature.feature) { FeatureFlags.Threads -> CompoundIcons.Threads() else -> null } - remember(feature, isEnabled) { + remember(enabledFeature) { FeatureUiModel( - key = feature.key, + key = enabledFeature.feature.key, title = title, description = description, icon = icon?.let(IconSource::Vector), - isEnabled = isEnabled + isEnabled = enabledFeature.isEnabled ) } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsState.kt index 0925cd7893..42e70d58c9 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsStateProvider.kt index df804cd409..08e251dcfa 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsView.kt index 2738da4b63..7a6703e3bc 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/labs/LabsView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/model/EnabledFeature.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/model/EnabledFeature.kt new file mode 100644 index 0000000000..2d1b5e54e7 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/model/EnabledFeature.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.preferences.impl.model + +import io.element.android.libraries.featureflag.api.Feature + +data class EnabledFeature( + val feature: Feature, + val isEnabled: Boolean, +) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsEvents.kt index 59dca36b3b..8f1b332c8c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsNode.kt index f488889c5b..d300217ecc 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,10 +13,10 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.SessionScope @ContributesNode(SessionScope::class) @@ -26,28 +27,20 @@ class NotificationSettingsNode( private val presenter: NotificationSettingsPresenter, ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun editDefaultNotificationMode(isOneToOne: Boolean) - fun onTroubleshootNotificationsClick() + fun navigateToEditDefaultNotificationSetting(isOneToOne: Boolean) + fun navigateToTroubleshootNotifications() } - private val callbacks = plugins() - - private fun openEditDefault(isOneToOne: Boolean) { - callbacks.forEach { it.editDefaultNotificationMode(isOneToOne) } - } - - private fun onTroubleshootNotificationsClick() { - callbacks.forEach { it.onTroubleshootNotificationsClick() } - } + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { val state = presenter.present() NotificationSettingsView( state = state, - onOpenEditDefault = { openEditDefault(isOneToOne = it) }, + onOpenEditDefault = callback::navigateToEditDefaultNotificationSetting, onBackClick = ::navigateUp, - onTroubleshootNotificationsClick = ::onTroubleshootNotificationsClick, + onTroubleshootNotificationsClick = callback::navigateToTroubleshootNotifications, modifier = modifier, ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt index 1fcb984924..9d9e80b3f3 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -24,6 +25,7 @@ import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runUpdatingStateNoSuccess import io.element.android.libraries.core.extensions.runCatchingExceptions +import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService @@ -50,6 +52,8 @@ class NotificationSettingsPresenter( private val pushService: PushService, private val systemNotificationsEnabledProvider: SystemNotificationsEnabledProvider, private val fullScreenIntentPermissionsPresenter: Presenter, + @SessionCoroutineScope + private val sessionCoroutineScope: CoroutineScope, ) : Presenter { @Composable override fun present(): NotificationSettingsState { @@ -94,7 +98,7 @@ class NotificationSettingsPresenter( var refreshPushProvider by remember { mutableIntStateOf(0) } LaunchedEffect(refreshPushProvider) { - val p = pushService.getCurrentPushProvider() + val p = pushService.getCurrentPushProvider(matrixClient.sessionId) val distributor = p?.getCurrentDistributor(matrixClient.sessionId) currentDistributor = if (distributor != null) { AsyncData.Success(distributor) @@ -129,7 +133,7 @@ class NotificationSettingsPresenter( ) } - fun handleEvents(event: NotificationSettingsEvents) { + fun handleEvent(event: NotificationSettingsEvents) { when (event) { is NotificationSettingsEvents.SetAtRoomNotificationsEnabled -> { localCoroutineScope.setAtRoomNotificationsEnabled(event.enabled, changeNotificationSettingAction) @@ -140,7 +144,7 @@ class NotificationSettingsPresenter( is NotificationSettingsEvents.SetInviteForMeNotificationsEnabled -> { localCoroutineScope.setInviteForMeNotificationsEnabled(event.enabled, changeNotificationSettingAction) } - is NotificationSettingsEvents.SetNotificationsEnabled -> localCoroutineScope.setNotificationsEnabled(userPushStore, event.enabled) + is NotificationSettingsEvents.SetNotificationsEnabled -> sessionCoroutineScope.setNotificationsEnabled(userPushStore, event.enabled) NotificationSettingsEvents.ClearConfigurationMismatchError -> { matrixSettings.value = NotificationSettingsState.MatrixSettings.Invalid(fixFailed = false) } @@ -167,7 +171,7 @@ class NotificationSettingsPresenter( availablePushDistributors = availableDistributors, showChangePushProviderDialog = showChangePushProviderDialog, fullScreenIntentPermissionsState = key(refreshFullScreenIntentSettings) { fullScreenIntentPermissionsPresenter.present() }, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } @@ -261,5 +265,10 @@ class NotificationSettingsPresenter( private fun CoroutineScope.setNotificationsEnabled(userPushStore: UserPushStore, enabled: Boolean) = launch { userPushStore.setNotificationEnabledForDevice(enabled) + if (enabled) { + pushService.ensurePusherIsRegistered(matrixClient) + } else { + pushService.getCurrentPushProvider(matrixClient.sessionId)?.unregister(matrixClient) + } } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsState.kt index a7a1bf9192..0a55909e2c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsState.kt @@ -1,13 +1,13 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.preferences.impl.notifications -import androidx.compose.runtime.Immutable import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState @@ -15,7 +15,6 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.pushproviders.api.Distributor import kotlinx.collections.immutable.ImmutableList -@Immutable data class NotificationSettingsState( val matrixSettings: MatrixSettings, val appSettings: AppSettings, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt index 0b46328b9b..fb396443c3 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt index 26b2de590e..09f54b7027 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/SystemNotificationsEnabledProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/SystemNotificationsEnabledProvider.kt index 8dc8b3d2bd..a21dbb1bf2 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/SystemNotificationsEnabledProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/SystemNotificationsEnabledProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.features.preferences.impl.notifications import androidx.core.app.NotificationManagerCompat import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn interface SystemNotificationsEnabledProvider { @@ -19,7 +19,6 @@ interface SystemNotificationsEnabledProvider { @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DefaultSystemNotificationsEnabledProvider( private val notificationManager: NotificationManagerCompat, ) : SystemNotificationsEnabledProvider { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/DefaultNotificationSettingOption.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/DefaultNotificationSettingOption.kt index a8431e70b5..f7fc251eeb 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/DefaultNotificationSettingOption.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/DefaultNotificationSettingOption.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingNode.kt index ccba221d9a..96097983c9 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,11 +13,11 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.RoomId @@ -29,27 +30,23 @@ class EditDefaultNotificationSettingNode( presenterFactory: EditDefaultNotificationSettingPresenter.Factory ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun openRoomNotificationSettings(roomId: RoomId) + fun navigateToRoomNotificationSettings(roomId: RoomId) } data class Inputs( val isOneToOne: Boolean ) : NodeInputs + private val callback: Callback = callback() private val inputs = inputs() - private val callbacks = plugins() private val presenter = presenterFactory.create(inputs.isOneToOne) - private fun openRoomNotificationSettings(roomId: RoomId) { - callbacks.forEach { it.openRoomNotificationSettings(roomId) } - } - @Composable override fun View(modifier: Modifier) { val state = presenter.present() EditDefaultNotificationSettingView( state = state, - openRoomNotificationSettings = { openRoomNotificationSettings(it) }, + openRoomNotificationSettings = callback::navigateToRoomNotificationSettings, onBackClick = ::navigateUp, modifier = modifier, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt index 25b062827f..df5247188a 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -22,6 +23,7 @@ import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runUpdatingStateNoSuccess import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.roomlist.RoomListService @@ -48,6 +50,10 @@ class EditDefaultNotificationSettingPresenter( fun create(oneToOne: Boolean): EditDefaultNotificationSettingPresenter } + private val collator = Collator.getInstance().apply { + decomposition = Collator.CANONICAL_DECOMPOSITION + } + @Composable override fun present(): EditDefaultNotificationSettingState { var displayMentionsOnlyDisclaimer by remember { mutableStateOf(false) } @@ -70,7 +76,7 @@ class EditDefaultNotificationSettingPresenter( displayMentionsOnlyDisclaimer = !notificationSettingsService.canHomeServerPushEncryptedEventsToDevice().getOrDefault(true) } - fun handleEvents(event: EditDefaultNotificationSettingStateEvents) { + fun handleEvent(event: EditDefaultNotificationSettingStateEvents) { when (event) { is EditDefaultNotificationSettingStateEvents.SetNotificationMode -> { localCoroutineScope.setDefaultNotificationMode(event.mode, changeNotificationSettingAction) @@ -85,7 +91,7 @@ class EditDefaultNotificationSettingPresenter( roomsWithUserDefinedMode = roomsWithUserDefinedMode.value.toImmutableList(), changeNotificationSettingAction = changeNotificationSettingAction.value, displayMentionsOnlyDisclaimer = displayMentionsOnlyDisclaimer, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } @@ -120,10 +126,10 @@ class EditDefaultNotificationSettingPresenter( summaries: List, roomsWithUserDefinedMode: MutableState> ) { - val roomWithUserDefinedRules: Set = notificationSettingsService.getRoomsWithUserDefinedRules().getOrDefault(emptyList()).toSet() + val roomWithUserDefinedRules: Set = notificationSettingsService.getRoomsWithUserDefinedRules().getOrDefault(emptyList()).toSet() roomsWithUserDefinedMode.value = summaries .filter { roomSummary -> - roomWithUserDefinedRules.contains(roomSummary.roomId.value) && roomSummary.isOneToOne == isOneToOne + roomWithUserDefinedRules.contains(roomSummary.roomId) && roomSummary.isOneToOne == isOneToOne } .map { roomSummary -> EditNotificationSettingRoomInfo( @@ -137,7 +143,12 @@ class EditDefaultNotificationSettingPresenter( ) } // locale sensitive sorting - .sortedWith(compareBy(Collator.getInstance()) { roomSummary -> roomSummary.name }) + .sortedWith( + compareBy(collator) { roomSummary -> + // Collator does not handle null values, so we provide a fallback + roomSummary.name ?: roomSummary.roomId.value + } + ) } private fun CoroutineScope.setDefaultNotificationMode(mode: RoomNotificationMode, action: MutableState>) = launch { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt index 2689850571..17c8cad95c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateEvents.kt index 5287b27d85..a7993c118a 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt index 7a8fcea628..389554d2e0 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt index f38e59355c..3b46ae737e 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditNotificationSettingRoomInfo.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditNotificationSettingRoomInfo.kt index 53c25eb263..daed0771b3 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditNotificationSettingRoomInfo.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditNotificationSettingRoomInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootEvents.kt index 87074ec7f9..be266869be 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt index 67d50a76f0..6b54a763af 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,7 +15,6 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode @@ -22,6 +22,7 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.features.logout.api.direct.DirectLogoutEvents import io.element.android.features.logout.api.direct.DirectLogoutView import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.user.MatrixUser @@ -34,53 +35,24 @@ class PreferencesRootNode( private val directLogoutView: DirectLogoutView, ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun onAddAccount() - fun onOpenBugReport() - fun onSecureBackupClick() - fun onOpenAnalytics() - fun onOpenAbout() - fun onOpenDeveloperSettings() - fun onOpenNotificationSettings() - fun onOpenLockScreenSettings() - fun onOpenAdvancedSettings() - fun onOpenLabs() - fun onOpenUserProfile(matrixUser: MatrixUser) - fun onOpenBlockedUsers() - fun onSignOutClick() - fun onOpenAccountDeactivation() + fun navigateToAddAccount() + fun navigateToBugReport() + fun navigateToSecureBackup() + fun navigateToAnalyticsSettings() + fun navigateToAbout() + fun navigateToDeveloperSettings() + fun navigateToNotificationSettings() + fun navigateToLockScreenSettings() + fun navigateToAdvancedSettings() + fun navigateToLabs() + fun navigateToLinkNewDevice() + fun navigateToUserProfile(matrixUser: MatrixUser) + fun navigateToBlockedUsers() + fun startSignOutFlow() + fun startAccountDeactivationFlow() } - private fun onAddAccount() { - plugins().forEach { it.onAddAccount() } - } - - private fun onOpenBugReport() { - plugins().forEach { it.onOpenBugReport() } - } - - private fun onSecureBackupClick() { - plugins().forEach { it.onSecureBackupClick() } - } - - private fun onOpenDeveloperSettings() { - plugins().forEach { it.onOpenDeveloperSettings() } - } - - private fun onOpenAdvancedSettings() { - plugins().forEach { it.onOpenAdvancedSettings() } - } - - private fun onOpenLabs() { - plugins().forEach { it.onOpenLabs() } - } - - private fun onOpenAnalytics() { - plugins().forEach { it.onOpenAnalytics() } - } - - private fun onOpenAbout() { - plugins().forEach { it.onOpenAbout() } - } + private val callback: Callback = callback() private fun onManageAccountClick( activity: Activity, @@ -96,30 +68,6 @@ class PreferencesRootNode( } } - private fun onOpenNotificationSettings() { - plugins().forEach { it.onOpenNotificationSettings() } - } - - private fun onOpenLockScreenSettings() { - plugins().forEach { it.onOpenLockScreenSettings() } - } - - private fun onOpenUserProfile(matrixUser: MatrixUser) { - plugins().forEach { it.onOpenUserProfile(matrixUser) } - } - - private fun onOpenBlockedUsers() { - plugins().forEach { it.onOpenBlockedUsers() } - } - - private fun onSignOutClick() { - plugins().forEach { it.onSignOutClick() } - } - - private fun onOpenAccountDeactivation() { - plugins().forEach { it.onOpenAccountDeactivation() } - } - @Composable override fun View(modifier: Modifier) { val state = presenter.present() @@ -129,27 +77,28 @@ class PreferencesRootNode( state = state, modifier = modifier, onBackClick = this::navigateUp, - onAddAccountClick = this::onAddAccount, - onOpenRageShake = this::onOpenBugReport, - onOpenAnalytics = this::onOpenAnalytics, - onOpenAbout = this::onOpenAbout, - onSecureBackupClick = this::onSecureBackupClick, - onOpenDeveloperSettings = this::onOpenDeveloperSettings, - onOpenAdvancedSettings = this::onOpenAdvancedSettings, - onOpenLabs = this::onOpenLabs, + onAddAccountClick = callback::navigateToAddAccount, + onOpenRageShake = callback::navigateToBugReport, + onOpenAnalytics = callback::navigateToAnalyticsSettings, + onOpenAbout = callback::navigateToAbout, + onSecureBackupClick = callback::navigateToSecureBackup, + onOpenDeveloperSettings = callback::navigateToDeveloperSettings, + onOpenAdvancedSettings = callback::navigateToAdvancedSettings, + onOpenLabs = callback::navigateToLabs, + onLinkNewDeviceClick = callback::navigateToLinkNewDevice, onManageAccountClick = { onManageAccountClick(activity, it, isDark) }, - onOpenNotificationSettings = this::onOpenNotificationSettings, - onOpenLockScreenSettings = this::onOpenLockScreenSettings, - onOpenUserProfile = this::onOpenUserProfile, - onOpenBlockedUsers = this::onOpenBlockedUsers, + onOpenNotificationSettings = callback::navigateToNotificationSettings, + onOpenLockScreenSettings = callback::navigateToLockScreenSettings, + onOpenUserProfile = callback::navigateToUserProfile, + onOpenBlockedUsers = callback::navigateToBlockedUsers, onSignOutClick = { if (state.directLogoutState.canDoDirectSignOut) { state.directLogoutState.eventSink(DirectLogoutEvents.Logout(ignoreSdkError = false)) } else { - onSignOutClick() + callback.startSignOutFlow() } }, - onDeactivateClick = this::onOpenAccountDeactivation + onDeactivateClick = callback::startAccountDeactivationFlow ) directLogoutView.Render(state = state.directLogoutState) 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 3da72f982e..1e056c0bf0 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -68,6 +69,9 @@ class PreferencesRootPresenter( val isMultiAccountEnabled by remember { featureFlagService.isFeatureEnabledFlow(FeatureFlags.MultiAccount) }.collectAsState(initial = false) + val showLinkNewDevice by remember { + featureFlagService.isFeatureEnabledFlow(FeatureFlags.QrCodeLogin) + }.collectAsState(initial = false) val otherSessions by remember { sessionStore.sessionsFlow().map { list -> @@ -112,7 +116,7 @@ class PreferencesRootPresenter( .launchIn(this) } - val showLabsItem = remember { featureFlagService.getAvailableFeatures().any { it.isInLabs && !it.isFinished } } + val showLabsItem = remember { featureFlagService.getAvailableFeatures(isInLabs = true).isNotEmpty() } val directLogoutState = directLogoutPresenter.present() @@ -145,6 +149,7 @@ class PreferencesRootPresenter( devicesManagementUrl = devicesManagementUrl.value, showAnalyticsSettings = hasAnalyticsProviders, canReportBug = canReportBug, + showLinkNewDevice = showLinkNewDevice, showDeveloperSettings = showDeveloperSettings, canDeactivateAccount = canDeactivateAccount, showBlockedUsersItem = showBlockedUsersItem, 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 3df13e0efd..d637ae6c87 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -24,6 +25,7 @@ data class PreferencesRootState( val accountManagementUrl: String?, val devicesManagementUrl: String?, val canReportBug: Boolean, + val showLinkNewDevice: Boolean, val showAnalyticsSettings: Boolean, val showDeveloperSettings: Boolean, val canDeactivateAccount: Boolean, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootStateProvider.kt index ab30d07bcf..b8d1f1c2b6 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -30,6 +31,7 @@ fun aPreferencesRootState( accountManagementUrl = "aUrl", devicesManagementUrl = "anOtherUrl", showAnalyticsSettings = true, + showLinkNewDevice = true, canReportBug = true, showDeveloperSettings = true, showBlockedUsersItem = true, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootView.kt index 3b4d2d2670..5e3c9d6759 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -53,6 +54,7 @@ fun PreferencesRootView( onAddAccountClick: () -> Unit, onSecureBackupClick: () -> Unit, onManageAccountClick: (url: String) -> Unit, + onLinkNewDeviceClick: () -> Unit, onOpenAnalytics: () -> Unit, onOpenRageShake: () -> Unit, onOpenLockScreenSettings: () -> Unit, @@ -100,6 +102,7 @@ fun PreferencesRootView( ManageAccountSection( state = state, onManageAccountClick = onManageAccountClick, + onLinkNewDeviceClick = onLinkNewDeviceClick, onOpenBlockedUsers = onOpenBlockedUsers ) @@ -192,8 +195,16 @@ private fun ColumnScope.ManageAppSection( private fun ColumnScope.ManageAccountSection( state: PreferencesRootState, onManageAccountClick: (url: String) -> Unit, + onLinkNewDeviceClick: () -> Unit, onOpenBlockedUsers: () -> Unit, ) { + if (state.showLinkNewDevice) { + ListItem( + headlineContent = { Text(stringResource(id = CommonStrings.common_link_new_device)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Devices())), + onClick = onLinkNewDeviceClick, + ) + } state.accountManagementUrl?.let { url -> ListItem( headlineContent = { Text(stringResource(id = CommonStrings.action_manage_account)) }, @@ -352,6 +363,7 @@ private fun ContentToPreview(matrixUser: MatrixUser) { onOpenAbout = {}, onSecureBackupClick = {}, onManageAccountClick = {}, + onLinkNewDeviceClick = {}, onOpenNotificationSettings = {}, onOpenLockScreenSettings = {}, onOpenUserProfile = {}, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/VersionFormatter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/VersionFormatter.kt index ce65f62f37..52517b5ca2 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/VersionFormatter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/VersionFormatter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.features.preferences.impl.root import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.toolbox.api.strings.StringProvider @@ -19,7 +19,6 @@ interface VersionFormatter { } @ContributesBinding(AppScope::class) -@Inject class DefaultVersionFormatter( private val stringProvider: StringProvider, private val buildMeta: BuildMeta, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt index 50d9cf8798..6c26866e93 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ClearCacheUseCase.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.features.preferences.impl.tasks import android.content.Context import coil3.SingletonImageLoader import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Provider import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.features.preferences.impl.DefaultCacheService @@ -28,7 +28,6 @@ interface ClearCacheUseCase { } @ContributesBinding(SessionScope::class) -@Inject class DefaultClearCacheUseCase( @ApplicationContext private val context: Context, private val matrixClient: MatrixClient, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ComputeCacheSizeUseCase.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ComputeCacheSizeUseCase.kt index 10b1748590..a7ac97c000 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ComputeCacheSizeUseCase.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/ComputeCacheSizeUseCase.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.features.preferences.impl.tasks import android.content.Context import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.androidutils.file.getSizeOfFiles import io.element.android.libraries.androidutils.filesize.FileSizeFormatter import io.element.android.libraries.core.coroutine.CoroutineDispatchers @@ -23,7 +23,6 @@ interface ComputeCacheSizeUseCase { } @ContributesBinding(SessionScope::class) -@Inject class DefaultComputeCacheSizeUseCase( @ApplicationContext private val context: Context, private val matrixClient: MatrixClient, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/VacuumStoresUseCase.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/VacuumStoresUseCase.kt new file mode 100644 index 0000000000..1d0de56f09 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/tasks/VacuumStoresUseCase.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.preferences.impl.tasks + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.matrix.api.MatrixClient +import timber.log.Timber + +fun interface VacuumStoresUseCase { + suspend operator fun invoke() +} + +@ContributesBinding(AppScope::class) +class DefaultVacuumStoresUseCase( + private val matrixClient: MatrixClient, +) : VacuumStoresUseCase { + override suspend fun invoke() { + matrixClient.performDatabaseVacuum() + .onFailure { Timber.e(it, "Failed to vacuum stores") } + } +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/UserPreferences.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/UserPreferences.kt index 8b4a2dd21a..a9066dcd73 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/UserPreferences.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/UserPreferences.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileEvent.kt similarity index 57% rename from features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileEvents.kt rename to features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileEvent.kt index e42de507b0..d88eb75963 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileEvent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,9 +10,10 @@ package io.element.android.features.preferences.impl.user.editprofile import io.element.android.libraries.matrix.ui.media.AvatarAction -sealed interface EditUserProfileEvents { - data class HandleAvatarAction(val action: AvatarAction) : EditUserProfileEvents - data class UpdateDisplayName(val name: String) : EditUserProfileEvents - data object Save : EditUserProfileEvents - data object CancelSaveChanges : EditUserProfileEvents +sealed interface EditUserProfileEvent { + data class HandleAvatarAction(val action: AvatarAction) : EditUserProfileEvent + data class UpdateDisplayName(val name: String) : EditUserProfileEvent + data object Exit : EditUserProfileEvent + data object Save : EditUserProfileEvent + data object CloseDialog : EditUserProfileEvent } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileNavigator.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileNavigator.kt new file mode 100644 index 0000000000..2935bce747 --- /dev/null +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileNavigator.kt @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.preferences.impl.user.editprofile + +interface EditUserProfileNavigator { + fun close() +} diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileNode.kt index d691508427..2303abb06e 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,6 +17,7 @@ import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.user.MatrixUser @@ -26,22 +28,32 @@ class EditUserProfileNode( @Assisted buildContext: BuildContext, @Assisted plugins: List, presenterFactory: EditUserProfilePresenter.Factory, -) : Node(buildContext, plugins = plugins) { +) : Node(buildContext, plugins = plugins), + EditUserProfileNavigator { data class Inputs( val matrixUser: MatrixUser ) : NodeInputs + interface Callback : Plugin { + fun onDone() + } + val matrixUser = inputs().matrixUser - val presenter = presenterFactory.create(matrixUser) + val callback: Callback = callback() + val presenter = presenterFactory.create( + matrixUser = matrixUser, + navigator = this, + ) @Composable override fun View(modifier: Modifier) { val state = presenter.present() EditUserProfileView( state = state, - onBackClick = ::navigateUp, - onEditProfileSuccess = ::navigateUp, + onEditProfileSuccess = ::close, modifier = modifier ) } + + override fun close() = callback.onDone() } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenter.kt index 0cd0144986..bddae2fffb 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -34,7 +35,7 @@ import io.element.android.libraries.matrix.ui.media.AvatarAction import io.element.android.libraries.mediapickers.api.PickerProvider import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider import io.element.android.libraries.mediaupload.api.MediaPreProcessor -import io.element.android.libraries.permissions.api.PermissionsEvents +import io.element.android.libraries.permissions.api.PermissionsEvent import io.element.android.libraries.permissions.api.PermissionsPresenter import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope @@ -44,6 +45,7 @@ import timber.log.Timber @AssistedInject class EditUserProfilePresenter( @Assisted private val matrixUser: MatrixUser, + @Assisted private val navigator: EditUserProfileNavigator, private val matrixClient: MatrixClient, private val mediaPickerProvider: PickerProvider, private val mediaPreProcessor: MediaPreProcessor, @@ -56,27 +58,30 @@ class EditUserProfilePresenter( @AssistedFactory interface Factory { - fun create(matrixUser: MatrixUser): EditUserProfilePresenter + fun create( + matrixUser: MatrixUser, + navigator: EditUserProfileNavigator, + ): EditUserProfilePresenter } @Composable override fun present(): EditUserProfileState { val cameraPermissionState = cameraPermissionPresenter.present() - var userAvatarUri by rememberSaveable { mutableStateOf(matrixUser.avatarUrl?.toUri()) } + var userAvatarUri by rememberSaveable { mutableStateOf(matrixUser.avatarUrl) } var userDisplayName by rememberSaveable { mutableStateOf(matrixUser.displayName) } val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker( onResult = { uri -> if (uri != null) { - temporaryUriDeleter.delete(userAvatarUri) - userAvatarUri = uri + temporaryUriDeleter.delete(userAvatarUri?.toUri()) + userAvatarUri = uri.toString() } } ) val galleryImagePicker = mediaPickerProvider.registerGalleryImagePicker( onResult = { uri -> if (uri != null) { - temporaryUriDeleter.delete(userAvatarUri) - userAvatarUri = uri + temporaryUriDeleter.delete(userAvatarUri?.toUri()) + userAvatarUri = uri.toString() } } ) @@ -100,36 +105,62 @@ class EditUserProfilePresenter( val saveAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val localCoroutineScope = rememberCoroutineScope() - fun handleEvents(event: EditUserProfileEvents) { + + val canSave = remember(userDisplayName, userAvatarUri) { + val hasProfileChanged = hasDisplayNameChanged(userDisplayName, matrixUser) || + hasAvatarUrlChanged(userAvatarUri, matrixUser) + !userDisplayName.isNullOrBlank() && hasProfileChanged + } + + fun handleEvent(event: EditUserProfileEvent) { when (event) { - is EditUserProfileEvents.Save -> localCoroutineScope.saveChanges(userDisplayName, userAvatarUri, matrixUser, saveAction) - is EditUserProfileEvents.HandleAvatarAction -> { + is EditUserProfileEvent.Save -> localCoroutineScope.saveChanges( + name = userDisplayName, + avatarUri = userAvatarUri?.toUri(), + currentUser = matrixUser, + action = saveAction, + ) + is EditUserProfileEvent.HandleAvatarAction -> { when (event.action) { AvatarAction.ChoosePhoto -> galleryImagePicker.launch() AvatarAction.TakePhoto -> if (cameraPermissionState.permissionGranted) { cameraPhotoPicker.launch() } else { pendingPermissionRequest = true - cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions) + cameraPermissionState.eventSink(PermissionsEvent.RequestPermissions) } AvatarAction.Remove -> { - temporaryUriDeleter.delete(userAvatarUri) + temporaryUriDeleter.delete(userAvatarUri?.toUri()) userAvatarUri = null } } } - - is EditUserProfileEvents.UpdateDisplayName -> userDisplayName = event.name - EditUserProfileEvents.CancelSaveChanges -> saveAction.value = AsyncAction.Uninitialized + is EditUserProfileEvent.UpdateDisplayName -> userDisplayName = event.name + EditUserProfileEvent.Exit -> { + when (saveAction.value) { + is AsyncAction.Confirming -> { + // Close the dialog right now + saveAction.value = AsyncAction.Uninitialized + navigator.close() + } + AsyncAction.Loading -> Unit + is AsyncAction.Failure, + is AsyncAction.Success -> { + // Should not happen + } + AsyncAction.Uninitialized -> { + if (canSave) { + saveAction.value = AsyncAction.ConfirmingCancellation + } else { + navigator.close() + } + } + } + } + EditUserProfileEvent.CloseDialog -> saveAction.value = AsyncAction.Uninitialized } } - val canSave = remember(userDisplayName, userAvatarUri) { - val hasProfileChanged = hasDisplayNameChanged(userDisplayName, matrixUser) || - hasAvatarUrlChanged(userAvatarUri, matrixUser) - !userDisplayName.isNullOrBlank() && hasProfileChanged - } - return EditUserProfileState( userId = matrixUser.userId, displayName = userDisplayName.orEmpty(), @@ -138,16 +169,15 @@ class EditUserProfilePresenter( saveButtonEnabled = canSave && saveAction.value !is AsyncAction.Loading, saveAction = saveAction.value, cameraPermissionState = cameraPermissionState, - eventSink = { handleEvents(it) }, + eventSink = ::handleEvent, ) } private fun hasDisplayNameChanged(name: String?, currentUser: MatrixUser) = name?.trim() != currentUser.displayName?.trim() - private fun hasAvatarUrlChanged(avatarUri: Uri?, currentUser: MatrixUser) = - // Need to call `toUri()?.toString()` to make the test pass (we mockk Uri) - avatarUri?.toString()?.trim() != currentUser.avatarUrl?.toUri()?.toString()?.trim() + private fun hasAvatarUrlChanged(avatarUri: String?, currentUser: MatrixUser) = + avatarUri?.trim() != currentUser.avatarUrl?.trim() private fun CoroutineScope.saveChanges( name: String?, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileState.kt index 5d3bd1baee..a638ed8378 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileState.kt @@ -1,13 +1,13 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.preferences.impl.user.editprofile -import android.net.Uri import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.ui.media.AvatarAction @@ -17,10 +17,10 @@ import kotlinx.collections.immutable.ImmutableList data class EditUserProfileState( val userId: UserId, val displayName: String, - val userAvatarUrl: Uri?, + val userAvatarUrl: String?, val avatarActions: ImmutableList, val saveButtonEnabled: Boolean, val saveAction: AsyncAction, val cameraPermissionState: PermissionsState, - val eventSink: (EditUserProfileEvents) -> Unit + val eventSink: (EditUserProfileEvent) -> Unit ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileStateProvider.kt index 0e4c1b4f2d..ca9571aea5 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileStateProvider.kt @@ -1,38 +1,46 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.preferences.impl.user.editprofile -import android.net.Uri import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import androidx.core.net.toUri import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.ui.media.AvatarAction +import io.element.android.libraries.permissions.api.PermissionsState import io.element.android.libraries.permissions.api.aPermissionsState -import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList open class EditUserProfileStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aEditUserProfileState(), - aEditUserProfileState(userAvatarUrl = "example://uri".toUri()), - // Add other states here + aEditUserProfileState(userAvatarUrl = "example://uri"), + aEditUserProfileState(saveAction = AsyncAction.ConfirmingCancellation), ) } fun aEditUserProfileState( - userAvatarUrl: Uri? = null, + userId: UserId = UserId("@john.doe:matrix.org"), + displayName: String = "John Doe", + userAvatarUrl: String? = null, + avatarActions: List = emptyList(), + saveButtonEnabled: Boolean = true, + saveAction: AsyncAction = AsyncAction.Uninitialized, + cameraPermissionState: PermissionsState = aPermissionsState(showDialog = false), + eventSink: (EditUserProfileEvent) -> Unit = {}, ) = EditUserProfileState( - userId = UserId("@john.doe:matrix.org"), - displayName = "John Doe", + userId = userId, + displayName = displayName, userAvatarUrl = userAvatarUrl, - avatarActions = persistentListOf(), - saveAction = AsyncAction.Uninitialized, - saveButtonEnabled = true, - cameraPermissionState = aPermissionsState(showDialog = false), - eventSink = {} + avatarActions = avatarActions.toImmutableList(), + saveButtonEnabled = saveButtonEnabled, + saveAction = saveAction, + cameraPermissionState = cameraPermissionState, + eventSink = eventSink, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt index 00581745ce..5b42440101 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt @@ -1,12 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.preferences.impl.user.editprofile +import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -29,11 +31,13 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.features.preferences.impl.R +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarType import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.dialogs.SaveChangesDialog import io.element.android.libraries.designsystem.modifiers.clearFocusOnTap import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -51,7 +55,6 @@ import io.element.android.libraries.ui.strings.CommonStrings @Composable fun EditUserProfileView( state: EditUserProfileState, - onBackClick: () -> Unit, onEditProfileSuccess: () -> Unit, modifier: Modifier = Modifier, ) { @@ -63,19 +66,28 @@ fun EditUserProfileView( isAvatarActionsSheetVisible.value = true } + fun onBackClick() { + focusManager.clearFocus() + state.eventSink(EditUserProfileEvent.Exit) + } + + BackHandler( + enabled = true, + ::onBackClick, + ) Scaffold( modifier = modifier.clearFocusOnTap(focusManager), topBar = { TopAppBar( titleStr = stringResource(R.string.screen_edit_profile_title), - navigationIcon = { BackButton(onClick = onBackClick) }, + navigationIcon = { BackButton(::onBackClick) }, actions = { TextButton( text = stringResource(CommonStrings.action_save), enabled = state.saveButtonEnabled, onClick = { focusManager.clearFocus() - state.eventSink(EditUserProfileEvents.Save) + state.eventSink(EditUserProfileEvent.Save) }, ) } @@ -113,7 +125,7 @@ fun EditUserProfileView( value = state.displayName, placeholder = stringResource(CommonStrings.common_room_name_placeholder), singleLine = true, - onValueChange = { state.eventSink(EditUserProfileEvents.UpdateDisplayName(it)) }, + onValueChange = { state.eventSink(EditUserProfileEvent.UpdateDisplayName(it)) }, ) } @@ -121,7 +133,7 @@ fun EditUserProfileView( actions = state.avatarActions, isVisible = isAvatarActionsSheetVisible.value, onDismiss = { isAvatarActionsSheetVisible.value = false }, - onSelectAction = { state.eventSink(EditUserProfileEvents.HandleAvatarAction(it)) } + onSelectAction = { state.eventSink(EditUserProfileEvent.HandleAvatarAction(it)) } ) AsyncActionView( @@ -131,10 +143,21 @@ fun EditUserProfileView( progressText = stringResource(R.string.screen_edit_profile_updating_details), ) }, + confirmationDialog = { confirming -> + when (confirming) { + is AsyncAction.ConfirmingCancellation -> { + SaveChangesDialog( + onSaveClick = { state.eventSink(EditUserProfileEvent.Save) }, + onDiscardClick = { state.eventSink(EditUserProfileEvent.Exit) }, + onDismiss = { state.eventSink(EditUserProfileEvent.CloseDialog) }, + ) + } + } + }, onSuccess = { onEditProfileSuccess() }, errorTitle = { stringResource(R.string.screen_edit_profile_error_title) }, errorMessage = { stringResource(R.string.screen_edit_profile_error) }, - onErrorDismiss = { state.eventSink(EditUserProfileEvents.CancelSaveChanges) }, + onErrorDismiss = { state.eventSink(EditUserProfileEvent.CloseDialog) }, ) } PermissionsView( @@ -147,7 +170,6 @@ fun EditUserProfileView( internal fun EditUserProfileViewPreview(@PreviewParameter(EditUserProfileStateProvider::class) state: EditUserProfileState) = ElementPreview { EditUserProfileView( - onBackClick = {}, onEditProfileSuccess = {}, state = state, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/utils/ShowDeveloperSettingsProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/utils/ShowDeveloperSettingsProvider.kt index d87b205d5e..b0cfea07d4 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/utils/ShowDeveloperSettingsProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/utils/ShowDeveloperSettingsProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/main/res/values-bg/translations.xml b/features/preferences/impl/src/main/res/values-bg/translations.xml index bfcab248c6..704bcc256d 100644 --- a/features/preferences/impl/src/main/res/values-bg/translations.xml +++ b/features/preferences/impl/src/main/res/values-bg/translations.xml @@ -4,6 +4,7 @@ "Режим за програмисти" "Активирайте, за да имате достъп до функции и функционалности за програмисти." "Скриване на профилните снимки в заявките за покана за стая" + "Експерименти" "Качвайте снимки и видеоклипове по-бързо и намалете използването на данни" "Оптимизиране на качеството на медията" "Модерация и безопасност" @@ -27,6 +28,9 @@ "Не може да се обнови профила" "Редактиране на профила" "Обновяване на профила…" + "Включване на отговори в нишка" + "Искате ли да експериментирате?" + "Експерименти" "Допълнителни настройки" "Аудио и видео разговори" "Несъответствие в конфигурацията" diff --git a/features/preferences/impl/src/main/res/values-cs/translations.xml b/features/preferences/impl/src/main/res/values-cs/translations.xml index 9fe8715cc3..0127a98b41 100644 --- a/features/preferences/impl/src/main/res/values-cs/translations.xml +++ b/features/preferences/impl/src/main/res/values-cs/translations.xml @@ -10,6 +10,7 @@ "Neplatné URL, ujistěte se, že jste uvedli protokol (http/https) a správnou adresu." "Skrýt avatary v žádostech o pozvání do místnosti" "Skrýt náhledy médií na časové ose" + "Experimentální funkce" "Rychlejší nahrávání fotografií a videí a snížení spotřeby dat" "Optimalizace kvality médií" "Moderování a bezpečnost" @@ -43,6 +44,11 @@ "Nelze aktualizovat profil" "Upravit profil" "Aktualizace profilu…" + "Povolit odpovědi ve vlákně" + "Aplikace se restartuje, aby se tato změna projevila." + "Vyzkoušejte naše nejnovější nápady, které jsou ve vývoji. Tyto funkce nejsou finalizované; mohou být nestabilní a mohou se změnit." + "Máte chuť experimentovat?" + "Experimentální funkce" "Další nastavení" "Halsové a video hovory" "Neshoda konfigurace" diff --git a/features/preferences/impl/src/main/res/values-en-rUS/translations.xml b/features/preferences/impl/src/main/res/values-en-rUS/translations.xml index b1f615e697..9f69227bec 100644 --- a/features/preferences/impl/src/main/res/values-en-rUS/translations.xml +++ b/features/preferences/impl/src/main/res/values-en-rUS/translations.xml @@ -3,4 +3,5 @@ "Optimize media quality" "Automatically optimize images for faster uploads and smaller file sizes." "Optimize image upload quality" + "Try out our latest ideas in development. These features are not finalized; they may be unstable, may change." diff --git a/features/preferences/impl/src/main/res/values-et/translations.xml b/features/preferences/impl/src/main/res/values-et/translations.xml index 9b2f04a9f7..4e7ba1c26e 100644 --- a/features/preferences/impl/src/main/res/values-et/translations.xml +++ b/features/preferences/impl/src/main/res/values-et/translations.xml @@ -10,6 +10,7 @@ "Vigane url. Palun vaata, et url algaks protokolliga (http/https) ning aadress ise oleks ka õige." "Peida jututubade kutsetest tunnuspildid" "Peida meedia eelvaated ajajoonel" + "Katsed" "Sellega laadid fotosid ja videoid kiiremini üles ning vähendad andmemahtu" "Optimeeri meedia kvaliteeti" "Modereerimine ja ohutus" @@ -43,6 +44,11 @@ "Profiili uuendamine ei õnnestunud" "Muuda profiili" "Profiil on muutmisel…" + "Võta kasutusele vastamine jutulõngas" + "Selle muudatuse jõustamiseks käivitub rakendus uuesti." + "Katseta meie uusimaid arendusideid. Need funktsionaalsused pole veel lõplikud, nad ei pruugi toimida parimal viisil ning võivad veel muutuda." + "Kas tahad katsetada?" + "Katsed" "Täiendavad seadistused" "Hääl- ja videokõned" "Eelistused ei sobi omavahel" diff --git a/features/preferences/impl/src/main/res/values-fa/translations.xml b/features/preferences/impl/src/main/res/values-fa/translations.xml index 22ff843dc2..05e73febb4 100644 --- a/features/preferences/impl/src/main/res/values-fa/translations.xml +++ b/features/preferences/impl/src/main/res/values-fa/translations.xml @@ -9,8 +9,13 @@ "URL نامعتبر، لطفا مطمئن شوید که پروتکل (http/https) و آدرس صحیح را درج کرده اید." "نهفتن چهرک‌ها در درخواست‌های دعوت اتاق" "نهفتن رسانه در خط زمانی" + "آزمایشگاه‌ها" "بهینه سازی کیفیت رسانه" "نظارت و امنیت" + "زیاد (۱۰۸۰ت)" + "کم (۴۸۰ت)" + "استاندارد (۷۲۰ت)" + "کیفیت بارگذاری ویدیو" "فراهم کنندهٔ آگاهی‌های ارسالی" "از کار انداختن ویرایشگر متن غنی یا نوشتن دستی مارک‌دون." "رسید‌های خواندن" @@ -31,6 +36,7 @@ "ناتوان در به‌روز کردن نمایه" "ویرایش نمایه" "به‌روز کردن نمایه…" + "آزمایشگاه‌ها" "تنظیمات اضافی" "تماس‌های صوتی و تصویری" "نامتطابقت در پیکربندی" diff --git a/features/preferences/impl/src/main/res/values-hr/translations.xml b/features/preferences/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..250af33d49 --- /dev/null +++ b/features/preferences/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,82 @@ + + + "Kako biste bili sigurni da nikada nećete propustiti važan poziv, promijenite postavke kako biste omogućili obavijesti preko cijelog zaslona kada je telefon zaključan." + "Poboljšajte svoje iskustvo poziva" + "Odaberite kako želite primati obavijesti" + "Način rada za razvojne inženjere" + "Omogućite pristup značajkama i funkcionalnostima za razvojne inženjere." + "Prilagođeni osnovni URL za Element Call" + "Postavite prilagođeni osnovni URL za Element Call." + "Nevažeći URL; provjerite jeste li uključili protokol (http/https) i ispravnu adresu." + "Sakrij avatare u zahtjevima za poziv u sobu" + "Sakrij preglede medija na vremenskoj traci" + "Laboratoriji" + "Brže prenesite fotografije i videozapise te smanjite potrošnju podataka" + "Optimiziraj kvalitetu medija" + "Moderiranje i sigurnost" + "Automatski optimizirajte slike za brži prijenos i manje veličine datoteka." + "Optimiziraj kvalitetu prijenosa slika" + "%1$s. Ovdje dodirnite za promjenu." + "Visoka (1080p)" + "Niska (480p)" + "Standardna (720p)" + "Kvaliteta prijenosa videozapisa" + "Pružatelj push obavijesti" + "Onemogućite uređivač obogaćenog teksta kako biste ručno tipkali Markdown." + "Potvrde o čitanju" + "Ako je to isključeno, vaše potvrde o čitanju neće se slati nikome. I dalje ćete primati potvrde o čitanju od drugih korisnika." + "Podijeli prisutnost" + "Ako je to isključeno, nećete moći slati ili primati potvrde o čitanju ili obavijesti o tipkanju." + "Uvijek sakrij" + "Uvijek prikaži" + "U privatnim sobama" + "Skriveni medij uvijek se može prikazati tako se da se dodirne" + "Prikaži medije na vremenskoj traci" + "Omogući opciju za prikaz izvora poruke na vremenskoj traci." + "Nemate blokiranih korisnika" + "Odblokiraj" + "Moći ćete ponovno vidjeti sve njihove poruke." + "Odblokiraj korisnika" + "Deblokiranje…" + "Ime za prikaz" + "Vaše ime za prikaz" + "Došlo je do nepoznate pogreške i informacije se nisu mogle promijeniti." + "Nije moguće ažurirati profil" + "Uredi profil" + "Ažuriranje profila…" + "Omogući odgovore u nizu" + "Aplikacija će se ponovno pokrenuti kako bi se primijenila ova promjena." + "Isprobajte naše najnovije ideje u razvoju. Ove značajke nisu finalizirane; mogu biti nestabilne i mijenjati se." + "Jeste li spremni za eksperimentiranje?" + "Laboratoriji" + "Dodatne postavke" + "Audiopozivi i videopozivi" + "Neusklađenost konfiguracije" + "Pojednostavili smo postavke obavijesti kako bismo olakšali pronalaženje mogućnosti. Neke prilagođene postavke koje ste odabrali u prošlosti nisu ovdje prikazane, ali su i dalje aktivne. + +Ako nastavite, neke od vaših postavki mogu se promijeniti." + "Izravni razgovori" + "Prilagođena postavka po razgovoru" + "Došlo je do pogreške prilikom ažuriranja postavke obavijesti." + "Sve poruke" + "Samo spominjanja i ključne riječi" + "U izravnim razgovorima obavijesti me za" + "U grupnim chatovima obavijesti me za" + "Omogući obavijesti na ovom uređaju" + "Konfiguracija nije ispravljena, pokušajte ponovno." + "Grupni razgovori" + "Pozivnice" + "Vaš matični poslužitelj ne podržava ovu mogućnost u šifriranim sobama; možda nećete dobiti obavijesti u nekim sobama." + "Spominjanja" + "Sve" + "Spominjanja" + "Obavijesti me za" + "Obavijesti me o sobi @soba" + "Kako biste primali obavijesti, promijenite %1$s." + "postavke sustava" + "Obavijesti sustava su isključene" + "Obavijesti" + "Povijest push obavijesti" + "Rješavanje problema" + "Rješavanje problema s obavijestima" + diff --git a/features/preferences/impl/src/main/res/values-hu/translations.xml b/features/preferences/impl/src/main/res/values-hu/translations.xml index 9cc5a97a44..c588e60eaf 100644 --- a/features/preferences/impl/src/main/res/values-hu/translations.xml +++ b/features/preferences/impl/src/main/res/values-hu/translations.xml @@ -10,6 +10,7 @@ "Érvénytelen webcím, győződjön meg arról, hogy szerepel-e benne a protokoll (http/https), és hogy helyes-e a cím." "Profilképek elrejtése a szobameghívókban" "Médiaelőnézetek elrejtése az idővonalon" + "Kísérletek" "Töltse fel gyorsabban a fényképeket és videókat, valamint csökkentse az adatforgalmat" "Média minőségének optimalizálása" "Moderálás és biztonság" @@ -43,6 +44,11 @@ "Nem sikerült frissíteni a profilt" "Profil szerkesztése" "Profil frissítése…" + "Üzenetszál válaszok engedélyezése" + "Az alkalmazás újraindul, hogy a változás érvénybe lépjen." + "Próbálja ki legújabb fejlesztéseinket. Ezek a funkciók még nem véglegesek, instabilak lehetnek és változhatnak." + "Kísérletezni szeretne?" + "Kísérletek" "További beállítások" "Hang- és videóhívások" "Konfigurációs eltérés" diff --git a/features/preferences/impl/src/main/res/values-it/translations.xml b/features/preferences/impl/src/main/res/values-it/translations.xml index 0a37b0d394..a10dcc9594 100644 --- a/features/preferences/impl/src/main/res/values-it/translations.xml +++ b/features/preferences/impl/src/main/res/values-it/translations.xml @@ -10,6 +10,7 @@ "URL non valido, assicurati di includere il protocollo (http/https) e l\'indirizzo corretto." "Nascondi gli avatar nelle richieste di invito alle stanze" "Nascondi le anteprime dei media nelle conversazioni" + "Labs" "Carica foto e video più velocemente e riduci l\'utilizzo dei dati" "Ottimizza la qualità dei contenuti multimediali" "Moderazione e Sicurezza" @@ -43,6 +44,11 @@ "Impossibile aggiornare il profilo" "Modifica profilo" "Aggiornamento del profilo…" + "Abilita le risposte alle discussioni" + "L\'app si riavvierà per applicare questa modifica." + "Prova le nostre ultime idee in fase di sviluppo. Queste funzionalità non sono definitive; potrebbero essere instabili e soggette a modifiche." + "Hai voglia di sperimentare?" + "Labs" "Impostazioni aggiuntive" "Chiamate audio e video" "Mancata corrispondenza di configurazione" diff --git a/features/preferences/impl/src/main/res/values-pl/translations.xml b/features/preferences/impl/src/main/res/values-pl/translations.xml index 8ae089b170..9e2c56e580 100644 --- a/features/preferences/impl/src/main/res/values-pl/translations.xml +++ b/features/preferences/impl/src/main/res/values-pl/translations.xml @@ -10,9 +10,17 @@ "Nieprawidłowy adres URL, upewnij się, że zawiera protokół (http/https) i poprawny adres." "Ukryj awatary w prośbach o dołączenie do pokoju" "Ukryj podglądy multimediów na osi czasu" + "Laboratoria" "Przesyłaj zdjęcia i filmy szybciej, zmniejszając zużycie danych" "Optymalizuj jakość multimediów" "Moderacja i bezpieczeństwo" + "Automatycznie optymalizuj obrazy, aby szybciej je przesyłać i zmniejszać rozmiar plików." + "Zoptymalizuj jakość przesyłania obrazów" + "%1$s. Dotknij tutaj, aby zmienić." + "Wysoka (1080p)" + "Niska (480p)" + "Standardowa (720p)" + "Jakość przesyłania wideo" "Dostawca powiadomień push" "Wyłącz edytor tekstu bogatego, aby pisać tekst Markdown ręcznie." "Potwierdzenia odczytania" @@ -36,6 +44,11 @@ "Nie można zaktualizować profilu" "Edytuj profil" "Aktualizuję profil…" + "Włącz odpowiedzi w wątkach" + "Aplikacja uruchomi się ponownie, aby zastosować tę zmianę." + "Wypróbuj nasze najnowsze pomysły w fazie rozwoju. Funkcje te nie są jeszcze sfinalizowane; mogą być niestabilne i ulec zmianie." + "Chcesz poeksperymentować?" + "Laboratoria" "Dodatkowe ustawienia" "Połączenia audio i wideo" "Niezgodność konfiguracji" diff --git a/features/preferences/impl/src/main/res/values-pt-rBR/translations.xml b/features/preferences/impl/src/main/res/values-pt-rBR/translations.xml index 5b3e75e7ec..f6e1bc90ba 100644 --- a/features/preferences/impl/src/main/res/values-pt-rBR/translations.xml +++ b/features/preferences/impl/src/main/res/values-pt-rBR/translations.xml @@ -10,9 +10,17 @@ "URL inválida, por favor verifique se o protocolo (http/https) está incluso e o endereço correto." "Ocultar avatares em solicitações de convite para salas" "Ocultar pré-visualizações de mídia na linha do tempo" + "Experimentos" "Envie fotos e vídeos com mais rapidez e reduza o uso de dados" "Otimizar a qualidade da mídia" "Moderação e segurança" + "Otimizar automaticamente as imagens para envios mais rápidos e arquivos com tamanhos menores." + "Otimizar qualidade de envio de imagens" + "%1$s. Toque aqui para alterar." + "Alta (1080p)" + "Baixa (480p)" + "Normal (720p)" + "Qualidade de envio de vídeos" "Provedor de notificações push" "Desative o editor de rich text para digitar Markdown manualmente." "Confirmações de leitura" @@ -36,6 +44,11 @@ "Não foi possível atualizar o perfil" "Editar perfil" "Atualizando o perfil…" + "Ativar respostas de tópicos" + "O app será reiniciado para aplicar esta mudança." + "Teste as nossas mais novas ideias em desenvolvimento. Esses recursos não estão finalizados; podem estar instáveis, e podem mudar." + "Se sentindo experimental?" + "Experimentos" "Configurações adicionais" "Chamadas de áudio e vídeo" "Não correspondência de configuração" diff --git a/features/preferences/impl/src/main/res/values-ru/translations.xml b/features/preferences/impl/src/main/res/values-ru/translations.xml index 6ac5a5d03c..7b2e1ebdb6 100644 --- a/features/preferences/impl/src/main/res/values-ru/translations.xml +++ b/features/preferences/impl/src/main/res/values-ru/translations.xml @@ -45,6 +45,7 @@ "Редактировать профиль" "Обновление профиля…" "Включить ответы в топике" + "Приложение перезапустится, чтобы применить это изменение." "Попробуйте наши последние идеи в разработке. Эти функции ещё не завершены, они могут быть нестабильны и могут измениться." "Хотите попробовать?" "Лаборатория" diff --git a/features/preferences/impl/src/main/res/values-sk/translations.xml b/features/preferences/impl/src/main/res/values-sk/translations.xml index f484a1aee0..9968c6d4cb 100644 --- a/features/preferences/impl/src/main/res/values-sk/translations.xml +++ b/features/preferences/impl/src/main/res/values-sk/translations.xml @@ -10,6 +10,7 @@ "Neplatná adresa URL, uistite sa, že ste uviedli protokol (http/https) a správnu adresu." "Skrytie profilové obrázky v žiadostiach o pozvánku do miestnosti" "Skryť ukážky médií na časovej osi" + "Laboratóriá" "Nahrávajte fotografie a videá rýchlejšie a znížte spotrebu dát" "Optimalizovať kvalitu médií" "Moderovanie a bezpečnosť" @@ -43,6 +44,11 @@ "Nepodarilo sa aktualizovať profil" "Upraviť profil" "Aktualizácia profilu…" + "Povoliť odpovede vo vlákne" + "Aplikácia sa reštartuje, aby sa táto zmena prejavila." + "Vyskúšajte naše najnovšie nápady vo vývoji. Tieto funkcie nie sú finalizované; môžu byť nestabilné a môžu sa zmeniť." + "Máte chuť experimentovať?" + "Laboratóriá" "Ďalšie nastavenia" "Audio a video hovory" "Nezhoda konfigurácie" diff --git a/features/preferences/impl/src/main/res/values-uz/translations.xml b/features/preferences/impl/src/main/res/values-uz/translations.xml index b01bd11fbd..82e9b24e98 100644 --- a/features/preferences/impl/src/main/res/values-uz/translations.xml +++ b/features/preferences/impl/src/main/res/values-uz/translations.xml @@ -8,14 +8,29 @@ "Maxsus element qo‘ng‘iroqlar bazasi URL manzili" "Element qo\'ng\'irog\'iga maxsus asosiy url or\'natish" "URL noto‘g‘ri, iltimos, protokol (http/https) va to‘g‘ri manzilni kiritganingizga ishonch hosil qiling." + "Xonaga taklif so‘rovlarida avatarlarni berkitish" + "Vaqt jadvalida mediaga razm solishlarni berkitish" "Rasm va videolarni tezroq yuklang va trafik sarfini kamaytiring" "Media sifatini yaxshilash" + "Moderatsiya va xavfsizlik" + "Tezroq yuklash va kichikroq fayl hajmi uchun rasmlarni avtomatik optimallashtirish." + "Rasm yuklash sifatini optimallashtirish" + "%1$s. Oʻzgartirish uchun bu yerga bosing." + "Yuqori (1080p)" + "Past (480p)" + "Standart (720p)" + "Video yuklash sifati" "Push bildirishnoma provayderi" "Boy matn muharriri o\'chiring Markdown bilan qo\'lda yozish uchun" "Kvitansiyalarni oʻqish" "Agar oʻchirib qo‘yilsa, sizning oʻqilganlik bildirishnomangiz hech kimga yuborilmaydi. Siz boshqa foydalanuvchilardan oʻqilganlik bildirishnomalarini olishda davom etasiz." "Mavjudligini ulashish" "Agar oʻchirib qoʻyilsa, siz oʻqilganlik haqidagi bildirishnomalarni yoki yozayotganingiz haqidagi xabarlarni yubora olmaysiz va qabul qila olmaysiz." + "Doim berkitilsin" + "Har doim ko‘rsatish" + "Shaxsiy xonalarda" + "Yashirin media har doim unga bosish orqali ko‘rsatilishi mumkin" + "Vaqt jadvalida media ko‘rsatish" "Xabar manbasini vaqt jadvalida ko‘rish imkoniyatini yoqing." "Sizda bloklangan foydalanuvchi yo‘q" "Blokdan chiqarish" @@ -55,6 +70,7 @@ Davom ettirsangiz, baʼzi sozlamalaringiz oʻzgarishi mumkin." "tizim sozlamalari" "Tizim bildirishnomalari o\'chirilgan" "Bildirishnomalar" + "Bildirishnoma tarixi" "Muammolarni bartaraf etish" "Bildirishnomalar bilan bog‘liq muammolarni bartaraf etish" diff --git a/features/preferences/impl/src/main/res/values-zh-rTW/translations.xml b/features/preferences/impl/src/main/res/values-zh-rTW/translations.xml index bda2b085a5..634911100a 100644 --- a/features/preferences/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/preferences/impl/src/main/res/values-zh-rTW/translations.xml @@ -10,6 +10,7 @@ "無效的 URL,請確定包含了協定 (http/https) 與正確的地址。" "在聊天室邀請請求中隱藏大頭貼" "在時間軸中隱藏媒體預覽" + "實驗室" "上傳照片與影片更快且減少資料使用量" "最佳化媒體品質" "管理與安全" @@ -43,6 +44,11 @@ "無法更新個人檔案" "編輯個人檔案" "正在更新個人檔案…" + "啟用討論串回覆" + "應用程式將會重新啟動以套用此變更。" + "試試我們正在開發中的最新構想。這些功能可能尚未完成,可能不夠穩定,也可能隨時變動。" + "想不想來點新花樣?" + "實驗室" "其他設定" "音訊與視訊通話" "組態錯誤" diff --git a/features/preferences/impl/src/main/res/values-zh/translations.xml b/features/preferences/impl/src/main/res/values-zh/translations.xml index 7ac8952090..0c576b58b6 100644 --- a/features/preferences/impl/src/main/res/values-zh/translations.xml +++ b/features/preferences/impl/src/main/res/values-zh/translations.xml @@ -44,6 +44,10 @@ "无法更新个人资料" "编辑个人资料" "更新个人资料……" + "启用主题回复" + "应用将重启以应用此更改。" + "尝试我们最新的开发理念。这些功能尚未最终确定,可能不稳定,也可能会发生变化。" + "想尝试新功能?" "实验室" "更多设置" "音视频通话" diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/DefaultPreferencesEntryPointTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/DefaultPreferencesEntryPointTest.kt index 9e1bd70376..963d7846b4 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/DefaultPreferencesEntryPointTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/DefaultPreferencesEntryPointTest.kt @@ -1,27 +1,26 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.preferences.impl -import android.content.Context import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node import com.bumble.appyx.testing.junit4.util.MainDispatcherRule import com.google.common.truth.Truth.assertThat -import io.element.android.features.deactivation.api.AccountDeactivationEntryPoint -import io.element.android.features.licenses.api.OpenSourceLicensesEntryPoint -import io.element.android.features.lockscreen.api.LockScreenEntryPoint -import io.element.android.features.logout.api.LogoutEntryPoint +import io.element.android.features.deactivation.test.FakeAccountDeactivationEntryPoint +import io.element.android.features.licenses.test.FakeOpenSourceLicensesEntryPoint +import io.element.android.features.lockscreen.test.FakeLockScreenEntryPoint +import io.element.android.features.logout.test.FakeLogoutEntryPoint import io.element.android.features.preferences.api.PreferencesEntryPoint import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.troubleshoot.api.NotificationTroubleShootEntryPoint -import io.element.android.libraries.troubleshoot.api.PushHistoryEntryPoint +import io.element.android.libraries.troubleshoot.test.FakeNotificationTroubleShootEntryPoint +import io.element.android.libraries.troubleshoot.test.FakePushHistoryEntryPoint import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.node.TestParentNode import org.junit.Rule @@ -41,41 +40,31 @@ class DefaultPreferencesEntryPointTest { PreferencesFlowNode( buildContext = buildContext, plugins = plugins, - lockScreenEntryPoint = object : LockScreenEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext, navTarget: LockScreenEntryPoint.Target) = lambdaError() - override fun pinUnlockIntent(context: Context) = lambdaError() - }, - notificationTroubleShootEntryPoint = object : NotificationTroubleShootEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext) = lambdaError() - }, - pushHistoryEntryPoint = object : PushHistoryEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext) = lambdaError() - }, - logoutEntryPoint = object : LogoutEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext) = lambdaError() - }, - openSourceLicensesEntryPoint = object : OpenSourceLicensesEntryPoint { - override fun createNode(parentNode: Node, buildContext: BuildContext) = lambdaError() - }, - accountDeactivationEntryPoint = object : AccountDeactivationEntryPoint { - override fun createNode(parentNode: Node, buildContext: BuildContext) = lambdaError() - }, + lockScreenEntryPoint = FakeLockScreenEntryPoint(), + notificationTroubleShootEntryPoint = FakeNotificationTroubleShootEntryPoint(), + pushHistoryEntryPoint = FakePushHistoryEntryPoint(), + logoutEntryPoint = FakeLogoutEntryPoint(), + openSourceLicensesEntryPoint = FakeOpenSourceLicensesEntryPoint(), + accountDeactivationEntryPoint = FakeAccountDeactivationEntryPoint(), ) } val callback = object : PreferencesEntryPoint.Callback { - override fun onAddAccount() = lambdaError() - override fun onOpenBugReport() = lambdaError() - override fun onSecureBackupClick() = lambdaError() - override fun onOpenRoomNotificationSettings(roomId: RoomId) = lambdaError() - override fun navigateTo(roomId: RoomId, eventId: EventId) = lambdaError() + override fun navigateToAddAccount() = lambdaError() + override fun navigateToLinkNewDevice() = lambdaError() + override fun navigateToBugReport() = lambdaError() + override fun navigateToSecureBackup() = lambdaError() + override fun navigateToRoomNotificationSettings(roomId: RoomId) = lambdaError() + override fun navigateToEvent(roomId: RoomId, eventId: EventId) = lambdaError() } val params = PreferencesEntryPoint.Params( initialElement = PreferencesEntryPoint.InitialTarget.NotificationSettings, ) - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .params(params) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + params = params, + callback = callback, + ) assertThat(result).isInstanceOf(PreferencesFlowNode::class.java) assertThat(result.plugins).contains(params) assertThat(result.plugins).contains(callback) 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 e9b9e2a5ef..eb0aa73675 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/about/AboutViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/about/AboutViewTest.kt index 95c22da46f..258e9855de 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/about/AboutViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/about/AboutViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt index 9bd90aa28c..942d549dab 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt index 9e105d4da1..36fd30983e 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/FakeMediaPreviewConfigStateStore.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/FakeMediaPreviewConfigStateStore.kt index 18c6283965..b07e8b7a55 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/FakeMediaPreviewConfigStateStore.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/FakeMediaPreviewConfigStateStore.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/MediaPreviewConfigStateStoreTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/MediaPreviewConfigStateStoreTest.kt index 01ede60e92..becd6b1a2f 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/MediaPreviewConfigStateStoreTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/MediaPreviewConfigStateStoreTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenterTest.kt index 67d7f3688f..5a00a1f7a7 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/analytics/AnalyticsSettingsPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUserViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUserViewTest.kt index ec7ac7e2e3..b3549762ab 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUserViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUserViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenterTest.kt index bb68063797..d3ac29df9a 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 76b22871f0..1fcf9bff70 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,22 +10,36 @@ package io.element.android.features.preferences.impl.developer +import androidx.compose.ui.graphics.Color import com.google.common.truth.Truth.assertThat +import io.element.android.features.enterprise.api.EnterpriseService +import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem import io.element.android.features.preferences.impl.tasks.FakeClearCacheUseCase import io.element.android.features.preferences.impl.tasks.FakeComputeCacheSizeUseCase +import io.element.android.features.preferences.impl.tasks.VacuumStoresUseCase import io.element.android.features.rageshake.api.preferences.aRageshakePreferencesState +import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.core.data.megaBytes import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildType +import io.element.android.libraries.featureflag.api.Feature import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeature import io.element.android.libraries.featureflag.test.FakeFeatureFlagService +import io.element.android.libraries.matrix.api.analytics.GetDatabaseSizesUseCase +import io.element.android.libraries.matrix.api.analytics.SdkStoreSizes +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.test +import kotlinx.collections.immutable.persistentMapOf import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -36,15 +51,22 @@ class DeveloperSettingsPresenterTest { @Test fun `present - ensures initial states are correct`() = runTest { - val availableFeatures = listOf( - FakeFeature( - key = "feature_1", - title = "Feature 1", - isInLabs = false, + val getAvailableFeaturesResult = lambdaRecorder> { _, _ -> + listOf( + FakeFeature( + key = "feature_1", + title = "Feature 1", + isInLabs = false, + ) ) - ) + } val presenter = createDeveloperSettingsPresenter( - featureFlagService = FakeFeatureFlagService(providedAvailableFeatures = availableFeatures) + featureFlagService = FakeFeatureFlagService(getAvailableFeaturesResult = getAvailableFeaturesResult), + databaseSizesUseCase = GetDatabaseSizesUseCase { + Result.success( + SdkStoreSizes(stateStore = 10.megaBytes, eventCacheStore = 10.megaBytes, mediaStore = 10.megaBytes, cryptoStore = 10.megaBytes) + ) + } ) presenter.test { awaitItem().also { state -> @@ -57,6 +79,8 @@ class DeveloperSettingsPresenterTest { assertThat(state.rageshakeState.isSupported).isTrue() assertThat(state.rageshakeState.sensitivity).isEqualTo(0.3f) assertThat(state.tracingLogLevel).isEqualTo(AsyncData.Uninitialized) + assertThat(state.isEnterpriseBuild).isFalse() + assertThat(state.showColorPicker).isFalse() } awaitItem().also { state -> assertThat(state.features).isNotEmpty() @@ -65,7 +89,17 @@ class DeveloperSettingsPresenterTest { } awaitItem().also { state -> assertThat(state.cacheSize).isInstanceOf(AsyncData.Success::class.java) + assertThat(state.databaseSizes.dataOrNull()).isEqualTo( + persistentMapOf( + "State store" to "10485760 Bytes", + "Event cache store" to "10485760 Bytes", + "Media store" to "10485760 Bytes", + "Crypto store" to "10485760 Bytes" + ) + ) } + getAvailableFeaturesResult.assertions().isCalledOnce() + .with(value(false), value(false)) } } @@ -171,62 +205,82 @@ class DeveloperSettingsPresenterTest { } @Test - fun `present - won't display features in labs or finished`() = runTest { - val availableFeatures = listOf( - // Only this feature should be displayed - FakeFeature( - key = "feature_1", - title = "Feature 1", - isInLabs = false, - ), - FakeFeature( - key = "feature_2", - title = "Feature 2", - isInLabs = true, - ), - FakeFeature( - key = "feature_3", - title = "Feature 3", - isInLabs = false, - isFinished = true, - ) - ) - + fun `present - enterprise build can change the brand color`() = runTest { + val overrideBrandColorResult = lambdaRecorder { _, _ -> } val presenter = createDeveloperSettingsPresenter( - featureFlagService = FakeFeatureFlagService( - providedAvailableFeatures = availableFeatures, + enterpriseService = FakeEnterpriseService( + isEnterpriseBuild = true, + overrideBrandColorResult = overrideBrandColorResult, ) ) presenter.test { - skipItems(2) - awaitItem().also { state -> - assertThat(state.features).hasSize(1) + skipItems(1) + val initialState = awaitItem() + assertThat(initialState.isEnterpriseBuild).isTrue() + initialState.eventSink(DeveloperSettingsEvents.SetShowColorPicker(true)) + assertThat(awaitItem().showColorPicker).isTrue() + initialState.eventSink(DeveloperSettingsEvents.SetShowColorPicker(false)) + assertThat(awaitItem().showColorPicker).isFalse() + initialState.eventSink(DeveloperSettingsEvents.SetShowColorPicker(true)) + assertThat(awaitItem().showColorPicker).isTrue() + initialState.eventSink(DeveloperSettingsEvents.ChangeBrandColor(Color.Green)) + assertThat(awaitItem().showColorPicker).isFalse() + skipItems(1) + overrideBrandColorResult.assertions().isCalledOnce() + .with(value(A_SESSION_ID), value("#00FF00")) + } + } + + @Test + fun `present - VacuumStores action invokes the VacuumStoresUseCase`() = runTest { + var vacuumCalled = false + val presenter = createDeveloperSettingsPresenter( + vacuumStoresUseCase = VacuumStoresUseCase { + vacuumCalled = true } + ) + presenter.test { + val state = awaitItem() + assertThat(vacuumCalled).isFalse() + state.eventSink(DeveloperSettingsEvents.VacuumStores) + skipItems(1) + assertThat(vacuumCalled).isTrue() } } private fun createDeveloperSettingsPresenter( + sessionId: SessionId = A_SESSION_ID, featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService( - providedAvailableFeatures = listOf( - FakeFeature( - key = "feature_1", - title = "Feature 1", - isInLabs = false, + getAvailableFeaturesResult = { _, _ -> + listOf( + FakeFeature( + key = "feature_1", + title = "Feature 1", + isInLabs = false, + ) ) - ) + } ), cacheSizeUseCase: FakeComputeCacheSizeUseCase = FakeComputeCacheSizeUseCase(), clearCacheUseCase: FakeClearCacheUseCase = FakeClearCacheUseCase(), preferencesStore: InMemoryAppPreferencesStore = InMemoryAppPreferencesStore(), buildMeta: BuildMeta = aBuildMeta(), + enterpriseService: EnterpriseService = FakeEnterpriseService(), + vacuumStoresUseCase: VacuumStoresUseCase = VacuumStoresUseCase {}, + databaseSizesUseCase: GetDatabaseSizesUseCase = GetDatabaseSizesUseCase { Result.success(SdkStoreSizes(null, null, null, null)) }, ): DeveloperSettingsPresenter { return DeveloperSettingsPresenter( + sessionId = sessionId, featureFlagService = featureFlagService, computeCacheSizeUseCase = cacheSizeUseCase, clearCacheUseCase = clearCacheUseCase, rageshakePresenter = { aRageshakePreferencesState() }, appPreferencesStore = preferencesStore, buildMeta = buildMeta, + enterpriseService = enterpriseService, + vacuumStoresUseCase = vacuumStoresUseCase, + databaseSizesUseCase = databaseSizesUseCase, + fileSizeFormatter = FakeFileSizeFormatter(), ) } } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt index f10fdc93c4..3854e3f4a1 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -112,7 +113,7 @@ class DeveloperSettingsViewTest { eventsRecorder.assertSingle(DeveloperSettingsEvents.SetTracingLogLevel(LogLevelItem.DEBUG)) } - @Config(qualifiers = "h2000dp") + @Config(qualifiers = "h2200dp") @Test fun `clicking on clear cache emits the expected event`() { val eventsRecorder = EventsRecorder() diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/labs/LabsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/labs/LabsPresenterTest.kt index 5117a9fe94..acf65ef2e6 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/labs/LabsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/labs/LabsPresenterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,13 +16,15 @@ import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeature import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.services.toolbox.test.strings.FakeStringProvider +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.test import kotlinx.coroutines.test.runTest import org.junit.Test class LabsPresenterTest { @Test - fun `present - ensures only unfinished features in labs are displayed`() = runTest { + fun `present - ensures features are displayed in the correct order`() = runTest { val availableFeatures = listOf( FakeFeature( key = "feature_1", @@ -30,24 +33,23 @@ class LabsPresenterTest { ), FakeFeature( key = "feature_2", - title = "Feature 2", - isInLabs = false, - ), - FakeFeature( - key = "feature_3", title = "Feature 3", isInLabs = true, - isFinished = true, ) ) + val getAvailableFeaturesResult = lambdaRecorder> { _, _ -> + availableFeatures + } createLabsPresenter( - availableFeatures = availableFeatures, + getAvailableFeaturesResult = getAvailableFeaturesResult, ).test { + skipItems(1) val receivedFeatures = awaitItem().features - assertThat(receivedFeatures).hasSize(1) - assertThat(receivedFeatures.first().key).isEqualTo(availableFeatures.first().key) - - cancelAndIgnoreRemainingEvents() + assertThat(receivedFeatures).hasSize(2) + assertThat(receivedFeatures[0].key).isEqualTo(availableFeatures[0].key) + assertThat(receivedFeatures[1].key).isEqualTo(availableFeatures[1].key) + getAvailableFeaturesResult.assertions().isCalledOnce() + .with(value(false), value(true)) } } @@ -61,19 +63,15 @@ class LabsPresenterTest { ), ) createLabsPresenter( - availableFeatures = availableFeatures, + getAvailableFeaturesResult = { _, _ -> availableFeatures }, ).test { + skipItems(1) val initialItem = awaitItem() val feature = initialItem.features.first() assertThat(feature.isEnabled).isFalse() - - // Wait until the data finished loading - skipItems(1) - // Toggle the feature, should be true now initialItem.eventSink(LabsEvents.ToggleFeature(feature)) assertThat(awaitItem().features.first().isEnabled).isTrue() - // Toggle the feature, should be false now initialItem.eventSink(LabsEvents.ToggleFeature(feature)) assertThat(awaitItem().features.first().isEnabled).isFalse() @@ -92,21 +90,17 @@ class LabsPresenterTest { val clearCacheUseCase = FakeClearCacheUseCase() createLabsPresenter( - availableFeatures = availableFeatures, + getAvailableFeaturesResult = { _, _ -> availableFeatures }, clearCacheUseCase = clearCacheUseCase, ).test { + skipItems(1) val initialItem = awaitItem() val feature = initialItem.features.first() assertThat(feature.isEnabled).isFalse() assertThat(initialItem.isApplyingChanges).isFalse() - - // Wait until the data finished loading - skipItems(1) - // Toggle the feature initialItem.eventSink(LabsEvents.ToggleFeature(feature)) assertThat(awaitItem().features.first().isEnabled).isTrue() - // The clear cache use case should have been called assertThat(awaitItem().isApplyingChanges).isTrue() assertThat(clearCacheUseCase.executeHasBeenCalled).isTrue() @@ -114,12 +108,12 @@ class LabsPresenterTest { } private fun createLabsPresenter( - availableFeatures: List = emptyList(), + getAvailableFeaturesResult: (Boolean, Boolean) -> List = { _, _ -> emptyList() }, clearCacheUseCase: ClearCacheUseCase = FakeClearCacheUseCase(), ): LabsPresenter { return LabsPresenter( stringProvider = FakeStringProvider(), - featureFlagService = FakeFeatureFlagService(providedAvailableFeatures = availableFeatures), + featureFlagService = FakeFeatureFlagService(getAvailableFeaturesResult = getAvailableFeaturesResult), clearCacheUseCase = clearCacheUseCase, ) } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTest.kt index b452b9c744..153c265560 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTest.kt @@ -1,39 +1,41 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.preferences.impl.notifications -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.preferences.impl.notifications.edit.EditDefaultNotificationSettingPresenter import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingStateEvents import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.test.AN_EXCEPTION +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_ROOM_ID_2 import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.room.aRoomSummary import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.tests.testutils.awaitLastSequentialItem import io.element.android.tests.testutils.consumeItemsUntilPredicate +import io.element.android.tests.testutils.test import kotlinx.coroutines.test.runTest import org.junit.Test class EditDefaultNotificationSettingsPresenterTest { @Test fun `present - ensures initial state is correct`() = runTest { - val notificationSettingsService = FakeNotificationSettingsService() + val notificationSettingsService = FakeNotificationSettingsService( + getRoomsWithUserDefinedRulesResult = { Result.success(emptyList()) }, + ) val presenter = createEditDefaultNotificationSettingPresenter(notificationSettingsService) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.mode).isNull() assertThat(initialState.isOneToOne).isFalse() + assertThat(initialState.roomsWithUserDefinedMode).isEmpty() val loadedState = consumeItemsUntilPredicate { it.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY @@ -48,13 +50,12 @@ class EditDefaultNotificationSettingsPresenterTest { fun `present - ensure list of rooms with user defined mode`() = runTest { val notificationSettingsService = FakeNotificationSettingsService( initialRoomMode = RoomNotificationMode.ALL_MESSAGES, - initialRoomModeIsDefault = false + initialRoomModeIsDefault = false, + getRoomsWithUserDefinedRulesResult = { Result.success(listOf(A_ROOM_ID)) }, ) val roomListService = FakeRoomListService() val presenter = createEditDefaultNotificationSettingPresenter(notificationSettingsService, roomListService) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { roomListService.postAllRooms(listOf(aRoomSummary(userDefinedNotificationMode = RoomNotificationMode.ALL_MESSAGES))) val loadedState = consumeItemsUntilPredicate { state -> state.roomsWithUserDefinedMode.any { it.notificationMode == RoomNotificationMode.ALL_MESSAGES } @@ -63,12 +64,78 @@ class EditDefaultNotificationSettingsPresenterTest { } } + @Test + fun `present - ensure list of rooms is sorted`() = runTest { + val notificationSettingsService = FakeNotificationSettingsService( + initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, + initialRoomModeIsDefault = false, + getRoomsWithUserDefinedRulesResult = { Result.success(listOf(A_ROOM_ID, A_ROOM_ID_2)) }, + ) + val roomListService = FakeRoomListService() + val presenter = createEditDefaultNotificationSettingPresenter(notificationSettingsService, roomListService) + presenter.test { + roomListService.postAllRooms( + listOf( + aRoomSummary( + roomId = A_ROOM_ID, + name = "Z", + userDefinedNotificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, + ), + aRoomSummary( + roomId = A_ROOM_ID_2, + name = "A", + userDefinedNotificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, + ), + ), + ) + val loadedState = consumeItemsUntilPredicate { state -> + state.roomsWithUserDefinedMode.any { it.notificationMode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY } + }.last() + assertThat(loadedState.roomsWithUserDefinedMode[0].name).isEqualTo("A") + assertThat(loadedState.roomsWithUserDefinedMode[1].name).isEqualTo("Z") + } + } + + @Test + fun `present - ensure list of rooms is sorted, with name null`() = runTest { + val notificationSettingsService = FakeNotificationSettingsService( + initialRoomMode = RoomNotificationMode.MUTE, + initialRoomModeIsDefault = false, + getRoomsWithUserDefinedRulesResult = { Result.success(listOf(A_ROOM_ID, A_ROOM_ID_2)) }, + ) + val roomListService = FakeRoomListService() + val presenter = createEditDefaultNotificationSettingPresenter(notificationSettingsService, roomListService) + presenter.test { + roomListService.postAllRooms( + listOf( + aRoomSummary( + roomId = A_ROOM_ID, + name = "Z", + userDefinedNotificationMode = RoomNotificationMode.MUTE, + ), + aRoomSummary( + roomId = A_ROOM_ID_2, + name = null, + userDefinedNotificationMode = RoomNotificationMode.MUTE, + ), + ), + ) + val loadedState = consumeItemsUntilPredicate { state -> + state.roomsWithUserDefinedMode.any { it.notificationMode == RoomNotificationMode.MUTE } + }.last() + assertThat(loadedState.roomsWithUserDefinedMode[0].name).isNull() + assertThat(loadedState.roomsWithUserDefinedMode[1].name).isEqualTo("Z") + } + } + @Test fun `present - edit default notification setting`() = runTest { - val presenter = createEditDefaultNotificationSettingPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val presenter = createEditDefaultNotificationSettingPresenter( + notificationSettingsService = FakeNotificationSettingsService( + getRoomsWithUserDefinedRulesResult = { Result.success(emptyList()) }, + ), + ) + presenter.test { awaitItem().eventSink(EditDefaultNotificationSettingStateEvents.SetNotificationMode(RoomNotificationMode.ALL_MESSAGES)) val loadedState = consumeItemsUntilPredicate { it.mode == RoomNotificationMode.ALL_MESSAGES @@ -79,12 +146,12 @@ class EditDefaultNotificationSettingsPresenterTest { @Test fun `present - edit default notification setting failed`() = runTest { - val notificationSettingsService = FakeNotificationSettingsService() + val notificationSettingsService = FakeNotificationSettingsService( + getRoomsWithUserDefinedRulesResult = { Result.success(emptyList()) }, + ) val presenter = createEditDefaultNotificationSettingPresenter(notificationSettingsService) notificationSettingsService.givenSetDefaultNotificationModeError(AN_EXCEPTION) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { awaitItem().eventSink(EditDefaultNotificationSettingStateEvents.SetNotificationMode(RoomNotificationMode.ALL_MESSAGES)) val errorState = consumeItemsUntilPredicate { it.changeNotificationSettingAction.isFailure() @@ -100,13 +167,13 @@ class EditDefaultNotificationSettingsPresenterTest { @Test fun `present - display mentions only warning if homeserver does not support it`() = runTest { - val notificationSettingsService = FakeNotificationSettingsService().apply { + val notificationSettingsService = FakeNotificationSettingsService( + getRoomsWithUserDefinedRulesResult = { Result.success(emptyList()) }, + ).apply { givenCanHomeServerPushEncryptedEventsToDeviceResult(Result.success(false)) } val presenter = createEditDefaultNotificationSettingPresenter(notificationSettingsService) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { assertThat(awaitLastSequentialItem().displayMentionsOnlyDisclaimer).isTrue() } } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/FakeSystemNotificationsEnabledProvider.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/FakeSystemNotificationsEnabledProvider.kt index f9804d1399..57357b5b9e 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/FakeSystemNotificationsEnabledProvider.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/FakeSystemNotificationsEnabledProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTest.kt index 8517d9d285..9b36c477a4 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTest.kt @@ -1,15 +1,13 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.preferences.impl.notifications -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState @@ -27,6 +25,9 @@ import io.element.android.libraries.pushproviders.test.FakePushProvider import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory import io.element.android.tests.testutils.awaitLastSequentialItem import io.element.android.tests.testutils.consumeItemsUntilPredicate +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.test +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test import kotlin.time.Duration.Companion.milliseconds @@ -35,9 +36,7 @@ class NotificationSettingsPresenterTest { @Test fun `present - ensures initial state is correct`() = runTest { val presenter = createNotificationSettingsPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.appSettings.appNotificationsEnabled).isFalse() assertThat(initialState.appSettings.systemNotificationsEnabled).isTrue() @@ -61,9 +60,7 @@ class NotificationSettingsPresenterTest { fun `present - default group notification mode changed`() = runTest { val notificationSettingsService = FakeNotificationSettingsService() val presenter = createNotificationSettingsPresenter(notificationSettingsService) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = true, isOneToOne = false, mode = RoomNotificationMode.ALL_MESSAGES) notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = false, isOneToOne = false, mode = RoomNotificationMode.ALL_MESSAGES) val updatedState = consumeItemsUntilPredicate { @@ -79,9 +76,7 @@ class NotificationSettingsPresenterTest { fun `present - notification settings mismatched`() = runTest { val notificationSettingsService = FakeNotificationSettingsService() val presenter = createNotificationSettingsPresenter(notificationSettingsService) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { notificationSettingsService.setDefaultRoomNotificationMode( isEncrypted = true, isOneToOne = false, @@ -109,9 +104,7 @@ class NotificationSettingsPresenterTest { initialOneToOneDefaultMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY ) val presenter = createNotificationSettingsPresenter(notificationSettingsService) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() initialState.eventSink(NotificationSettingsEvents.FixConfigurationMismatch) val fixedState = consumeItemsUntilPredicate(timeout = 2000.milliseconds) { @@ -124,10 +117,19 @@ class NotificationSettingsPresenterTest { @Test fun `present - set notifications enabled`() = runTest { - val presenter = createNotificationSettingsPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val unregisterWithResult = lambdaRecorder> { Result.success(Unit) } + val ensurePusherIsRegisteredResult = lambdaRecorder> { Result.success(Unit) } + val presenter = createNotificationSettingsPresenter( + pushService = FakePushService( + currentPushProvider = { + FakePushProvider( + unregisterWithResult = unregisterWithResult, + ) + }, + ensurePusherIsRegisteredResult = ensurePusherIsRegisteredResult, + ) + ) + presenter.test { val loadedState = consumeItemsUntilPredicate { it.matrixSettings is NotificationSettingsState.MatrixSettings.Valid }.last() @@ -137,16 +139,21 @@ class NotificationSettingsPresenterTest { !it.appSettings.appNotificationsEnabled }.last() assertThat(updatedState.appSettings.appNotificationsEnabled).isFalse() - cancelAndIgnoreRemainingEvents() + unregisterWithResult.assertions().isCalledOnce() + // Enable notification again + loadedState.eventSink(NotificationSettingsEvents.SetNotificationsEnabled(true)) + val updatedState2 = consumeItemsUntilPredicate { + it.appSettings.appNotificationsEnabled + }.last() + assertThat(updatedState2.appSettings.appNotificationsEnabled).isTrue() + ensurePusherIsRegisteredResult.assertions().isCalledOnce() } } @Test fun `present - set call notifications enabled`() = runTest { val presenter = createNotificationSettingsPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val loadedState = consumeItemsUntilPredicate { (it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.callNotificationsEnabled == false }.last() @@ -165,9 +172,7 @@ class NotificationSettingsPresenterTest { @Test fun `present - set invite for me notifications enabled`() = runTest { val presenter = createNotificationSettingsPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val loadedState = consumeItemsUntilPredicate { (it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.inviteForMeNotificationsEnabled == false }.last() @@ -186,9 +191,7 @@ class NotificationSettingsPresenterTest { @Test fun `present - set atRoom notifications enabled`() = runTest { val presenter = createNotificationSettingsPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val loadedState = consumeItemsUntilPredicate { (it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.atRoomNotificationsEnabled == false }.last() @@ -209,9 +212,7 @@ class NotificationSettingsPresenterTest { val notificationSettingsService = FakeNotificationSettingsService() val presenter = createNotificationSettingsPresenter(notificationSettingsService) notificationSettingsService.givenSetAtRoomError(AN_EXCEPTION) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val loadedState = consumeItemsUntilPredicate { (it.matrixSettings as? NotificationSettingsState.MatrixSettings.Valid)?.atRoomNotificationsEnabled == false }.last() @@ -236,9 +237,7 @@ class NotificationSettingsPresenterTest { val presenter = createNotificationSettingsPresenter( pushService = createFakePushService(), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitLastSequentialItem() assertThat(initialState.currentPushDistributor).isEqualTo(AsyncData.Success(Distributor(value = "aDistributorValue0", name = "aDistributorName0"))) assertThat(initialState.availablePushDistributors).containsExactly( @@ -270,9 +269,7 @@ class NotificationSettingsPresenterTest { val presenter = createNotificationSettingsPresenter( pushService = createFakePushService(), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitLastSequentialItem() assertThat(initialState.currentPushDistributor).isEqualTo(AsyncData.Success(Distributor(value = "aDistributorValue0", name = "aDistributorName0"))) assertThat(initialState.availablePushDistributors).containsExactly( @@ -297,9 +294,7 @@ class NotificationSettingsPresenterTest { pushService = createFakePushService(), fullScreenIntentPermissionsStateLambda = fullScreenIntentPermissionsStateLambda, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitLastSequentialItem() assertThat(initialState.fullScreenIntentPermissionsState.permissionGranted).isFalse() @@ -323,9 +318,7 @@ class NotificationSettingsPresenterTest { }, ), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitLastSequentialItem() initialState.eventSink.invoke(NotificationSettingsEvents.ChangePushProvider) val withDialog = awaitItem() @@ -340,7 +333,7 @@ class NotificationSettingsPresenterTest { } private fun createFakePushService( - registerWithLambda: suspend (MatrixClient, PushProvider, Distributor) -> Result = { _, _, _ -> + registerWithLambda: (MatrixClient, PushProvider, Distributor) -> Result = { _, _, _ -> Result.success(Unit) } ): PushService { @@ -360,7 +353,7 @@ class NotificationSettingsPresenterTest { ) } - private fun createNotificationSettingsPresenter( + private fun TestScope.createNotificationSettingsPresenter( notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(), pushService: PushService = FakePushService(), fullScreenIntentPermissionsStateLambda: () -> FullScreenIntentPermissionsState = { aFullScreenIntentPermissionsState() }, @@ -373,6 +366,7 @@ class NotificationSettingsPresenterTest { pushService = pushService, systemNotificationsEnabledProvider = FakeSystemNotificationsEnabledProvider(), fullScreenIntentPermissionsPresenter = { fullScreenIntentPermissionsStateLambda() }, + sessionCoroutineScope = backgroundScope, ) } } diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt index 2aa485a192..ea140abbd7 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/FakeVersionFormatter.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/FakeVersionFormatter.kt index 3e8035fa2b..95691dac37 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/FakeVersionFormatter.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/FakeVersionFormatter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 f65589beb6..d10f860f0d 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -86,6 +87,7 @@ class PreferencesRootPresenterTest { assertThat(loadedState.accountManagementUrl).isNull() assertThat(loadedState.devicesManagementUrl).isNull() assertThat(loadedState.showAnalyticsSettings).isFalse() + assertThat(loadedState.showLinkNewDevice).isFalse() assertThat(loadedState.showDeveloperSettings).isTrue() assertThat(loadedState.canDeactivateAccount).isTrue() assertThat(loadedState.canReportBug).isTrue() @@ -189,14 +191,16 @@ class PreferencesRootPresenterTest { fun `present - labs can be shown if any feature flag is in labs and not finished`() = runTest { createPresenter( featureFlagService = FakeFeatureFlagService( - providedAvailableFeatures = listOf( - FakeFeature( - key = "feature_1", - title = "Feature 1", - isInLabs = true, - isFinished = false, + getAvailableFeaturesResult = { _, _ -> + listOf( + FakeFeature( + key = "feature_1", + title = "Feature 1", + isInLabs = true, + isFinished = false, + ) ) - ) + } ), matrixClient = FakeMatrixClient( canDeactivateAccountResult = { true }, @@ -212,20 +216,16 @@ class PreferencesRootPresenterTest { fun `present - labs can't be shown if all feature flags in labs are finished`() = runTest { createPresenter( featureFlagService = FakeFeatureFlagService( - providedAvailableFeatures = listOf( - FakeFeature( - key = "feature_1", - title = "Feature 1", - isInLabs = true, - isFinished = true, - ) - ) + getAvailableFeaturesResult = { _, _ -> + emptyList() + } ), matrixClient = FakeMatrixClient( canDeactivateAccountResult = { true }, accountManagementUrlResult = { Result.success(null) }, ), ).test { + skipItems(1) assertThat(awaitItem().showLabsItem).isFalse() cancelAndIgnoreRemainingEvents() } @@ -259,6 +259,22 @@ class PreferencesRootPresenterTest { } } + @Test + fun `present - link new device`() = runTest { + createPresenter( + matrixClient = FakeMatrixClient( + sessionId = A_SESSION_ID, + canDeactivateAccountResult = { true }, + ), + featureFlagService = FakeFeatureFlagService( + initialState = mapOf(FeatureFlags.QrCodeLogin.key to true) + ), + ).test { + val state = awaitFirstItem() + assertThat(state.showLinkNewDevice).isTrue() + } + } + private suspend fun ReceiveTurbine.awaitFirstItem(): T { skipItems(1) return awaitItem() diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/VersionFormatterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/VersionFormatterTest.kt index ce772fe3f2..c00dc7689b 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/VersionFormatterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/VersionFormatterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/DefaultClearCacheUseCaseTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/DefaultClearCacheUseCaseTest.kt index 1e16509ad2..6845ecb3a4 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/DefaultClearCacheUseCaseTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/DefaultClearCacheUseCaseTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,7 +19,7 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.push.test.FakePushService -import io.element.android.services.appnavstate.api.ActiveRoomsHolder +import io.element.android.services.appnavstate.impl.DefaultActiveRoomsHolder import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.testCoroutineDispatchers @@ -33,7 +34,7 @@ import org.robolectric.RobolectricTestRunner class DefaultClearCacheUseCaseTest { @Test fun `execute clear cache should do all the expected tasks`() = runTest { - val activeRoomsHolder = ActiveRoomsHolder().apply { addRoom(FakeJoinedRoom()) } + val activeRoomsHolder = DefaultActiveRoomsHolder().apply { addRoom(FakeJoinedRoom()) } val clearCacheLambda = lambdaRecorder { } val matrixClient = FakeMatrixClient( sessionId = A_SESSION_ID, diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/FakeClearCacheUseCase.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/FakeClearCacheUseCase.kt index e10e7f1a6a..94dec8488b 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/FakeClearCacheUseCase.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/FakeClearCacheUseCase.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/FakeComputeCacheSizeUseCase.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/FakeComputeCacheSizeUseCase.kt index 1b12d2804e..455c181cc0 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/FakeComputeCacheSizeUseCase.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/tasks/FakeComputeCacheSizeUseCase.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt index 947be54785..0602709ec8 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt @@ -1,16 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.preferences.impl.user.editprofile import android.net.Uri -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.androidutils.file.TemporaryUriDeleter import io.element.android.libraries.architecture.AsyncAction @@ -34,6 +32,7 @@ import io.element.android.tests.testutils.consumeItemsUntilTimeout import io.element.android.tests.testutils.fake.FakeTemporaryUriDeleter import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value +import io.element.android.tests.testutils.test import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic @@ -57,8 +56,6 @@ class EditUserProfilePresenterTest { private val userAvatarUri: Uri = mockk() private val anotherAvatarUri: Uri = mockk() - private val fakeFileContents = ByteArray(2) - @Before fun setup() { fakePickerProvider = FakePickerProvider() @@ -66,7 +63,9 @@ class EditUserProfilePresenterTest { mockkStatic(Uri::class) every { Uri.parse(AN_AVATAR_URL) } returns userAvatarUri + every { userAvatarUri.toString() } returns AN_AVATAR_URL every { Uri.parse(ANOTHER_AVATAR_URL) } returns anotherAvatarUri + every { anotherAvatarUri.toString() } returns ANOTHER_AVATAR_URL } @After @@ -76,6 +75,7 @@ class EditUserProfilePresenterTest { private fun createEditUserProfilePresenter( matrixClient: MatrixClient = FakeMatrixClient(), + navigator: EditUserProfileNavigator = FakeEditUserProfileNavigator(), matrixUser: MatrixUser = aMatrixUser(), permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(), temporaryUriDeleter: TemporaryUriDeleter = FakeTemporaryUriDeleter(), @@ -83,6 +83,7 @@ class EditUserProfilePresenterTest { ): EditUserProfilePresenter { return EditUserProfilePresenter( matrixClient = matrixClient, + navigator = navigator, matrixUser = matrixUser, mediaPickerProvider = fakePickerProvider, mediaPreProcessor = fakeMediaPreProcessor, @@ -96,13 +97,11 @@ class EditUserProfilePresenterTest { fun `present - initial state is created from user info`() = runTest { val user = aMatrixUser(avatarUrl = AN_AVATAR_URL) val presenter = createEditUserProfilePresenter(matrixUser = user) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.userId).isEqualTo(user.userId) assertThat(initialState.displayName).isEqualTo(user.displayName) - assertThat(initialState.userAvatarUrl).isEqualTo(userAvatarUri) + assertThat(initialState.userAvatarUrl).isEqualTo(AN_AVATAR_URL) assertThat(initialState.avatarActions).containsExactly( AvatarAction.ChoosePhoto, AvatarAction.TakePhoto, @@ -113,6 +112,53 @@ class EditUserProfilePresenterTest { } } + @Test + fun `present - exit invokes the expected callback`() = runTest { + val user = aMatrixUser(avatarUrl = AN_AVATAR_URL) + val closeLambda = lambdaRecorder {} + val presenter = createEditUserProfilePresenter( + matrixUser = user, + navigator = FakeEditUserProfileNavigator(closeLambda), + ) + presenter.test { + val initialState = awaitItem() + initialState.eventSink(EditUserProfileEvent.Exit) + closeLambda.assertions().isCalledOnce() + } + } + + @Test + fun `present - exit without unsaved changes`() = runTest { + val user = aMatrixUser(avatarUrl = AN_AVATAR_URL) + val closeLambda = lambdaRecorder {} + val presenter = createEditUserProfilePresenter( + matrixUser = user, + navigator = FakeEditUserProfileNavigator(closeLambda), + ) + presenter.test { + val initialState = awaitItem() + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("New name")) + val withUpdatedName = awaitItem() + withUpdatedName.eventSink(EditUserProfileEvent.Exit) + val withConfirmation = awaitItem() + assertThat(withConfirmation.saveAction).isEqualTo(AsyncAction.ConfirmingCancellation) + // Cancel + withConfirmation.eventSink(EditUserProfileEvent.CloseDialog) + val afterCancel = awaitItem() + assertThat(afterCancel.saveAction).isEqualTo(AsyncAction.Uninitialized) + // Try again and confirm + afterCancel.eventSink(EditUserProfileEvent.Exit) + val withConfirmation2 = awaitItem() + assertThat(withConfirmation2.saveAction).isEqualTo(AsyncAction.ConfirmingCancellation) + closeLambda.assertions().isNeverCalled() + withConfirmation2.eventSink(EditUserProfileEvent.Exit) + // Dialog is closed + val finalState = awaitItem() + assertThat(finalState.saveAction).isEqualTo(AsyncAction.Uninitialized) + closeLambda.assertions().isCalledOnce() + } + } + @Test fun `present - updates state in response to changes`() = runTest { val user = aMatrixUser(id = A_USER_ID.value, displayName = "Name", avatarUrl = AN_AVATAR_URL) @@ -122,23 +168,21 @@ class EditUserProfilePresenterTest { deleteLambda = { assertThat(it).isEqualTo(userAvatarUri) } ), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.displayName).isEqualTo("Name") - assertThat(initialState.userAvatarUrl).isEqualTo(userAvatarUri) - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("Name II")) + assertThat(initialState.userAvatarUrl).isEqualTo(AN_AVATAR_URL) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("Name II")) awaitItem().apply { assertThat(displayName).isEqualTo("Name II") - assertThat(userAvatarUrl).isEqualTo(userAvatarUri) + assertThat(userAvatarUrl).isEqualTo(AN_AVATAR_URL) } - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("Name III")) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("Name III")) awaitItem().apply { assertThat(displayName).isEqualTo("Name III") - assertThat(userAvatarUrl).isEqualTo(userAvatarUri) + assertThat(userAvatarUrl).isEqualTo(AN_AVATAR_URL) } - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.Remove)) + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.Remove)) awaitItem().apply { assertThat(displayName).isEqualTo("Name III") assertThat(userAvatarUrl).isNull() @@ -156,14 +200,12 @@ class EditUserProfilePresenterTest { deleteLambda = { assertThat(it).isEqualTo(userAvatarUri) } ), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() - assertThat(initialState.userAvatarUrl).isEqualTo(userAvatarUri) - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) + assertThat(initialState.userAvatarUrl).isEqualTo(AN_AVATAR_URL) + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) awaitItem().apply { - assertThat(userAvatarUrl).isEqualTo(anotherAvatarUri) + assertThat(userAvatarUrl).isEqualTo(ANOTHER_AVATAR_URL) } } } @@ -181,25 +223,23 @@ class EditUserProfilePresenterTest { deleteLambda = deleteCallback, ), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() - assertThat(initialState.userAvatarUrl).isEqualTo(userAvatarUri) + assertThat(initialState.userAvatarUrl).isEqualTo(AN_AVATAR_URL) assertThat(initialState.cameraPermissionState.permissionGranted).isFalse() - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.TakePhoto)) + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.TakePhoto)) val stateWithAskingPermission = awaitItem() assertThat(stateWithAskingPermission.cameraPermissionState.showDialog).isTrue() fakePermissionsPresenter.setPermissionGranted() val stateWithPermission = awaitItem() assertThat(stateWithPermission.cameraPermissionState.permissionGranted).isTrue() val stateWithNewAvatar = awaitItem() - assertThat(stateWithNewAvatar.userAvatarUrl).isEqualTo(anotherAvatarUri) + assertThat(stateWithNewAvatar.userAvatarUrl).isEqualTo(ANOTHER_AVATAR_URL) // Do it again, no permission is requested fakePickerProvider.givenResult(userAvatarUri) - stateWithNewAvatar.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.TakePhoto)) + stateWithNewAvatar.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.TakePhoto)) val stateWithNewAvatar2 = awaitItem() - assertThat(stateWithNewAvatar2.userAvatarUrl).isEqualTo(userAvatarUri) + assertThat(stateWithNewAvatar2.userAvatarUrl).isEqualTo(AN_AVATAR_URL) deleteCallback.assertions().isCalledExactly(2).withSequence( listOf(value(userAvatarUri)), listOf(value(anotherAvatarUri)), @@ -218,28 +258,26 @@ class EditUserProfilePresenterTest { deleteLambda = deleteCallback ), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.saveButtonEnabled).isFalse() // Once a change is made, the save button is enabled - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("Name II")) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("Name II")) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // If it's reverted then the save disables again - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("Name")) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("Name")) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } // Make a change... - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.Remove)) + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.Remove)) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // Revert it... - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } @@ -261,28 +299,26 @@ class EditUserProfilePresenterTest { deleteLambda = deleteCallback ), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() assertThat(initialState.saveButtonEnabled).isFalse() // Once a change is made, the save button is enabled - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("Name II")) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("Name II")) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // If it's reverted then the save disables again - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("Name")) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("Name")) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } // Make a change... - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // Revert it... - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.Remove)) + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.Remove)) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } @@ -304,13 +340,11 @@ class EditUserProfilePresenterTest { deleteLambda = { assertThat(it).isEqualTo(userAvatarUri) } ), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("New name")) - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.Remove)) - initialState.eventSink(EditUserProfileEvents.Save) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("New name")) + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.Remove)) + initialState.eventSink(EditUserProfileEvent.Save) consumeItemsUntilPredicate { matrixClient.setDisplayNameCalled && matrixClient.removeAvatarCalled && !matrixClient.uploadAvatarCalled } assertThat(matrixClient.setDisplayNameCalled).isTrue() assertThat(matrixClient.removeAvatarCalled).isTrue() @@ -327,12 +361,10 @@ class EditUserProfilePresenterTest { matrixClient = matrixClient, matrixUser = user ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName(" Name ")) - initialState.eventSink(EditUserProfileEvents.Save) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName(" Name ")) + initialState.eventSink(EditUserProfileEvent.Save) consumeItemsUntilTimeout() assertThat(matrixClient.setDisplayNameCalled).isFalse() assertThat(matrixClient.uploadAvatarCalled).isFalse() @@ -348,12 +380,10 @@ class EditUserProfilePresenterTest { matrixClient = matrixClient, matrixUser = user ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("")) - initialState.eventSink(EditUserProfileEvents.Save) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("")) + initialState.eventSink(EditUserProfileEvent.Save) assertThat(matrixClient.setDisplayNameCalled).isFalse() assertThat(matrixClient.uploadAvatarCalled).isFalse() assertThat(matrixClient.removeAvatarCalled).isFalse() @@ -365,7 +395,7 @@ class EditUserProfilePresenterTest { fun `present - save processes and sets avatar when processor returns successfully`() = runTest { val matrixClient = FakeMatrixClient() val user = aMatrixUser(id = A_USER_ID.value, displayName = "Name", avatarUrl = AN_AVATAR_URL) - givenPickerReturnsFile() + val tmpFile = givenPickerReturnsFile() val presenter = createEditUserProfilePresenter( matrixClient = matrixClient, matrixUser = user, @@ -373,14 +403,16 @@ class EditUserProfilePresenterTest { deleteLambda = { assertThat(it).isEqualTo(userAvatarUri) } ), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) - initialState.eventSink(EditUserProfileEvents.Save) - consumeItemsUntilPredicate { matrixClient.uploadAvatarCalled } - assertThat(matrixClient.uploadAvatarCalled).isTrue() + try { + presenter.test { + val initialState = awaitItem() + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) + initialState.eventSink(EditUserProfileEvent.Save) + consumeItemsUntilPredicate { matrixClient.uploadAvatarCalled } + assertThat(matrixClient.uploadAvatarCalled).isTrue() + } + } finally { + tmpFile.delete() } } @@ -397,12 +429,10 @@ class EditUserProfilePresenterTest { ) fakePickerProvider.givenResult(anotherAvatarUri) fakeMediaPreProcessor.givenResult(Result.failure(RuntimeException("Oh no"))) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) - initialState.eventSink(EditUserProfileEvents.Save) + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) + initialState.eventSink(EditUserProfileEvent.Save) skipItems(2) assertThat(matrixClient.uploadAvatarCalled).isFalse() assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) @@ -415,7 +445,7 @@ class EditUserProfilePresenterTest { val matrixClient = FakeMatrixClient().apply { givenSetDisplayNameResult(Result.failure(RuntimeException("!"))) } - saveAndAssertFailure(user, matrixClient, EditUserProfileEvents.UpdateDisplayName("New name")) + saveAndAssertFailure(user, matrixClient, EditUserProfileEvent.UpdateDisplayName("New name")) } @Test @@ -424,41 +454,47 @@ class EditUserProfilePresenterTest { val matrixClient = FakeMatrixClient().apply { givenRemoveAvatarResult(Result.failure(RuntimeException("!"))) } - saveAndAssertFailure(user, matrixClient, EditUserProfileEvents.HandleAvatarAction(AvatarAction.Remove)) + saveAndAssertFailure(user, matrixClient, EditUserProfileEvent.HandleAvatarAction(AvatarAction.Remove)) } @Test fun `present - sets save action to failure if setting avatar fails`() = runTest { - givenPickerReturnsFile() + val tmpFile = givenPickerReturnsFile() val user = aMatrixUser(id = A_USER_ID.value, displayName = "Name", avatarUrl = AN_AVATAR_URL) val matrixClient = FakeMatrixClient().apply { givenUploadAvatarResult(Result.failure(RuntimeException("!"))) } - saveAndAssertFailure(user, matrixClient, EditUserProfileEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) + try { + saveAndAssertFailure(user, matrixClient, EditUserProfileEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) + } finally { + tmpFile.delete() + } } @Test - fun `present - CancelSaveChanges resets save action state`() = runTest { - givenPickerReturnsFile() + fun `present - CloseDialog resets save action state`() = runTest { + val tmpFile = givenPickerReturnsFile() val user = aMatrixUser(id = A_USER_ID.value, displayName = "Name", avatarUrl = AN_AVATAR_URL) val matrixClient = FakeMatrixClient().apply { givenSetDisplayNameResult(Result.failure(RuntimeException("!"))) } val presenter = createEditUserProfilePresenter(matrixUser = user, matrixClient = matrixClient) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("foo")) - initialState.eventSink(EditUserProfileEvents.Save) - skipItems(2) - assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) - initialState.eventSink(EditUserProfileEvents.CancelSaveChanges) - assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Uninitialized::class.java) + try { + presenter.test { + val initialState = awaitItem() + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("foo")) + initialState.eventSink(EditUserProfileEvent.Save) + skipItems(2) + assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) + initialState.eventSink(EditUserProfileEvent.CloseDialog) + assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Uninitialized::class.java) + } + } finally { + tmpFile.delete() } } - private suspend fun saveAndAssertFailure(matrixUser: MatrixUser, matrixClient: MatrixClient, event: EditUserProfileEvents) { + private suspend fun saveAndAssertFailure(matrixUser: MatrixUser, matrixClient: MatrixClient, event: EditUserProfileEvent) { val presenter = createEditUserProfilePresenter( matrixUser = matrixUser, matrixClient = matrixClient, @@ -466,32 +502,28 @@ class EditUserProfilePresenterTest { deleteLambda = { assertThat(it).isEqualTo(userAvatarUri) } ), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() initialState.eventSink(event) - initialState.eventSink(EditUserProfileEvents.Save) + initialState.eventSink(EditUserProfileEvent.Save) skipItems(1) assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Loading::class.java) assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) } } - private fun givenPickerReturnsFile() { - mockkStatic(File::readBytes) - val processedFile: File = mockk { - every { readBytes() } returns fakeFileContents - } + private fun givenPickerReturnsFile(): File { + val file = File.createTempFile("test", "jpg") fakePickerProvider.givenResult(anotherAvatarUri) fakeMediaPreProcessor.givenResult( Result.success( MediaUploadInfo.AnyFile( - file = processedFile, + file = file, fileInfo = mockk(), ) ) ) + return file } companion object { diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileViewTest.kt new file mode 100644 index 0000000000..728e05ee7e --- /dev/null +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileViewTest.kt @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.preferences.impl.user.editprofile + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.ui.media.AvatarAction +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBack +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class EditUserProfileViewTest { + @get:Rule val rule = createAndroidComposeRule() + + @Test + fun `clicking on back emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setEditUserProfileView( + aEditUserProfileState( + eventSink = eventsRecorder, + ), + ) + rule.pressBack() + eventsRecorder.assertSingle(EditUserProfileEvent.Exit) + } + + @Test + fun `clicking on save from the exit confirmation dialog emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setEditUserProfileView( + aEditUserProfileState( + saveAction = AsyncAction.ConfirmingCancellation, + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.action_save, inDialog = true) + eventsRecorder.assertSingle(EditUserProfileEvent.Save) + } + + @Test + fun `clicking on discard exit emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setEditUserProfileView( + aEditUserProfileState( + saveAction = AsyncAction.ConfirmingCancellation, + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.action_discard) + eventsRecorder.assertSingle(EditUserProfileEvent.Exit) + } + + @Test + fun `clicking on save emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setEditUserProfileView( + aEditUserProfileState( + saveButtonEnabled = true, + saveAction = AsyncAction.Uninitialized, + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.action_save) + eventsRecorder.assertSingle(EditUserProfileEvent.Save) + } + + @Test + fun `clicking on avatar opens the bottom sheet dialog`() { + val eventsRecorder = EventsRecorder() + val actions = listOf( + AvatarAction.TakePhoto, + AvatarAction.ChoosePhoto, + AvatarAction.Remove, + ) + rule.setEditUserProfileView( + aEditUserProfileState( + saveAction = AsyncAction.Uninitialized, + avatarActions = actions, + eventSink = eventsRecorder, + ), + ) + val contentDescription = rule.activity.getString(CommonStrings.a11y_avatar) + rule.onNodeWithContentDescription(contentDescription).performClick() + // Assert that the actions are displayed + actions.forEach { action -> + val text = rule.activity.getString(action.titleResId) + rule.onNodeWithText(text).assertExists() + } + } + + @Test + fun `success invokes the expected callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { callback -> + rule.setEditUserProfileView( + aEditUserProfileState( + saveAction = AsyncAction.Success(Unit), + eventSink = eventsRecorder, + ), + onEditProfileSuccess = callback, + ) + } + } +} + +private fun AndroidComposeTestRule.setEditUserProfileView( + state: EditUserProfileState, + onEditProfileSuccess: () -> Unit = EnsureNeverCalled(), +) { + setContent { + EditUserProfileView( + state = state, + onEditProfileSuccess = onEditProfileSuccess, + ) + } +} diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/FakeEditUserProfileNavigator.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/FakeEditUserProfileNavigator.kt new file mode 100644 index 0000000000..7b34e904a1 --- /dev/null +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/FakeEditUserProfileNavigator.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.preferences.impl.user.editprofile + +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeEditUserProfileNavigator( + val closeLambda: () -> Unit = { lambdaError() } +) : EditUserProfileNavigator { + override fun close() = closeLambda() +} diff --git a/features/rageshake/api/build.gradle.kts b/features/rageshake/api/build.gradle.kts index 7fe1620613..8a37497094 100644 --- a/features/rageshake/api/build.gradle.kts +++ b/features/rageshake/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/RageshakeFeatureAvailability.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/RageshakeFeatureAvailability.kt index a22040355e..bc72e7021b 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/RageshakeFeatureAvailability.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/RageshakeFeatureAvailability.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/bugreport/BugReportEntryPoint.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/bugreport/BugReportEntryPoint.kt index 0eb84b529b..6030c8552c 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/bugreport/BugReportEntryPoint.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/bugreport/BugReportEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,12 +14,11 @@ import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint interface BugReportEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - - interface NodeBuilder { - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: Callback, + ): Node interface Callback : Plugin { fun onDone() diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionEvents.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionEvents.kt index f3364d2a33..89c49338d1 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionEvents.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionPresenter.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionPresenter.kt index 913e1b65f4..085d4c8e41 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionPresenter.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionState.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionState.kt index 66c206adcd..dd08cbf86c 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionState.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionState.kt @@ -1,15 +1,13 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.rageshake.api.crash -import androidx.compose.runtime.Immutable - -@Immutable data class CrashDetectionState( val appName: String, val crashDetected: Boolean, diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionStateProvider.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionStateProvider.kt index 2674552093..b1516731fb 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionStateProvider.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionView.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionView.kt index 163f0b50e6..57d28527ca 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionView.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/crash/CrashDetectionView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionEvents.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionEvents.kt index 75b377588f..921ae861f2 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionEvents.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionPresenter.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionPresenter.kt index 1be1ae67c4..d371e475fa 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionPresenter.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 8ace909da3..a240fed687 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 @@ -1,16 +1,15 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.rageshake.api.detection -import androidx.compose.runtime.Immutable import io.element.android.features.rageshake.api.preferences.RageshakePreferencesState -@Immutable data class RageshakeDetectionState( val takeScreenshot: Boolean, val showDialog: Boolean, diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionStateProvider.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionStateProvider.kt index 09134c7a75..f79024ef25 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionStateProvider.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionView.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionView.kt index c0fc0ce099..dff6d9b716 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionView.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/detection/RageshakeDetectionView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/logs/LogFilesRemover.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/logs/LogFilesRemover.kt index 9d1f9157d9..f8eee31675 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/logs/LogFilesRemover.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/logs/LogFilesRemover.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/logs/WriteToFilesConfigurationFactory.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/logs/WriteToFilesConfigurationFactory.kt index 52298b5414..dafefb0e2e 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/logs/WriteToFilesConfigurationFactory.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/logs/WriteToFilesConfigurationFactory.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesEvents.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesEvents.kt index fa34c10740..49458c2e34 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesEvents.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesPresenter.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesPresenter.kt index b508b7f0fe..d4c1e348c0 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesPresenter.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesState.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesState.kt index 40a9d0282b..faba80a3c5 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesState.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesStateProvider.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesStateProvider.kt index a98c75a02a..d1a863745f 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesStateProvider.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesView.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesView.kt index 86f1c05247..e4f32982f9 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesView.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/preferences/RageshakePreferencesView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporter.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporter.kt index bb625370cf..79b05ea32e 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporter.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,6 +19,7 @@ interface BugReporter { * @param withScreenshot true to include the screenshot * @param problemDescription the bug description * @param canContact true if the user opt in to be contacted directly + * @param sendPushRules true to include the push rules * @param listener the listener */ suspend fun sendBugReport( @@ -26,6 +28,7 @@ interface BugReporter { withScreenshot: Boolean, problemDescription: String, canContact: Boolean = false, + sendPushRules: Boolean = false, listener: BugReporterListener ) @@ -34,14 +37,6 @@ interface BugReporter { */ fun logDirectory(): File - /** - * Set the subfolder name for the log directory. - * This will create a subfolder in the log directory with the given name. - * It will also configure the Rust SDK to use this subfolder for its logs. - * If the name is null, the log files will be stored in the base folder for the logs. - */ - fun setLogDirectorySubfolder(subfolderName: String?) - /** * Set the current tracing log level. */ @@ -50,5 +45,5 @@ interface BugReporter { /** * Save the logcat. */ - fun saveLogCat() + fun saveLogCat(): File? } diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporterListener.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporterListener.kt index 0e7d8d4028..b8c2d26d07 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporterListener.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/reporter/BugReporterListener.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/screenshot/Screenshot.kt b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/screenshot/Screenshot.kt index 7498f5e151..e820a14346 100644 --- a/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/screenshot/Screenshot.kt +++ b/features/rageshake/api/src/main/kotlin/io/element/android/features/rageshake/api/screenshot/Screenshot.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/api/src/main/res/values-hr/translations.xml b/features/rageshake/api/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..c3dfcb3203 --- /dev/null +++ b/features/rageshake/api/src/main/res/values-hr/translations.xml @@ -0,0 +1,7 @@ + + + "%1$s neočekivano je prestao s radom prilikom posljednjeg korištenja. Želite li s nama podijeliti izvješće o padu?" + "Čini se da ljutito treseš telefon. Želiš li otvoriti zaslon s izvješćem o pogrešci?" + "Snažno protresi" + "Prag detekcije" + diff --git a/features/rageshake/api/src/main/res/values-ru/translations.xml b/features/rageshake/api/src/main/res/values-ru/translations.xml index f8fd569add..72bf6fc363 100644 --- a/features/rageshake/api/src/main/res/values-ru/translations.xml +++ b/features/rageshake/api/src/main/res/values-ru/translations.xml @@ -1,6 +1,6 @@ - "При последнем использовании %1$s произошел сбой. Хотите поделиться отчетом о сбое?" + "При последнем использовании %1$s произошел сбой. Хотите поделиться отчетом?" "Похоже, что вы трясете телефон. Хотите открыть экран сообщения об ошибке?" "Встряхните" "Порог обнаружения" diff --git a/features/rageshake/impl/build.gradle.kts b/features/rageshake/impl/build.gradle.kts index b17d78f3aa..0200f21e61 100644 --- a/features/rageshake/impl/build.gradle.kts +++ b/features/rageshake/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -52,6 +53,7 @@ dependencies { testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.sessionStorage.test) testImplementation(projects.features.rageshake.test) + testImplementation(projects.features.viewfolder.test) testImplementation(projects.libraries.preferences.test) testImplementation(projects.services.toolbox.test) testImplementation(libs.network.mockwebserver) diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/DefaultRageshakeFeatureAvailability.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/DefaultRageshakeFeatureAvailability.kt index 8cb210159b..97fe32513d 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/DefaultRageshakeFeatureAvailability.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/DefaultRageshakeFeatureAvailability.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,14 +10,12 @@ package io.element.android.features.rageshake.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.rageshake.api.RageshakeFeatureAvailability import io.element.android.features.rageshake.impl.reporter.BugReporterUrlProvider import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @ContributesBinding(AppScope::class) -@Inject class DefaultRageshakeFeatureAvailability( private val bugReporterUrlProvider: BugReporterUrlProvider, ) : RageshakeFeatureAvailability { diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportEvents.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportEvents.kt index fbf2906623..90751873dd 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportEvents.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,4 +17,5 @@ sealed interface BugReportEvents { data class SetSendLog(val sendLog: Boolean) : BugReportEvents data class SetCanContact(val canContact: Boolean) : BugReportEvents data class SetSendScreenshot(val sendScreenshot: Boolean) : BugReportEvents + data class SetSendPushRules(val sendPushRules: Boolean) : BugReportEvents } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportFlowNode.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportFlowNode.kt index 10af89f740..f843d7da82 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportFlowNode.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportFlowNode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,6 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push @@ -25,6 +25,7 @@ import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint import io.element.android.features.viewfolder.api.ViewFolderEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode import kotlinx.parcelize.Parcelize @@ -42,9 +43,7 @@ class BugReportFlowNode( buildContext = buildContext, plugins = plugins ) { - private fun onDone() { - plugins().forEach { it.onDone() } - } + private val callback: BugReportEntryPoint.Callback = callback() sealed interface NavTarget : Parcelable { @Parcelize @@ -61,10 +60,10 @@ class BugReportFlowNode( NavTarget.Root -> { val callback = object : BugReportNode.Callback { override fun onDone() { - this@BugReportFlowNode.onDone() + callback.onDone() } - override fun onViewLogs(basePath: String) { + override fun navigateToViewLogs(basePath: String) { backstack.push(NavTarget.ViewLogs(rootPath = basePath)) } } @@ -79,11 +78,12 @@ class BugReportFlowNode( val params = ViewFolderEntryPoint.Params( rootPath = navTarget.rootPath, ) - viewFolderEntryPoint - .nodeBuilder(this, buildContext) - .params(params) - .callback(callback) - .build() + viewFolderEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = params, + callback = callback, + ) } } } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportFormError.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportFormError.kt index d912510951..ba3d9f16f0 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportFormError.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportFormError.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportNode.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportNode.kt index e307dba8ec..f6d6788ba7 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportNode.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,13 +14,13 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.rageshake.api.reporter.BugReporter import io.element.android.libraries.androidutils.system.toast +import io.element.android.libraries.architecture.callback import io.element.android.libraries.ui.strings.CommonStrings @ContributesNode(AppScope::class) @@ -32,16 +33,10 @@ class BugReportNode( ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { fun onDone() - fun onViewLogs(basePath: String) + fun navigateToViewLogs(basePath: String) } - private fun onViewLogs(basePath: String) { - plugins().forEach { it.onViewLogs(basePath) } - } - - private fun onDone() { - plugins().forEach { it.onDone() } - } + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { @@ -53,12 +48,12 @@ class BugReportNode( onBackClick = { navigateUp() }, onSuccess = { activity?.toast(CommonStrings.common_report_submitted) - onDone() + callback.onDone() }, onViewLogs = { // Force a logcat dump bugReporter.saveLogCat() - onViewLogs(bugReporter.logDirectory().absolutePath) + callback.navigateToViewLogs(bugReporter.logDirectory().absolutePath) } ) } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt index 4faef73589..4985f9b30e 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -77,12 +78,12 @@ class BugReportPresenter( val sendingAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } - val formState: MutableState = remember { + val formState: MutableState = rememberSaveable { mutableStateOf(BugReportFormState.Default) } val uploadListener = BugReporterUploadListener(sendingProgress, sendingAction) - fun handleEvents(event: BugReportEvents) { + fun handleEvent(event: BugReportEvents) { when (event) { BugReportEvents.SendBugReport -> { if (formState.value.description.length < 10) { @@ -105,6 +106,9 @@ class BugReportPresenter( is BugReportEvents.SetSendScreenshot -> updateFormState(formState) { copy(sendScreenshot = event.sendScreenshot) } + is BugReportEvents.SetSendPushRules -> updateFormState(formState) { + copy(sendPushRules = event.sendPushRules) + } BugReportEvents.ClearError -> { sendingProgress.floatValue = 0f sendingAction.value = AsyncAction.Uninitialized @@ -118,7 +122,7 @@ class BugReportPresenter( sending = sendingAction.value, formState = formState.value, screenshotUri = screenshotUri.value, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } @@ -137,6 +141,7 @@ class BugReportPresenter( withScreenshot = formState.sendScreenshot, problemDescription = formState.description, canContact = formState.canContact, + sendPushRules = formState.sendPushRules, listener = listener ) } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportState.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportState.kt index 8922446329..65cc055ec9 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportState.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -29,14 +30,16 @@ data class BugReportFormState( val description: String, val sendLogs: Boolean, val canContact: Boolean, - val sendScreenshot: Boolean + val sendScreenshot: Boolean, + val sendPushRules: Boolean, ) : Parcelable { companion object { val Default = BugReportFormState( description = "", sendLogs = true, canContact = false, - sendScreenshot = false + sendScreenshot = false, + sendPushRules = false, ) } } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportStateProvider.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportStateProvider.kt index ef36feabbd..0afd06f22a 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportStateProvider.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt index 760106b0fb..612d3aa68c 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportView.kt @@ -1,12 +1,14 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.rageshake.impl.bugreport +import android.content.res.Configuration import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -26,6 +28,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage @@ -40,7 +43,6 @@ import io.element.android.libraries.designsystem.components.preferences.Preferen import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch import io.element.android.libraries.designsystem.modifiers.onTabOrEnterKeyFocusNext import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.Text @@ -142,6 +144,13 @@ fun BugReportView( } } } + PreferenceSwitch( + isChecked = state.formState.sendPushRules, + onCheckedChange = { eventSink(BugReportEvents.SetSendPushRules(it)) }, + enabled = isFormEnabled, + title = stringResource(R.string.screen_bug_report_send_notification_settings_title), + subtitle = stringResource(R.string.screen_bug_report_send_notification_settings_description), + ) // Submit PreferenceRow { Button( @@ -174,9 +183,20 @@ fun BugReportView( } } -@PreviewsDayNight +@Preview(heightDp = 1000) @Composable -internal fun BugReportViewPreview(@PreviewParameter(BugReportStateProvider::class) state: BugReportState) = ElementPreview { +internal fun BugReportViewDayPreview(@PreviewParameter(BugReportStateProvider::class) state: BugReportState) = ElementPreview { + BugReportView( + state = state, + onSuccess = {}, + onBackClick = {}, + onViewLogs = {}, + ) +} + +@Preview(heightDp = 1000, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +internal fun BugReportViewNightPreview(@PreviewParameter(BugReportStateProvider::class) state: BugReportState) = ElementPreview { BugReportView( state = state, onSuccess = {}, diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/DefaultBugReportEntryPoint.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/DefaultBugReportEntryPoint.kt index 6fa5772c17..615bd37f2d 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/DefaultBugReportEntryPoint.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/DefaultBugReportEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,28 +10,18 @@ package io.element.android.features.rageshake.impl.bugreport import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultBugReportEntryPoint : BugReportEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): BugReportEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : BugReportEntryPoint.NodeBuilder { - override fun callback(callback: BugReportEntryPoint.Callback): BugReportEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: BugReportEntryPoint.Callback, + ): Node { + return parentNode.createNode(buildContext, listOf(callback)) } } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/CrashDataStore.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/CrashDataStore.kt index 3d6df9a424..234605d644 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/CrashDataStore.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/CrashDataStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt index 4d9f596d1d..25afadec57 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/DefaultCrashDetectionPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,7 +17,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.rageshake.api.RageshakeFeatureAvailability import io.element.android.features.rageshake.api.crash.CrashDetectionEvents import io.element.android.features.rageshake.api.crash.CrashDetectionPresenter @@ -29,7 +29,6 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch @ContributesBinding(AppScope::class) -@Inject class DefaultCrashDetectionPresenter( private val buildMeta: BuildMeta, private val crashDataStore: CrashDataStore, @@ -49,7 +48,7 @@ class DefaultCrashDetectionPresenter( } }.collectAsState(false) - fun handleEvents(event: CrashDetectionEvents) { + fun handleEvent(event: CrashDetectionEvents) { when (event) { CrashDetectionEvents.ResetAllCrashData -> localCoroutineScope.resetAll() CrashDetectionEvents.ResetAppHasCrashed -> localCoroutineScope.resetAppHasCrashed() @@ -59,7 +58,7 @@ class DefaultCrashDetectionPresenter( return CrashDetectionState( appName = buildMeta.applicationName, crashDetected = crashDetected, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/PreferencesCrashDataStore.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/PreferencesCrashDataStore.kt index a2be13b4cd..91aee0dc67 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/PreferencesCrashDataStore.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/PreferencesCrashDataStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,7 +13,6 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory import kotlinx.coroutines.flow.Flow @@ -23,7 +23,6 @@ private val appHasCrashedKey = booleanPreferencesKey("appHasCrashed") private val crashDataKey = stringPreferencesKey("crashData") @ContributesBinding(AppScope::class) -@Inject class PreferencesCrashDataStore( preferenceDataStoreFactory: PreferenceDataStoreFactory, ) : CrashDataStore { diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/VectorUncaughtExceptionHandler.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/VectorUncaughtExceptionHandler.kt index e41583b61c..0eafdf35a9 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/VectorUncaughtExceptionHandler.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/crash/VectorUncaughtExceptionHandler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/detection/DefaultRageshakeDetectionPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/detection/DefaultRageshakeDetectionPresenter.kt index c0120bfc3a..a7813ed6fb 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/detection/DefaultRageshakeDetectionPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/detection/DefaultRageshakeDetectionPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,7 +17,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.rageshake.api.detection.RageshakeDetectionEvents import io.element.android.features.rageshake.api.detection.RageshakeDetectionPresenter import io.element.android.features.rageshake.api.detection.RageshakeDetectionState @@ -30,7 +30,6 @@ import kotlinx.coroutines.launch import timber.log.Timber @ContributesBinding(AppScope::class) -@Inject class DefaultRageshakeDetectionPresenter( private val screenshotHolder: ScreenshotHolder, private val rageShake: RageShake, @@ -50,7 +49,7 @@ class DefaultRageshakeDetectionPresenter( mutableStateOf(false) } - fun handleEvents(event: RageshakeDetectionEvents) { + fun handleEvent(event: RageshakeDetectionEvents) { when (event) { RageshakeDetectionEvents.Disable -> { preferencesState.eventSink(RageshakePreferencesEvents.SetIsEnabled(false)) @@ -69,7 +68,7 @@ class DefaultRageshakeDetectionPresenter( takeScreenshot = takeScreenshot.value, showDialog = showDialog.value, preferenceState = preferencesState, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/di/RageshakeBindings.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/di/RageshakeBindings.kt index e1172c31fc..59795e528f 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/di/RageshakeBindings.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/di/RageshakeBindings.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/di/RageshakeModule.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/di/RageshakeModule.kt index 97f4d39ec3..475127a345 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/di/RageshakeModule.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/di/RageshakeModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/DefaultLogFilesRemover.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/DefaultLogFilesRemover.kt index 1122042d7f..0e1abd30b7 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/DefaultLogFilesRemover.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/logs/DefaultLogFilesRemover.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,13 +10,11 @@ package io.element.android.features.rageshake.impl.logs import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.rageshake.api.logs.LogFilesRemover import io.element.android.features.rageshake.impl.reporter.DefaultBugReporter import java.io.File @ContributesBinding(AppScope::class) -@Inject class DefaultLogFilesRemover( private val bugReporter: DefaultBugReporter, ) : LogFilesRemover { diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt index d1072f360c..f16d99c634 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,7 +18,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.rageshake.api.RageshakeFeatureAvailability import io.element.android.features.rageshake.api.preferences.RageshakePreferencesEvents import io.element.android.features.rageshake.api.preferences.RageshakePreferencesPresenter @@ -28,7 +28,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @ContributesBinding(AppScope::class) -@Inject class DefaultRageshakePreferencesPresenter( private val rageshake: RageShake, private val rageshakeDataStore: RageshakeDataStore, @@ -49,7 +48,7 @@ class DefaultRageshakePreferencesPresenter( rageshakeDataStore.sensitivity() }.collectAsState(initial = 0f) - fun handleEvents(event: RageshakePreferencesEvents) { + fun handleEvent(event: RageshakePreferencesEvents) { when (event) { is RageshakePreferencesEvents.SetIsEnabled -> localCoroutineScope.setIsEnabled(event.isEnabled) is RageshakePreferencesEvents.SetSensitivity -> localCoroutineScope.setSensitivity(event.sensitivity) @@ -61,7 +60,7 @@ class DefaultRageshakePreferencesPresenter( isEnabled = isEnabled, isSupported = isSupported.value, sensitivity = sensitivity, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/DefaultRageShake.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/DefaultRageShake.kt index 634e3ed65a..04efd5bcfc 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/DefaultRageShake.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/DefaultRageShake.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,14 +15,12 @@ import androidx.core.content.getSystemService import com.squareup.seismic.ShakeDetector import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import dev.zacsweers.metro.binding import io.element.android.libraries.di.annotations.ApplicationContext @SingleIn(AppScope::class) @ContributesBinding(scope = AppScope::class, binding = binding()) -@Inject class DefaultRageShake( @ApplicationContext context: Context, ) : ShakeDetector.Listener, RageShake { diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/PreferencesRageshakeDataStore.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/PreferencesRageshakeDataStore.kt index 32be2f7e95..04ead6ee14 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/PreferencesRageshakeDataStore.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/PreferencesRageshakeDataStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,7 +13,6 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.floatPreferencesKey import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory import kotlinx.coroutines.flow.Flow @@ -22,7 +22,6 @@ private val enabledKey = booleanPreferencesKey("enabled") private val sensitivityKey = floatPreferencesKey("sensitivity") @ContributesBinding(AppScope::class) -@Inject class PreferencesRageshakeDataStore( preferenceDataStoreFactory: PreferenceDataStoreFactory, ) : RageshakeDataStore { diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/RageShake.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/RageShake.kt index d75d5e5666..f1f6e6f5c2 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/RageShake.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/RageShake.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/RageshakeDataStore.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/RageshakeDataStore.kt index f13419bfb2..7afaa0d976 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/RageshakeDataStore.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/rageshake/RageshakeDataStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReportAppNameProvider.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReportAppNameProvider.kt index 9e450d5edb..cef434a5dd 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReportAppNameProvider.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReportAppNameProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.features.rageshake.impl.reporter import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.appconfig.RageshakeConfig fun interface BugReportAppNameProvider { @@ -17,7 +17,6 @@ fun interface BugReportAppNameProvider { } @ContributesBinding(AppScope::class) -@Inject class DefaultBugReportAppNameProvider : BugReportAppNameProvider { override fun provide(): String = RageshakeConfig.BUG_REPORT_APP_NAME } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.kt index 0fe0bfdc86..ccdff2e69e 100755 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBody.kt @@ -1,8 +1,9 @@ /* - * Copyright (C) 2014 Square, Inc. - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. + * Copyright 2014 Square, Inc. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @file:Suppress( diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBodyListener.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBodyListener.kt index f6fe406666..8d21b281ca 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBodyListener.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterMultipartBodyListener.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterUrlProvider.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterUrlProvider.kt index e7e0e4e060..d6d74d81a5 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterUrlProvider.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/BugReporterUrlProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 5787939688..f51d699350 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,6 @@ import androidx.core.net.toFile import androidx.core.net.toUri import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Provider import dev.zacsweers.metro.SingleIn import io.element.android.appconfig.RageshakeConfig @@ -28,22 +28,29 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.mimetype.MimeTypes +import io.element.android.libraries.di.annotations.AppCoroutineScope import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.matrix.api.SdkMetadata -import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.tracing.TracingService import io.element.android.libraries.network.useragent.UserAgentProvider import io.element.android.libraries.sessionstorage.api.SessionStore +import io.element.android.libraries.sessionstorage.api.sessionIdFlow import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response import org.json.JSONException import org.json.JSONObject @@ -63,9 +70,10 @@ import java.util.Locale */ @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DefaultBugReporter( @ApplicationContext private val context: Context, + @AppCoroutineScope + private val appCoroutineScope: CoroutineScope, private val screenshotHolder: ScreenshotHolder, private val crashDataStore: CrashDataStore, private val coroutineDispatchers: CoroutineDispatchers, @@ -77,7 +85,6 @@ class DefaultBugReporter( private val sdkMetadata: SdkMetadata, private val matrixClientProvider: MatrixClientProvider, private val tracingService: TracingService, - matrixAuthenticationService: MatrixAuthenticationService, ) : BugReporter { companion object { // filenames @@ -88,8 +95,6 @@ class DefaultBugReporter( private val logcatCommandDebug = arrayOf("logcat", "-d", "-v", "threadtime", "*:*") private var currentTracingLogLevel: String? = null - private val logCatErrFile: File - get() = File(logDirectory(), LOG_CAT_FILENAME) private val baseLogDirectory = File(context.cacheDir, LOG_DIRECTORY_NAME) private var currentLogDirectory: File = baseLogDirectory @@ -97,13 +102,18 @@ class DefaultBugReporter( if (buildMeta.isEnterpriseBuild) { val logSubfolder = runBlocking { sessionStore.getLatestSession() - }?.userId?.substringAfter(":") + }?.userId?.let(::UserId)?.domainName setCurrentLogDirectory(logSubfolder) - matrixAuthenticationService.listenToNewMatrixClients { - // When a new Matrix client is created, we update the tracing configuration to write - // the files in a dedicated subfolders. - setLogDirectorySubfolder(it.userIdServerName()) - } + sessionStore.sessionIdFlow() + .map { + it?.let(::UserId)?.domainName + } + .distinctUntilChanged() + .onEach { logSubfolder -> + setCurrentLogDirectory(logSubfolder) + tracingService.updateWriteToFilesConfiguration(createWriteToFilesConfiguration()) + } + .launchIn(appCoroutineScope) } } @@ -113,6 +123,7 @@ class DefaultBugReporter( withScreenshot: Boolean, problemDescription: String, canContact: Boolean, + sendPushRules: Boolean, listener: BugReporterListener, ) { val url = bugReporterUrlProvider.provide().first() @@ -147,12 +158,17 @@ class DefaultBugReporter( } if (withCrashLogs || withDevicesLogs) { saveLogCat() - val gzippedLogcat = compressFile(logCatErrFile) - if (gzippedLogcat != null) { - gzippedFiles.add(0, gzippedLogcat) - } + ?.let { logCatFile -> + compressFile(logCatFile).also { + logCatFile.safeDelete() + } + } + ?.let { gzippedLogcat -> + gzippedFiles.add(0, gzippedLogcat) + } } val sessionData = sessionStore.getLatestSession() + val numberOfAccounts = sessionStore.numberOfSessions() val deviceId = sessionData?.deviceId ?: "undefined" val userId = sessionData?.userId?.let { UserId(it) } // build the multi part request @@ -161,6 +177,7 @@ class DefaultBugReporter( .addFormDataPart("app", RageshakeConfig.BUG_REPORT_APP_NAME) .addFormDataPart("user_agent", userAgentProvider.provide()) .addFormDataPart("user_id", userId?.toString() ?: "undefined") + .addFormDataPart("number_of_accounts", numberOfAccounts.toString()) .addFormDataPart("can_contact", canContact.toString()) .addFormDataPart("device_id", deviceId) .addFormDataPart("device", Build.MODEL.trim()) @@ -181,6 +198,16 @@ class DefaultBugReporter( if (curveKey != null && edKey != null) { builder.addFormDataPart("device_keys", "curve25519:$curveKey, ed25519:$edKey") } + + if (sendPushRules) { + client.notificationSettingsService.getRawPushRules().getOrNull()?.let { pushRules -> + builder.addFormDataPart( + name = "file", + filename = "push_rules.json", + body = pushRules.toByteArray().toRequestBody(MimeTypes.Json.toMediaTypeOrNull()) + ) + } + } } } if (crashCallStack.isNotEmpty() && withCrashLogs) { @@ -321,13 +348,6 @@ class DefaultBugReporter( } } - override fun setLogDirectorySubfolder(subfolderName: String?) { - if (buildMeta.isEnterpriseBuild) { - setCurrentLogDirectory(subfolderName) - tracingService.updateWriteToFilesConfiguration(createWriteToFilesConfiguration()) - } - } - private fun setCurrentLogDirectory(subfolderName: String?) { currentLogDirectory = if (subfolderName == null) { baseLogDirectory @@ -369,7 +389,8 @@ class DefaultBugReporter( onException = { Timber.e(it, "## getLogFiles() failed") } ) { val logDirectory = logDirectory() - logDirectory.listFiles()?.toList() + logDirectory.listFiles() + ?.filter { it.isFile && !it.name.endsWith(LOG_CAT_FILENAME) } }.orEmpty() } @@ -382,19 +403,19 @@ class DefaultBugReporter( * * @return the file if the operation succeeds */ - override fun saveLogCat() { - val file = logCatErrFile + override fun saveLogCat(): File? { + val file = File(baseLogDirectory, LOG_CAT_FILENAME) if (file.exists()) { file.safeDelete() } - try { + return try { file.writer().use { - getLogCatError(it) + getLogCatContent(it) } - } catch (error: OutOfMemoryError) { - Timber.e(error, "## saveLogCat() : fail to write logcat OOM") + file } catch (e: Exception) { Timber.e(e, "## saveLogCat() : fail to write logcat") + null } } @@ -403,15 +424,10 @@ class DefaultBugReporter( * * @param streamWriter the stream writer */ - private fun getLogCatError(streamWriter: OutputStreamWriter) { - val logcatProcess: Process - - try { - logcatProcess = Runtime.getRuntime().exec(logcatCommandDebug) - } catch (e1: IOException) { - return - } - + private fun getLogCatContent(streamWriter: OutputStreamWriter) { + val logcatProcess = tryOrNull { + Runtime.getRuntime().exec(logcatCommandDebug) + } ?: return try { val separator = System.lineSeparator() logcatProcess.inputStream @@ -422,7 +438,7 @@ class DefaultBugReporter( streamWriter.append(separator) } } catch (e: IOException) { - Timber.e(e, "getLog fails") + Timber.e(e, "getLogCatContent fails") } } } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProvider.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProvider.kt index 84f8e2ca0a..384d059e80 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProvider.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,32 +10,39 @@ package io.element.android.features.rageshake.impl.reporter import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.appconfig.RageshakeConfig import io.element.android.features.enterprise.api.BugReportUrl import io.element.android.features.enterprise.api.EnterpriseService +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.sessionstorage.api.SessionStore +import io.element.android.libraries.sessionstorage.api.sessionIdFlow +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl @ContributesBinding(AppScope::class) -@Inject class DefaultBugReporterUrlProvider( private val bugReportAppNameProvider: BugReportAppNameProvider, private val enterpriseService: EnterpriseService, + private val sessionStore: SessionStore, ) : BugReporterUrlProvider { + @OptIn(ExperimentalCoroutinesApi::class) override fun provide(): Flow { if (bugReportAppNameProvider.provide().isEmpty()) return flowOf(null) - return enterpriseService.bugReportUrlFlow - .map { bugReportUrl -> - when (bugReportUrl) { - is BugReportUrl.Custom -> bugReportUrl.url - BugReportUrl.Disabled -> null - BugReportUrl.UseDefault -> RageshakeConfig.BUG_REPORT_URL.takeIf { it.isNotEmpty() } + return sessionStore.sessionIdFlow().flatMapLatest { sessionId -> + enterpriseService.bugReportUrlFlow(sessionId?.let(::SessionId)) + .map { bugReportUrl -> + when (bugReportUrl) { + is BugReportUrl.Custom -> bugReportUrl.url + BugReportUrl.Disabled -> null + BugReportUrl.UseDefault -> RageshakeConfig.BUG_REPORT_URL.takeIf { it.isNotEmpty() } + } } - } - .map { it?.toHttpUrl() } + .map { it?.toHttpUrl() } + } } } diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/screenshot/DefaultScreenshotHolder.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/screenshot/DefaultScreenshotHolder.kt index 5166f3d672..96fb145d83 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/screenshot/DefaultScreenshotHolder.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/screenshot/DefaultScreenshotHolder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,7 +13,6 @@ import android.graphics.Bitmap import androidx.core.net.toUri import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.androidutils.bitmap.writeBitmap import io.element.android.libraries.androidutils.file.safeDelete @@ -21,7 +21,6 @@ import java.io.File @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DefaultScreenshotHolder( @ApplicationContext private val context: Context, ) : ScreenshotHolder { diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/screenshot/ScreenshotHolder.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/screenshot/ScreenshotHolder.kt index c746a573d3..8ea58f67d8 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/screenshot/ScreenshotHolder.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/screenshot/ScreenshotHolder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/impl/src/main/res/values-hr/translations.xml b/features/rageshake/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..e49427b2a9 --- /dev/null +++ b/features/rageshake/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,20 @@ + + + "Priloži snimku zaslona" + "Možete mi se obratiti ako imate bilo kakvih dodatnih pitanja." + "Javi mi se" + "Uredi snimku zaslona" + "Opišite problem. Što ste napravili? Što ste očekivali da će se dogoditi? Što se zapravo dogodilo. Molimo vas da što detaljnije opišete problem." + "Opišite problem…" + "Ako je moguće, molimo vas da opis bude na engleskom jeziku." + "Opis je prekratak; navedite više pojedinosti o tome što se dogodilo. Hvala!" + "Pošalji zapisnike o padu aplikacije" + "Dopusti zapisnike" + "Vaši su zapisnici preopširni pa ih nije moguće uključiti u ovo izvješće. Molimo vas da nam ih pošaljete na drugi način." + "Pošalji snimku zaslona" + "Zapisnici će biti uključeni u vašu poruku kako bismo bili sigurni da sve ispravno funkcionira. Kako biste poslali poruku bez zapisnika, isključite ovu postavku." + "%1$s neočekivano je prestao s radom prilikom posljednjeg korištenja. Želite li s nama podijeliti izvješće o padu?" + "Ako imate problema s obavijestima, prijenos pravila za slanje obavijesti može nam pomoći da utvrdimo uzrok. Imajte na umu da ta pravila mogu sadržavati privatne podatke, kao što su vaše ime za prikaz ili ključne riječi za koje želite primati obavijesti." + "Postavke slanja obavijesti" + "Prikaz zapisnika" + diff --git a/features/rageshake/impl/src/main/res/values-it/translations.xml b/features/rageshake/impl/src/main/res/values-it/translations.xml index 8709881709..31fb95f9f6 100644 --- a/features/rageshake/impl/src/main/res/values-it/translations.xml +++ b/features/rageshake/impl/src/main/res/values-it/translations.xml @@ -14,5 +14,7 @@ "Invia istantanea schermo" "Per verificare che le cose funzionino come previsto, i log verranno inviati con il tuo messaggio. Per inviare solo il tuo messaggio, disattiva questa impostazione." "%1$s si è chiuso inaspettatamente l\'ultima volta che è stato usato. Vuoi condividere con noi un rapporto sull\'arresto anomalo?" + "Se riscontri problemi con le notifiche, caricare le regole per le notifiche push può aiutarci a individuare la causa principale. Tieni presente che queste regole possono contenere informazioni private, come il tuo nome visualizzato o le parole chiave per cui ricevere notifiche." + "Invia impostazioni di notifica" "Visualizza i log" diff --git a/features/rageshake/impl/src/main/res/values-nb/translations.xml b/features/rageshake/impl/src/main/res/values-nb/translations.xml index 4fa4d0a653..996793c262 100644 --- a/features/rageshake/impl/src/main/res/values-nb/translations.xml +++ b/features/rageshake/impl/src/main/res/values-nb/translations.xml @@ -15,5 +15,6 @@ "Logger vil bli inkludert i meldingen din, for å sikre at alt fungerer som det skal. For å sende meldingen uten logger, slå av denne innstillingen." "%1$s krasjet sist gang den ble brukt. Vil du dele en krasjrapport med oss?" "Hvis du har problemer med varsler, kan det å laste opp varslingsinnstillingene hjelpe oss med å finne den underliggende årsaken." + "Send varslingsinnstillinger" "Vis logger" diff --git a/features/rageshake/impl/src/main/res/values-pl/translations.xml b/features/rageshake/impl/src/main/res/values-pl/translations.xml index e421d747bf..c433071584 100644 --- a/features/rageshake/impl/src/main/res/values-pl/translations.xml +++ b/features/rageshake/impl/src/main/res/values-pl/translations.xml @@ -14,5 +14,7 @@ "Wyślij zrzut ekranu" "Logi zostaną dołączone do Twojej wiadomości, aby upewnić się, że wszystko działa poprawnie. Aby wysłać wiadomość bez logów, wyłącz to ustawienie." "%1$s uległ awarii podczas ostatniego użycia. Czy chcesz przesłać nam raport o awarii?" + "Jeśli posiadasz problemy z powiadomieniami, przesłanie nam swoich ustawień pomoże nam ustalić przyczynę usterki." + "Ustawienia powiadomień" "Wyświetl logi" diff --git a/features/rageshake/impl/src/main/res/values-pt-rBR/translations.xml b/features/rageshake/impl/src/main/res/values-pt-rBR/translations.xml index 046c1b30d7..7495d5946e 100644 --- a/features/rageshake/impl/src/main/res/values-pt-rBR/translations.xml +++ b/features/rageshake/impl/src/main/res/values-pt-rBR/translations.xml @@ -14,5 +14,7 @@ "Enviar captura de tela" "Os registros serão incluídos com sua mensagem para garantir que tudo esteja funcionando corretamente. Para enviar sua mensagem sem registros, desative essa configuração." "%1$s falhou inesperadamente na última vez que foi usado. Gostaria de compartilhar um relatório de falhas conosco?" + "Se estiver enfrentando problemas com as notificações, enviar as regras de notificações push pode ajudar-nos a descobrir o que está errado. Observe que as regras podem conter informações privadas, como o seu nome de exibição ou palavras chaves de notificação." + "Enviar configurações de notificação" "Ver registros" diff --git a/features/rageshake/impl/src/main/res/values-ru/translations.xml b/features/rageshake/impl/src/main/res/values-ru/translations.xml index f1d5216405..42286efa44 100644 --- a/features/rageshake/impl/src/main/res/values-ru/translations.xml +++ b/features/rageshake/impl/src/main/res/values-ru/translations.xml @@ -13,7 +13,7 @@ "Ваши журналы слишком большие для включения в этот отчет. Пожалуйста, отправьте их нам другим способом." "Отправить снимок экрана" "Чтобы убедиться, что все работает правильно, в сообщение будут включены журналы. Чтобы отправить сообщение без журналов, отключите эту настройку." - "При последнем использовании %1$s произошел сбой. Хотите поделиться отчетом о сбое?" + "При последнем использовании %1$s произошел сбой. Хотите поделиться отчетом?" "Если у вас возникли проблемы с уведомлениями, загрузка настроек уведомлений может помочь нам определить основную причину." "Настройки отправки уведомлений" "Просмотр журналов" diff --git a/features/rageshake/impl/src/main/res/values-sk/translations.xml b/features/rageshake/impl/src/main/res/values-sk/translations.xml index c46181ad29..9edad566ce 100644 --- a/features/rageshake/impl/src/main/res/values-sk/translations.xml +++ b/features/rageshake/impl/src/main/res/values-sk/translations.xml @@ -14,5 +14,7 @@ "Odoslať snímku obrazovky" "K vašej správe budú priložené záznamy o chybe, aby sme sa uistili, že všetko funguje správne. Ak chcete odoslať správu bez záznamov o chybe, vypnite toto nastavenie." "%1$s zlyhal pri poslednom použití. Chcete zdieľať správu o páde s našim tímom?" + "Ak máte problémy s upozorneniami, nahranie pravidiel pre push upozornenia nám môže pomôcť určiť príčinu. Upozorňujeme, že tieto pravidlá môžu obsahovať súkromné ​​informácie, ako napríklad vaše zobrazované meno alebo kľúčové slová, na ktoré sa majú dostávať upozornenia." + "Nastavenia odosielania upozornení" "Zobraziť záznamy" diff --git a/features/rageshake/impl/src/main/res/values-uz/translations.xml b/features/rageshake/impl/src/main/res/values-uz/translations.xml index fbeb0d3271..e3aceaa7d6 100644 --- a/features/rageshake/impl/src/main/res/values-uz/translations.xml +++ b/features/rageshake/impl/src/main/res/values-uz/translations.xml @@ -10,6 +10,7 @@ "Tavsif juda qisqa, nima boʻlganligi haqida batafsilroq maʼlumot bering. Rahmat!" "Buzilish jurnallarini yuboring" "Jurnallarga ruxsat bering" + "Sizning jurnallaringiz juda katta, shuning uchun bu hisobotga kiritilmaydi, iltimos ularni bizga boshqa usulda yuboring." "Ekran tasvirini yuboring" "Har bir narsa to\'ri ishlayotganiga ishonch hosil qilish uchun xabaringizga jurnallar kiritiladi. Xabarni jurnallarsiz yuborish uchun ushbu sozlamani oʻchiring." "%1$soxirgi marta ishlatilganda qulab tushdi. Biz bilan nosozlik hisobotini baham ko\'rmoqchimisiz?" diff --git a/features/rageshake/impl/src/main/res/values-zh-rTW/translations.xml b/features/rageshake/impl/src/main/res/values-zh-rTW/translations.xml index 2105ab1697..10252ab699 100644 --- a/features/rageshake/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/rageshake/impl/src/main/res/values-zh-rTW/translations.xml @@ -14,5 +14,7 @@ "傳送螢幕截圖" "紀錄檔將包含在您的訊息中以確保一切運作正常。要在不包含紀錄檔的情況下傳送訊息,請關閉此設定。" "%1$s 上次使用時當機了。您想要與我們分享當機報告嗎?" + "若您遇到通知問題,上傳通知推播規則可以協助我們找出根本原因。請注意,這些規則可能包含您的私人資訊,例如您的顯示名稱或要接收通知的關鍵字。" + "傳送通知設定" "查看日誌" diff --git a/features/rageshake/impl/src/main/res/values/localazy.xml b/features/rageshake/impl/src/main/res/values/localazy.xml index f6d93c4114..85af2af4a7 100644 --- a/features/rageshake/impl/src/main/res/values/localazy.xml +++ b/features/rageshake/impl/src/main/res/values/localazy.xml @@ -14,7 +14,7 @@ "Send screenshot" "Logs will be included with your message to make sure that everything is working properly. To send your message without logs, turn off this setting." "%1$s crashed the last time it was used. Would you like to share a crash report with us?" - "If you are having issues with notifications, uploading the notification settings can help us pinpoint the root cause." + "If you are having issues with notifications, uploading the notification push rules can help us pinpoint the root cause. Note these rules can contain private information, such as your display name or keywords to be notified for." "Send notification settings" "View logs" diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/DefaultRageshakeFeatureAvailabilityTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/DefaultRageshakeFeatureAvailabilityTest.kt index 7f59245647..b656d14803 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/DefaultRageshakeFeatureAvailabilityTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/DefaultRageshakeFeatureAvailabilityTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 362b3798d6..f5b0508063 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -106,6 +107,20 @@ class BugReportPresenterTest { } } + @Test + fun `present - send notification settings`() = runTest { + val presenter = createPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink.invoke(BugReportEvents.SetSendPushRules(true)) + assertThat(awaitItem().formState).isEqualTo(BugReportFormState.Default.copy(sendPushRules = true)) + initialState.eventSink.invoke(BugReportEvents.SetSendPushRules(false)) + assertThat(awaitItem().formState).isEqualTo(BugReportFormState.Default.copy(sendPushRules = false)) + } + } + @Test fun `present - reset all`() = runTest { val presenter = createPresenter( diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/DefaultBugReportEntryPointTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/DefaultBugReportEntryPointTest.kt index 23d74f7247..bf459827fe 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/DefaultBugReportEntryPointTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/DefaultBugReportEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,11 +10,10 @@ package io.element.android.features.rageshake.impl.bugreport import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node import com.bumble.appyx.testing.junit4.util.MainDispatcherRule import com.google.common.truth.Truth.assertThat import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint -import io.element.android.features.viewfolder.api.ViewFolderEntryPoint +import io.element.android.features.viewfolder.test.FakeViewFolderEntryPoint import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.node.TestParentNode import kotlinx.coroutines.test.runTest @@ -34,17 +34,17 @@ class DefaultBugReportEntryPointTest { BugReportFlowNode( buildContext = buildContext, plugins = plugins, - viewFolderEntryPoint = object : ViewFolderEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext) = lambdaError() - }, + viewFolderEntryPoint = FakeViewFolderEntryPoint(), ) } val callback = object : BugReportEntryPoint.Callback { override fun onDone() = lambdaError() } - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + callback = callback, + ) assertThat(result).isInstanceOf(BugReportFlowNode::class.java) assertThat(result.plugins).contains(callback) } diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/FakeBugReporter.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/FakeBugReporter.kt index ebaa524bd5..36cc185e86 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/FakeBugReporter.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/FakeBugReporter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -26,6 +27,7 @@ class FakeBugReporter(val mode: Mode = Mode.Success) : BugReporter { withScreenshot: Boolean, problemDescription: String, canContact: Boolean, + sendPushRules: Boolean, listener: BugReporterListener, ) { delay(100) @@ -53,15 +55,11 @@ class FakeBugReporter(val mode: Mode = Mode.Success) : BugReporter { return File("fake") } - override fun setLogDirectorySubfolder(subfolderName: String?) { - // No op - } - override fun setCurrentTracingLogLevel(logLevel: String) { // No op } - override fun saveLogCat() { - // No op + override fun saveLogCat(): File? { + return null } } diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/FakeCrashDataStore.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/FakeCrashDataStore.kt index 8ab5d33eb3..3ddacd3d58 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/FakeCrashDataStore.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/FakeCrashDataStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/VectorUncaughtExceptionHandlerTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/VectorUncaughtExceptionHandlerTest.kt index f19362f99c..665e1ce4bd 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/VectorUncaughtExceptionHandlerTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/VectorUncaughtExceptionHandlerTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 a0172e3d39..dcaaa20a69 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 451bfd82ed..d76ab73aa8 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 df34e1cf48..8fda5c3d97 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/rageshake/FakeRageShake.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/rageshake/FakeRageShake.kt index b35bde295a..08dbe91823 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/rageshake/FakeRageShake.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/rageshake/FakeRageShake.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/rageshake/FakeRageshakeDataStore.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/rageshake/FakeRageshakeDataStore.kt index ec67ed13c0..122706b12b 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/rageshake/FakeRageshakeDataStore.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/rageshake/FakeRageshakeDataStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt index ecb8d13b12..41e136a53e 100755 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,15 +16,16 @@ import io.element.android.features.rageshake.impl.crash.FakeCrashDataStore import io.element.android.features.rageshake.impl.screenshot.FakeScreenshotHolder import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.api.MatrixClientProvider -import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.tracing.TracingService import io.element.android.libraries.matrix.api.tracing.WriteToFilesConfiguration +import io.element.android.libraries.matrix.test.A_DEVICE_ID +import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.FakeSdkMetadata -import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService +import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.tracing.FakeTracingService import io.element.android.libraries.network.useragent.DefaultUserAgentProvider import io.element.android.libraries.sessionstorage.api.SessionStore @@ -31,8 +33,10 @@ import io.element.android.libraries.sessionstorage.test.InMemorySessionStore import io.element.android.libraries.sessionstorage.test.aSessionData import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import okhttp3.MultipartReader import okhttp3.OkHttpClient @@ -65,6 +69,7 @@ class DefaultBugReporterTest { withDevicesLogs = true, withCrashLogs = true, withScreenshot = true, + sendPushRules = true, problemDescription = "a bug occurred", canContact = true, listener = object : BugReporterListener { @@ -108,6 +113,79 @@ class DefaultBugReporterTest { initialList = listOf(aSessionData(sessionId = "@foo:example.com", deviceId = "ABCDEFGH")) ) + val fakeEncryptionService = FakeEncryptionService() + + val fakePushRules = "{ content: ... }" + val fakeNotificationSettingsService = FakeNotificationSettingsService( + getRawPushRulesResult = { Result.success(fakePushRules) } + ) + val matrixClient = FakeMatrixClient(encryptionService = fakeEncryptionService, notificationSettingsService = fakeNotificationSettingsService) + + fakeEncryptionService.givenDeviceKeys("CURVECURVECURVE", "EDKEYEDKEYEDKY") + val sut = createDefaultBugReporter( + server = server, + crashDataStore = FakeCrashDataStore(), + sessionStore = mockSessionStore, + matrixClientProvider = FakeMatrixClientProvider(getClient = { Result.success(matrixClient) }) + ) + + val progressValues = mutableListOf() + sut.sendBugReport( + withDevicesLogs = true, + withCrashLogs = true, + withScreenshot = true, + sendPushRules = true, + problemDescription = "a bug occurred", + canContact = true, + listener = object : BugReporterListener { + override fun onUploadCancelled() {} + + override fun onUploadFailed(reason: String?) {} + + override fun onProgress(progress: Int) { + progressValues.add(progress) + } + + override fun onUploadSucceed() {} + }, + ) + val request = server.takeRequest() + + val foundValues = collectValuesFromFormData(request) + + assertThat(foundValues["app"]).isEqualTo(RageshakeConfig.BUG_REPORT_APP_NAME) + assertThat(foundValues["can_contact"]).isEqualTo("true") + assertThat(foundValues["device_id"]).isEqualTo("ABCDEFGH") + assertThat(foundValues["sdk_sha"]).isEqualTo("123456789") + assertThat(foundValues["user_id"]).isEqualTo("@foo:example.com") + assertThat(foundValues["number_of_accounts"]).isEqualTo("1") + assertThat(foundValues["text"]).isEqualTo("a bug occurred") + assertThat(foundValues["device_keys"]).isEqualTo("curve25519:CURVECURVECURVE, ed25519:EDKEYEDKEYEDKY") + assertThat(foundValues["file"]).contains(fakePushRules) + + // device_key now added given they are not null + // so is the file value for the included push_rules + assertThat(progressValues.size).isEqualTo(EXPECTED_NUMBER_OF_PROGRESS_VALUE + 2) + + server.shutdown() + } + + @Test + fun `test sendBugReport multi accounts`() = runTest { + val server = MockWebServer() + server.enqueue( + MockResponse() + .setResponseCode(200) + ) + server.start() + + val mockSessionStore = InMemorySessionStore( + initialList = listOf( + aSessionData(sessionId = "@foo:example.com", deviceId = "ABCDEFGH"), + aSessionData(sessionId = A_USER_ID.value, deviceId = A_DEVICE_ID.value), + ) + ) + val fakeEncryptionService = FakeEncryptionService() val matrixClient = FakeMatrixClient(encryptionService = fakeEncryptionService) @@ -147,6 +225,7 @@ class DefaultBugReporterTest { assertThat(foundValues["device_id"]).isEqualTo("ABCDEFGH") assertThat(foundValues["sdk_sha"]).isEqualTo("123456789") assertThat(foundValues["user_id"]).isEqualTo("@foo:example.com") + assertThat(foundValues["number_of_accounts"]).isEqualTo("2") assertThat(foundValues["text"]).isEqualTo("a bug occurred") assertThat(foundValues["device_keys"]).isEqualTo("curve25519:CURVECURVECURVE, ed25519:EDKEYEDKEYEDKY") @@ -228,6 +307,7 @@ class DefaultBugReporterTest { assertThat(foundValues["device_keys"]).isNull() assertThat(foundValues["device_id"]).isEqualTo("undefined") assertThat(foundValues["user_id"]).isEqualTo("undefined") + assertThat(foundValues["number_of_accounts"]).isEqualTo("0") assertThat(foundValues["label"]).isEqualTo("crash") } @@ -243,7 +323,7 @@ class DefaultBugReporterTest { while (part != null) { part.headers["Content-Disposition"]?.let { contentDisposition -> regex.find(contentDisposition)?.groupValues?.get(1)?.let { name -> - foundValues.put(name, part!!.body.readUtf8()) + foundValues.put(name, part.body.readUtf8()) } } part = multipartReader.nextPart() @@ -272,6 +352,7 @@ class DefaultBugReporterTest { withDevicesLogs = true, withCrashLogs = true, withScreenshot = true, + sendPushRules = true, problemDescription = "a bug occurred", canContact = true, listener = object : BugReporterListener { @@ -325,53 +406,85 @@ class DefaultBugReporterTest { assertThat(sut.logDirectory().absolutePath).endsWith("/cache/logs") } + @OptIn(ExperimentalCoroutinesApi::class) @Test - fun `when the log directory is updated, the tracing service is invoked`() = runTest { + fun `when a session is added, the tracing service is invoked`() = runTest { var param: WriteToFilesConfiguration? = null val updateWriteToFilesConfigurationResult = lambdaRecorder { param = it } - val sut = createDefaultBugReporter( + val sessionStore = InMemorySessionStore() + createDefaultBugReporter( buildMeta = aBuildMeta(isEnterpriseBuild = true), + sessionStore = sessionStore, tracingService = FakeTracingService( updateWriteToFilesConfigurationResult = updateWriteToFilesConfigurationResult, ), ) - sut.setLogDirectorySubfolder("my.sub.folder") + sessionStore.addSession(aSessionData(sessionId = "@alice:server.org")) + runCurrent() updateWriteToFilesConfigurationResult.assertions().isCalledOnce() assertThat(param).isNotNull() assertThat(param).isInstanceOf(WriteToFilesConfiguration.Enabled::class.java) - assertThat((param as WriteToFilesConfiguration.Enabled).directory).endsWith("/cache/logs/my.sub.folder") + assertThat((param as WriteToFilesConfiguration.Enabled).directory).endsWith("/cache/logs/server.org") assertThat((param as WriteToFilesConfiguration.Enabled).filenamePrefix).isEqualTo("logs") assertThat((param as WriteToFilesConfiguration.Enabled).numberOfFiles).isEqualTo(168) assertThat((param as WriteToFilesConfiguration.Enabled).filenameSuffix).isEqualTo("log") } + @OptIn(ExperimentalCoroutinesApi::class) @Test - fun `foss build - when the log directory is updated, the tracing service is not invoked`() = runTest { + fun `when another session is added on same domain, the tracing service is not invoked`() = runTest { val updateWriteToFilesConfigurationResult = lambdaRecorder {} - val sut = createDefaultBugReporter( - tracingService = FakeTracingService( - updateWriteToFilesConfigurationResult = updateWriteToFilesConfigurationResult, - ) - ) - sut.setLogDirectorySubfolder("my.sub.folder") - updateWriteToFilesConfigurationResult.assertions().isNeverCalled() - } - - @Test - fun `when the log directory is reset, the tracing service is invoked`() = runTest { - var param: WriteToFilesConfiguration? = null - val updateWriteToFilesConfigurationResult = lambdaRecorder { - param = it - } - val sut = createDefaultBugReporter( + val sessionStore = InMemorySessionStore() + createDefaultBugReporter( buildMeta = aBuildMeta(isEnterpriseBuild = true), + sessionStore = sessionStore, tracingService = FakeTracingService( updateWriteToFilesConfigurationResult = updateWriteToFilesConfigurationResult, ), ) - sut.setLogDirectorySubfolder(null) + sessionStore.addSession(aSessionData(sessionId = "@alice:server.org")) + runCurrent() + updateWriteToFilesConfigurationResult.assertions().isCalledOnce() + sessionStore.addSession(aSessionData(sessionId = "@bob:server.org")) + runCurrent() + updateWriteToFilesConfigurationResult.assertions().isCalledOnce() + } + + @Test + fun `foss build - when a session is added, the tracing service is not invoked`() = runTest { + val updateWriteToFilesConfigurationResult = lambdaRecorder {} + val sessionStore = InMemorySessionStore() + createDefaultBugReporter( + tracingService = FakeTracingService( + updateWriteToFilesConfigurationResult = updateWriteToFilesConfigurationResult, + ), + sessionStore = sessionStore, + ) + sessionStore.addSession(aSessionData(sessionId = "@alice:server.org")) + updateWriteToFilesConfigurationResult.assertions().isNeverCalled() + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `when the user signs out, the tracing service is invoked`() = runTest { + var param: WriteToFilesConfiguration? = null + val updateWriteToFilesConfigurationResult = lambdaRecorder { + param = it + } + val sessionStore = InMemorySessionStore( + initialList = listOf(aSessionData(sessionId = "@alice:server.org")), + ) + createDefaultBugReporter( + buildMeta = aBuildMeta(isEnterpriseBuild = true), + tracingService = FakeTracingService( + updateWriteToFilesConfigurationResult = updateWriteToFilesConfigurationResult, + ), + sessionStore = sessionStore, + ) + sessionStore.removeSession("@alice:server.org") + runCurrent() updateWriteToFilesConfigurationResult.assertions().isCalledOnce() assertThat(param).isNotNull() assertThat(param).isInstanceOf(WriteToFilesConfiguration.Enabled::class.java) @@ -384,66 +497,16 @@ class DefaultBugReporterTest { @Test fun `foss build - when the log directory is reset, the tracing service is not invoked`() = runTest { val updateWriteToFilesConfigurationResult = lambdaRecorder {} - val sut = createDefaultBugReporter( + val sessionStore = InMemorySessionStore( + initialList = listOf(aSessionData(sessionId = "@alice:server.org")), + ) + createDefaultBugReporter( tracingService = FakeTracingService( updateWriteToFilesConfigurationResult = updateWriteToFilesConfigurationResult, - ) + ), + sessionStore = sessionStore, ) - sut.setLogDirectorySubfolder(null) - updateWriteToFilesConfigurationResult.assertions().isNeverCalled() - } - - @Test - fun `when a new MatrixClient is created the logs folder is updated`() = runTest { - var param: WriteToFilesConfiguration? = null - val updateWriteToFilesConfigurationResult = lambdaRecorder { - param = it - } - val matrixAuthenticationService = FakeMatrixAuthenticationService().apply { - givenMatrixClient( - FakeMatrixClient( - userIdServerNameLambda = { "domain.foo.org" }, - ) - ) - } - val sut = createDefaultBugReporter( - buildMeta = aBuildMeta(isEnterpriseBuild = true), - matrixAuthenticationService = matrixAuthenticationService, - tracingService = FakeTracingService( - updateWriteToFilesConfigurationResult = updateWriteToFilesConfigurationResult, - ) - ) - assertThat(sut.logDirectory().absolutePath).endsWith("/cache/logs") - matrixAuthenticationService.login("alice", "password") - assertThat(sut.logDirectory().absolutePath).endsWith("/cache/logs/domain.foo.org") - updateWriteToFilesConfigurationResult.assertions().isCalledOnce() - assertThat(param).isNotNull() - assertThat(param).isInstanceOf(WriteToFilesConfiguration.Enabled::class.java) - assertThat((param as WriteToFilesConfiguration.Enabled).directory).endsWith("/cache/logs/domain.foo.org") - assertThat((param as WriteToFilesConfiguration.Enabled).filenamePrefix).isEqualTo("logs") - assertThat((param as WriteToFilesConfiguration.Enabled).numberOfFiles).isEqualTo(168) - assertThat((param as WriteToFilesConfiguration.Enabled).filenameSuffix).isEqualTo("log") - } - - @Test - fun `foss build - when a new MatrixClient is created the logs folder is not updated`() = runTest { - val updateWriteToFilesConfigurationResult = lambdaRecorder {} - val matrixAuthenticationService = FakeMatrixAuthenticationService().apply { - givenMatrixClient( - FakeMatrixClient( - userIdServerNameLambda = { "domain.foo.org" }, - ) - ) - } - val sut = createDefaultBugReporter( - matrixAuthenticationService = matrixAuthenticationService, - tracingService = FakeTracingService( - updateWriteToFilesConfigurationResult = updateWriteToFilesConfigurationResult, - ) - ) - assertThat(sut.logDirectory().absolutePath).endsWith("/cache/logs") - matrixAuthenticationService.login("alice", "password") - assertThat(sut.logDirectory().absolutePath).endsWith("/cache/logs") + sessionStore.removeSession("@alice:server.org") updateWriteToFilesConfigurationResult.assertions().isNeverCalled() } @@ -454,10 +517,10 @@ class DefaultBugReporterTest { crashDataStore: CrashDataStore = FakeCrashDataStore(), server: MockWebServer = MockWebServer(), tracingService: TracingService = FakeTracingService(), - matrixAuthenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(), ): DefaultBugReporter { return DefaultBugReporter( context = RuntimeEnvironment.getApplication(), + appCoroutineScope = backgroundScope, screenshotHolder = FakeScreenshotHolder(), crashDataStore = crashDataStore, coroutineDispatchers = testCoroutineDispatchers(), @@ -469,11 +532,10 @@ class DefaultBugReporterTest { sdkMetadata = FakeSdkMetadata("123456789"), matrixClientProvider = matrixClientProvider, tracingService = tracingService, - matrixAuthenticationService = matrixAuthenticationService, ) } companion object { - private const val EXPECTED_NUMBER_OF_PROGRESS_VALUE = 17 + private const val EXPECTED_NUMBER_OF_PROGRESS_VALUE = 18 } } diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProviderTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProviderTest.kt index fb7464adf7..67af12c19a 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProviderTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporterUrlProviderTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,16 +12,19 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.appconfig.RageshakeConfig import io.element.android.features.enterprise.api.BugReportUrl +import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.test.FakeEnterpriseService +import io.element.android.libraries.sessionstorage.api.SessionStore +import io.element.android.libraries.sessionstorage.test.InMemorySessionStore import kotlinx.coroutines.test.runTest import okhttp3.HttpUrl.Companion.toHttpUrl import org.junit.Test class DefaultBugReporterUrlProviderTest { @Test - fun `provide return values when there is an rageshake app name`() = runTest { + fun `provide returns values when there is an rageshake app name`() = runTest { val enterpriseService = FakeEnterpriseService() - val sut = DefaultBugReporterUrlProvider( + val sut = createDefaultBugReporterUrlProvider( bugReportAppNameProvider = { "rageshakeAppName" }, enterpriseService = enterpriseService, ) @@ -36,15 +40,21 @@ class DefaultBugReporterUrlProviderTest { } @Test - fun `provide return null when there is no rageshake app name`() = runTest { - val enterpriseService = FakeEnterpriseService() - val sut = DefaultBugReporterUrlProvider( - bugReportAppNameProvider = { "" }, - enterpriseService = enterpriseService, - ) + fun `provide returns null when there is no rageshake app name`() = runTest { + val sut = createDefaultBugReporterUrlProvider() sut.provide().test { assertThat(awaitItem()).isNull() awaitComplete() } } } + +private fun createDefaultBugReporterUrlProvider( + bugReportAppNameProvider: BugReportAppNameProvider = BugReportAppNameProvider { "" }, + enterpriseService: EnterpriseService = FakeEnterpriseService(), + sessionStore: SessionStore = InMemorySessionStore(), +) = DefaultBugReporterUrlProvider( + bugReportAppNameProvider = bugReportAppNameProvider, + enterpriseService = enterpriseService, + sessionStore = sessionStore, +) diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/NoopBugReporterListener.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/NoopBugReporterListener.kt index 19e5c6304e..557f0c9414 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/NoopBugReporterListener.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/reporter/NoopBugReporterListener.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/screenshot/FakeScreenshotHolder.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/screenshot/FakeScreenshotHolder.kt index 8e37da1910..543f8e505d 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/screenshot/FakeScreenshotHolder.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/screenshot/FakeScreenshotHolder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/rageshake/test/build.gradle.kts b/features/rageshake/test/build.gradle.kts index d731de03a8..afc5e51499 100644 --- a/features/rageshake/test/build.gradle.kts +++ b/features/rageshake/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/logs/FakeLogFilesRemover.kt b/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/logs/FakeLogFilesRemover.kt index a7a5ee04d2..9bf00c80c0 100644 --- a/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/logs/FakeLogFilesRemover.kt +++ b/features/rageshake/test/src/main/kotlin/io/element/android/features/rageshake/test/logs/FakeLogFilesRemover.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/reportroom/api/build.gradle.kts b/features/reportroom/api/build.gradle.kts index f5499b7a74..83318480d5 100644 --- a/features/reportroom/api/build.gradle.kts +++ b/features/reportroom/api/build.gradle.kts @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/reportroom/api/src/main/kotlin/io/element/android/features/reportroom/api/ReportRoomEntryPoint.kt b/features/reportroom/api/src/main/kotlin/io/element/android/features/reportroom/api/ReportRoomEntryPoint.kt index 1eff7f8206..ea99507346 100644 --- a/features/reportroom/api/src/main/kotlin/io/element/android/features/reportroom/api/ReportRoomEntryPoint.kt +++ b/features/reportroom/api/src/main/kotlin/io/element/android/features/reportroom/api/ReportRoomEntryPoint.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,5 +14,9 @@ import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.matrix.api.core.RoomId fun interface ReportRoomEntryPoint : FeatureEntryPoint { - fun createNode(parentNode: Node, buildContext: BuildContext, roomId: RoomId): Node + fun createNode( + parentNode: Node, + buildContext: BuildContext, + roomId: RoomId, + ): Node } diff --git a/features/reportroom/impl/build.gradle.kts b/features/reportroom/impl/build.gradle.kts index 99d65612bd..c945465aec 100644 --- a/features/reportroom/impl/build.gradle.kts +++ b/features/reportroom/impl/build.gradle.kts @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/DefaultReportRoomEntryPoint.kt b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/DefaultReportRoomEntryPoint.kt index e433c70bf6..1d4ee13d00 100644 --- a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/DefaultReportRoomEntryPoint.kt +++ b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/DefaultReportRoomEntryPoint.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,15 +12,17 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.reportroom.api.ReportRoomEntryPoint import io.element.android.libraries.architecture.createNode import io.element.android.libraries.matrix.api.core.RoomId @ContributesBinding(AppScope::class) -@Inject class DefaultReportRoomEntryPoint : ReportRoomEntryPoint { - override fun createNode(parentNode: Node, buildContext: BuildContext, roomId: RoomId): Node { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + roomId: RoomId, + ): Node { return parentNode.createNode(buildContext, plugins = listOf(ReportRoomNode.Inputs(roomId))) } } diff --git a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoom.kt b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoom.kt index dae5c4e272..45f6c30b90 100644 --- a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoom.kt +++ b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoom.kt @@ -1,14 +1,14 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.reportroom.impl import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId @@ -29,7 +29,6 @@ interface ReportRoom { } @ContributesBinding(SessionScope::class) -@Inject class DefaultReportRoom( private val client: MatrixClient, ) : ReportRoom { diff --git a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomEvents.kt b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomEvents.kt index e72f00e2a9..f7185f7fcd 100644 --- a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomEvents.kt +++ b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomNode.kt b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomNode.kt index cd136efcba..ef11730d47 100644 --- a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomNode.kt +++ b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomNode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomPresenter.kt b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomPresenter.kt index 42ab1cf08a..422cf42718 100644 --- a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomPresenter.kt +++ b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomPresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -43,7 +44,7 @@ class ReportRoomPresenter( val coroutineScope = rememberCoroutineScope() - fun handleEvents(event: ReportRoomEvents) { + fun handleEvent(event: ReportRoomEvents) { when (event) { ReportRoomEvents.Report -> coroutineScope.reportRoom(reason, leaveRoom, reportAction) ReportRoomEvents.ToggleLeaveRoom -> { @@ -61,7 +62,7 @@ class ReportRoomPresenter( reason = reason, leaveRoom = leaveRoom, reportAction = reportAction.value, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomState.kt b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomState.kt index a04f24123b..fa09306991 100644 --- a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomState.kt +++ b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomStateProvider.kt b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomStateProvider.kt index 1e89a31adb..8baae080f5 100644 --- a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomStateProvider.kt +++ b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomView.kt b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomView.kt index 5af0beefcd..4ab1a51336 100644 --- a/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomView.kt +++ b/features/reportroom/impl/src/main/kotlin/io/element/android/features/reportroom/impl/ReportRoomView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/reportroom/impl/src/main/res/values-bg/translations.xml b/features/reportroom/impl/src/main/res/values-bg/translations.xml new file mode 100644 index 0000000000..aeb51d316f --- /dev/null +++ b/features/reportroom/impl/src/main/res/values-bg/translations.xml @@ -0,0 +1,4 @@ + + + "Докладване на стаята" + diff --git a/features/reportroom/impl/src/main/res/values-eu/translations.xml b/features/reportroom/impl/src/main/res/values-eu/translations.xml new file mode 100644 index 0000000000..bc8f50e002 --- /dev/null +++ b/features/reportroom/impl/src/main/res/values-eu/translations.xml @@ -0,0 +1,4 @@ + + + "Salatu gela" + diff --git a/features/reportroom/impl/src/main/res/values-hr/translations.xml b/features/reportroom/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..50ecc7a24d --- /dev/null +++ b/features/reportroom/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,8 @@ + + + "Vaša je prijava uspješno poslana, ali naišli smo na problem prilikom pokušaja napuštanja sobe. Pokušajte ponovno." + "Sobu nije moguće napustiti" + "Prijavi ovu sobu svom administratoru. Ako su poruke šifrirane, vaš administrator neće ih moći pročitati." + "Navedite razlog prijave…" + "Prijavi sobu" + diff --git a/features/reportroom/impl/src/main/res/values-nb/translations.xml b/features/reportroom/impl/src/main/res/values-nb/translations.xml index bda1334210..5d822223ae 100644 --- a/features/reportroom/impl/src/main/res/values-nb/translations.xml +++ b/features/reportroom/impl/src/main/res/values-nb/translations.xml @@ -4,5 +4,5 @@ "Kan ikke forlate rommet" "Rapporter dette rommet til administratoren din. Hvis meldingene er kryptert, vil administratoren ikke kunne lese dem." "Beskriv årsaken…" - "Rapporter rom" + "Rapporter rommet" diff --git a/features/reportroom/impl/src/main/res/values-nl/translations.xml b/features/reportroom/impl/src/main/res/values-nl/translations.xml new file mode 100644 index 0000000000..22252f00d1 --- /dev/null +++ b/features/reportroom/impl/src/main/res/values-nl/translations.xml @@ -0,0 +1,4 @@ + + + "Kamer melden" + diff --git a/features/reportroom/impl/src/main/res/values-ru/translations.xml b/features/reportroom/impl/src/main/res/values-ru/translations.xml index d413f25b44..6ab5600259 100644 --- a/features/reportroom/impl/src/main/res/values-ru/translations.xml +++ b/features/reportroom/impl/src/main/res/values-ru/translations.xml @@ -3,6 +3,6 @@ "Ваш отчет был успешно отправлен, но мы столкнулись с проблемой при попытке покинуть комнату. Пожалуйста, попробуйте еще раз." "Невозможно покинуть комнату" "Сообщите об этой комнате своему администратору. Если сообщения зашифрованы, ваш администратор не сможет их прочитать." - "Опишите причину…" + "Опишите причину жалобы…" "Комната отчетов" diff --git a/features/reportroom/impl/src/main/res/values-sv/translations.xml b/features/reportroom/impl/src/main/res/values-sv/translations.xml index 174949b87d..8c824b9675 100644 --- a/features/reportroom/impl/src/main/res/values-sv/translations.xml +++ b/features/reportroom/impl/src/main/res/values-sv/translations.xml @@ -4,5 +4,5 @@ "Kunde inte lämna rummet" "Anmäl det här rummet till din administratör. Om meddelandena är krypterade kommer din administratör inte att kunna läsa dem." "Beskriv anledningen …" - "Rapportera rum" + "Anmäl rum" diff --git a/features/reportroom/impl/src/main/res/values-uz/translations.xml b/features/reportroom/impl/src/main/res/values-uz/translations.xml new file mode 100644 index 0000000000..e7424ade8f --- /dev/null +++ b/features/reportroom/impl/src/main/res/values-uz/translations.xml @@ -0,0 +1,8 @@ + + + "Hisobotingiz muvaffaqiyatli yuborildi, ammo xonadan chiqishda muammo yuzaga keldi. Iltimos, qaytadan urinib ko‘ring." + "Xonani tark etish imkonsiz" + "Bu xona haqida administratoringizga xabar bering. Agar xabarlar shifrlangan bo‘lsa, administratoringiz ularni o‘qiy olmaydi." + "Xabar berish sababini tushuntiring…" + "Xona ustidan shikoyat qilish" + diff --git a/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/DefaultReportRoomEntryPointTest.kt b/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/DefaultReportRoomEntryPointTest.kt index c9f850062e..2c8354a0f2 100644 --- a/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/DefaultReportRoomEntryPointTest.kt +++ b/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/DefaultReportRoomEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -32,7 +33,11 @@ class DefaultReportRoomEntryPointTest { } ) } - val result = entryPoint.createNode(parentNode, BuildContext.root(null), A_ROOM_ID) + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + roomId = A_ROOM_ID, + ) assertThat(result).isInstanceOf(ReportRoomNode::class.java) assertThat(result.plugins).contains(ReportRoomNode.Inputs(A_ROOM_ID)) } diff --git a/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/DefaultReportRoomTest.kt b/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/DefaultReportRoomTest.kt index fc410ab08f..93cd81056b 100644 --- a/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/DefaultReportRoomTest.kt +++ b/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/DefaultReportRoomTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -19,12 +20,12 @@ import org.junit.Test class DefaultReportRoomTest { private val roomId = A_ROOM_ID - private val successLeaveRoomLambda = lambdaRecorder> { -> Result.success(Unit) } + private val successLeaveRoomLambda = lambdaRecorder> { Result.success(Unit) } private val successReportRoomLambda = lambdaRecorder> { _ -> Result.success(Unit) } private val failureLeaveRoomLambda = - lambdaRecorder> { -> Result.failure(Exception("Leave room error")) } + lambdaRecorder> { Result.failure(Exception("Leave room error")) } private val failureReportRoomLambda = lambdaRecorder> { _ -> Result.failure(Exception("Report room error")) } diff --git a/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomPresenterTest.kt b/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomPresenterTest.kt index eb2e94366d..6bcaeac02d 100644 --- a/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomPresenterTest.kt +++ b/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomPresenterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomViewTest.kt b/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomViewTest.kt index 13dc5eb204..59d9507571 100644 --- a/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomViewTest.kt +++ b/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/ReportRoomViewTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/fakes/FakeReportRoom.kt b/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/fakes/FakeReportRoom.kt index 35d2db3ad8..402ff19a00 100644 --- a/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/fakes/FakeReportRoom.kt +++ b/features/reportroom/impl/src/test/kotlin/io/element/android/features/reportroom/impl/fakes/FakeReportRoom.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/reportroom/test/build.gradle.kts b/features/reportroom/test/build.gradle.kts new file mode 100644 index 0000000000..049ced1ece --- /dev/null +++ b/features/reportroom/test/build.gradle.kts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.reportroom.test" +} + +dependencies { + implementation(projects.features.reportroom.api) + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) + implementation(projects.tests.testutils) +} diff --git a/features/reportroom/test/src/main/kotlin/io/element/android/features/reportroom/test/FakeReportRoomEntryPoint.kt b/features/reportroom/test/src/main/kotlin/io/element/android/features/reportroom/test/FakeReportRoomEntryPoint.kt new file mode 100644 index 0000000000..02e5020db2 --- /dev/null +++ b/features/reportroom/test/src/main/kotlin/io/element/android/features/reportroom/test/FakeReportRoomEntryPoint.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.reportroom.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.reportroom.api.ReportRoomEntryPoint +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeReportRoomEntryPoint : ReportRoomEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + roomId: RoomId, + ): Node { + lambdaError() + } +} diff --git a/features/changeroommemberroles/api/build.gradle.kts b/features/rolesandpermissions/api/build.gradle.kts similarity index 78% rename from features/changeroommemberroles/api/build.gradle.kts rename to features/rolesandpermissions/api/build.gradle.kts index 655bd5683e..ca29972606 100644 --- a/features/changeroommemberroles/api/build.gradle.kts +++ b/features/rolesandpermissions/api/build.gradle.kts @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,7 @@ plugins { } android { - namespace = "io.element.android.features.changeroommemberroles.api" + namespace = "io.element.android.features.rolesandpermissions.api" } dependencies { diff --git a/features/changeroommemberroles/api/src/main/kotlin/io/element/android/features/changeroommemberroes/api/ChangeRoomMemberRolesEntryPoint.kt b/features/rolesandpermissions/api/src/main/kotlin/io/element/android/features/rolesandpermissions/api/ChangeRoomMemberRolesEntryPoint.kt similarity index 56% rename from features/changeroommemberroles/api/src/main/kotlin/io/element/android/features/changeroommemberroes/api/ChangeRoomMemberRolesEntryPoint.kt rename to features/rolesandpermissions/api/src/main/kotlin/io/element/android/features/rolesandpermissions/api/ChangeRoomMemberRolesEntryPoint.kt index b6f7680b38..7cfdfefae0 100644 --- a/features/changeroommemberroles/api/src/main/kotlin/io/element/android/features/changeroommemberroes/api/ChangeRoomMemberRolesEntryPoint.kt +++ b/features/rolesandpermissions/api/src/main/kotlin/io/element/android/features/rolesandpermissions/api/ChangeRoomMemberRolesEntryPoint.kt @@ -1,35 +1,34 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroes.api +package io.element.android.features.rolesandpermissions.api import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import io.element.android.libraries.architecture.FeatureEntryPoint -import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.JoinedRoom fun interface ChangeRoomMemberRolesEntryPoint : FeatureEntryPoint { - fun builder(parentNode: Node, buildContext: BuildContext): Builder - - interface Builder { - fun room(room: JoinedRoom): Builder - fun listType(changeRoomMemberRolesListType: ChangeRoomMemberRolesListType): Builder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + room: JoinedRoom, + listType: ChangeRoomMemberRolesListType, + ): Node interface NodeProxy { val roomId: RoomId - suspend fun waitForRoleChanged() + suspend fun waitForCompletion(): Boolean } } -enum class ChangeRoomMemberRolesListType : NodeInputs { +enum class ChangeRoomMemberRolesListType { SelectNewOwnersWhenLeaving, Admins, Moderators diff --git a/features/rolesandpermissions/api/src/main/kotlin/io/element/android/features/rolesandpermissions/api/RolesAndPermissionsEntryPoint.kt b/features/rolesandpermissions/api/src/main/kotlin/io/element/android/features/rolesandpermissions/api/RolesAndPermissionsEntryPoint.kt new file mode 100644 index 0000000000..bd3cea0439 --- /dev/null +++ b/features/rolesandpermissions/api/src/main/kotlin/io/element/android/features/rolesandpermissions/api/RolesAndPermissionsEntryPoint.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.rolesandpermissions.api + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import io.element.android.libraries.architecture.FeatureEntryPoint + +fun interface RolesAndPermissionsEntryPoint : FeatureEntryPoint { + interface Callback : Plugin { + fun onDone() + } + + fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: Callback, + ): Node +} diff --git a/features/changeroommemberroles/impl/build.gradle.kts b/features/rolesandpermissions/impl/build.gradle.kts similarity index 86% rename from features/changeroommemberroles/impl/build.gradle.kts rename to features/rolesandpermissions/impl/build.gradle.kts index be2bf17979..19820a88d9 100644 --- a/features/changeroommemberroles/impl/build.gradle.kts +++ b/features/rolesandpermissions/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,7 +15,7 @@ plugins { } android { - namespace = "io.element.android.features.changeroommemberroles.impl" + namespace = "io.element.android.features.rolesandpermissions.impl" testOptions { unitTests { @@ -26,7 +27,7 @@ android { setupDependencyInjection() dependencies { - api(projects.features.changeroommemberroles.api) + api(projects.features.rolesandpermissions.api) implementation(projects.appnav) implementation(projects.libraries.architecture) implementation(projects.libraries.core) diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/DefaultRolesAndPermissionsEntryPoint.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/DefaultRolesAndPermissionsEntryPoint.kt new file mode 100644 index 0000000000..5f27365857 --- /dev/null +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/DefaultRolesAndPermissionsEntryPoint.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.rolesandpermissions.impl + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import dev.zacsweers.metro.ContributesBinding +import io.element.android.features.rolesandpermissions.api.RolesAndPermissionsEntryPoint +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.RoomScope + +@ContributesBinding(RoomScope::class) +class DefaultRolesAndPermissionsEntryPoint : RolesAndPermissionsEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: RolesAndPermissionsEntryPoint.Callback, + ): Node { + return parentNode.createNode(buildContext, listOf(callback)) + } +} diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/RolesAndPermissionsFlowNode.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/RolesAndPermissionsFlowNode.kt new file mode 100644 index 0000000000..4bd88071c3 --- /dev/null +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/RolesAndPermissionsFlowNode.kt @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.rolesandpermissions.impl + +import android.os.Parcelable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.coroutineScope +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.pop +import com.bumble.appyx.navmodel.backstack.operation.push +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject +import io.element.android.annotations.ContributesNode +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType +import io.element.android.features.rolesandpermissions.api.RolesAndPermissionsEntryPoint +import io.element.android.features.rolesandpermissions.impl.permissions.ChangeRoomPermissionsNode +import io.element.android.features.rolesandpermissions.impl.roles.ChangeRolesNode +import io.element.android.features.rolesandpermissions.impl.root.RolesAndPermissionsNode +import io.element.android.libraries.architecture.BackstackView +import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.callback +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.designsystem.components.async.AsyncIndicator +import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost +import io.element.android.libraries.designsystem.components.async.AsyncIndicatorState +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.matrix.api.room.JoinedRoom +import io.element.android.libraries.matrix.api.room.powerlevels.canEditRolesAndPermissions +import io.element.android.libraries.matrix.api.room.powerlevels.permissionsFlow +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.parcelize.Parcelize + +@ContributesNode(RoomScope::class) +@AssistedInject +class RolesAndPermissionsFlowNode( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val room: JoinedRoom, +) : BaseFlowNode( + backstack = BackStack( + initialElement = NavTarget.Root, + savedStateMap = buildContext.savedStateMap, + ), + buildContext = buildContext, + plugins = plugins, +) { + sealed interface NavTarget : Parcelable { + @Parcelize + data object Root : NavTarget + + @Parcelize + data object ChangeAdmins : NavTarget + + @Parcelize + data object ChangeModerators : NavTarget + + @Parcelize + data object ChangeRoomPermissions : NavTarget + } + + private val callback: RolesAndPermissionsEntryPoint.Callback = callback() + private val asyncIndicatorState = AsyncIndicatorState() + + override fun onBuilt() { + super.onBuilt() + whenChildAttached { lifecycle, node: ChangeRolesNode -> + lifecycle.coroutineScope.launch { + val changesSaved = node.waitForCompletion() + onChangeComplete(changesSaved) + } + } + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + room.permissionsFlow(false) { perms -> perms.canEditRolesAndPermissions() } + .filter { canEdit -> !canEdit } + .first() + // If the user can no longer edit roles and permissions, exit the flow + callback.onDone() + } + } + } + + private fun onChangeComplete(changesSaved: Boolean) { + backstack.pop() + if (changesSaved) { + asyncIndicatorState.enqueue(durationMs = AsyncIndicator.DURATION_SHORT) { + AsyncIndicator.Custom(text = stringResource(CommonStrings.common_saved_changes)) + } + } + } + + override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { + return when (navTarget) { + is NavTarget.Root -> { + val callback = object : RolesAndPermissionsNode.Callback { + override fun openAdminList() { + backstack.push(NavTarget.ChangeAdmins) + } + + override fun openModeratorList() { + backstack.push(NavTarget.ChangeModerators) + } + + override fun openEditPermissions() { + backstack.push(NavTarget.ChangeRoomPermissions) + } + } + createNode( + buildContext = buildContext, + plugins = listOf(callback), + ) + } + is NavTarget.ChangeAdmins -> { + val inputs = ChangeRolesNode.Inputs(ChangeRoomMemberRolesListType.Admins) + createNode(buildContext = buildContext, plugins = listOf(inputs)) + } + is NavTarget.ChangeModerators -> { + val inputs = ChangeRolesNode.Inputs(ChangeRoomMemberRolesListType.Moderators) + createNode(buildContext = buildContext, plugins = listOf(inputs)) + } + is NavTarget.ChangeRoomPermissions -> { + val callback = object : ChangeRoomPermissionsNode.Callback { + override fun onComplete(changesSaved: Boolean) { + onChangeComplete(changesSaved) + } + } + createNode(buildContext = buildContext, plugins = listOf(callback)) + } + } + } + + @Composable + override fun View(modifier: Modifier) { + Box(modifier = modifier) { + BackstackView() + AsyncIndicatorHost(modifier = Modifier.statusBarsPadding(), asyncIndicatorState) + } + } +} diff --git a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/RoomMemberListDataSource.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/RoomMemberListDataSource.kt similarity index 92% rename from features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/RoomMemberListDataSource.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/RoomMemberListDataSource.kt index a27833122a..b7a26917ea 100644 --- a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/RoomMemberListDataSource.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/RoomMemberListDataSource.kt @@ -1,11 +1,12 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl import dev.zacsweers.metro.Inject import io.element.android.libraries.core.bool.orFalse diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/analytics/AnalyticUtils.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/analytics/AnalyticUtils.kt similarity index 89% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/analytics/AnalyticUtils.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/analytics/AnalyticUtils.kt index 09d57af034..546d54c21a 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/analytics/AnalyticUtils.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/analytics/AnalyticUtils.kt @@ -1,11 +1,12 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.analytics +package io.element.android.features.rolesandpermissions.impl.analytics import im.vector.app.features.analytics.plan.RoomModeration import io.element.android.libraries.matrix.api.room.RoomMember @@ -33,8 +34,8 @@ internal fun AnalyticsService.trackPermissionChangeAnalytics(initial: RoomPowerL if (updated.kick != initial?.kick) { capture(RoomModeration(RoomModeration.Action.ChangePermissionsKickMembers, analyticsMemberRoleForPowerLevel(updated.kick))) } - if (updated.sendEvents != initial?.sendEvents) { - capture(RoomModeration(RoomModeration.Action.ChangePermissionsSendMessages, analyticsMemberRoleForPowerLevel(updated.sendEvents))) + if (updated.eventsDefault != initial?.eventsDefault) { + capture(RoomModeration(RoomModeration.Action.ChangePermissionsSendMessages, analyticsMemberRoleForPowerLevel(updated.eventsDefault))) } if (updated.redactEvents != initial?.redactEvents) { capture(RoomModeration(RoomModeration.Action.ChangePermissionsRedactMessages, analyticsMemberRoleForPowerLevel(updated.redactEvents))) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsEvent.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsEvent.kt similarity index 61% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsEvent.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsEvent.kt index a4c49c7ac3..beb161f900 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsEvent.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsEvent.kt @@ -1,16 +1,15 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions - -import io.element.android.libraries.matrix.api.room.RoomMember +package io.element.android.features.rolesandpermissions.impl.permissions interface ChangeRoomPermissionsEvent { - data class ChangeMinimumRoleForAction(val action: RoomPermissionType, val role: RoomMember.Role) : ChangeRoomPermissionsEvent + data class ChangeMinimumRoleForAction(val action: RoomPermissionType, val role: SelectableRole) : ChangeRoomPermissionsEvent data object Save : ChangeRoomPermissionsEvent data object Exit : ChangeRoomPermissionsEvent data object ResetPendingActions : ChangeRoomPermissionsEvent diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyNode.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsNode.kt similarity index 60% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyNode.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsNode.kt index 15580aab6a..b63ffb9163 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyNode.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsNode.kt @@ -1,40 +1,44 @@ /* - * Copyright 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.securityandprivacy +package io.element.android.features.rolesandpermissions.impl.permissions import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.RoomScope @ContributesNode(RoomScope::class) @AssistedInject -class SecurityAndPrivacyNode( +class ChangeRoomPermissionsNode( @Assisted buildContext: BuildContext, @Assisted plugins: List, - presenterFactory: SecurityAndPrivacyPresenter.Factory, + private val presenter: ChangeRoomPermissionsPresenter, ) : Node(buildContext, plugins = plugins) { - private val navigator = plugins().first() - private val presenter = presenterFactory.create(navigator) + interface Callback : Plugin { + fun onComplete(changesSaved: Boolean) + } + + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { val state = presenter.present() - SecurityAndPrivacyView( + ChangeRoomPermissionsView( + modifier = modifier, state = state, - onBackClick = this::navigateUp, - modifier = modifier + onComplete = callback::onComplete, ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsPresenter.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenter.kt similarity index 57% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsPresenter.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenter.kt index ec90df1d5e..552f1d0ec6 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsPresenter.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenter.kt @@ -1,69 +1,84 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions +package io.element.android.features.rolesandpermissions.impl.permissions import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import dev.zacsweers.metro.Assisted -import dev.zacsweers.metro.AssistedFactory -import dev.zacsweers.metro.AssistedInject -import io.element.android.features.roomdetails.impl.analytics.trackPermissionChangeAnalytics +import dev.zacsweers.metro.Inject +import io.element.android.features.rolesandpermissions.impl.analytics.trackPermissionChangeAnalytics import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.core.coroutine.mapState import io.element.android.libraries.matrix.api.room.JoinedRoom +import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues +import io.element.android.libraries.matrix.ui.model.powerLevelOf import io.element.android.services.analytics.api.AnalyticsService -import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -@AssistedInject +@Inject class ChangeRoomPermissionsPresenter( - @Assisted private val section: ChangeRoomPermissionsSection, private val room: JoinedRoom, private val analyticsService: AnalyticsService, ) : Presenter { companion object { - internal fun itemsForSection(section: ChangeRoomPermissionsSection) = when (section) { - ChangeRoomPermissionsSection.RoomDetails -> persistentListOf( + private fun itemsForSection(section: RoomPermissionsSection) = when (section) { + RoomPermissionsSection.EditDetails -> persistentListOf( RoomPermissionType.ROOM_NAME, RoomPermissionType.ROOM_AVATAR, RoomPermissionType.ROOM_TOPIC, ) - ChangeRoomPermissionsSection.MessagesAndContent -> persistentListOf( + RoomPermissionsSection.MessagesAndContent -> persistentListOf( RoomPermissionType.SEND_EVENTS, RoomPermissionType.REDACT_EVENTS, ) - ChangeRoomPermissionsSection.MembershipModeration -> persistentListOf( + RoomPermissionsSection.ManageMembers -> persistentListOf( RoomPermissionType.INVITE, RoomPermissionType.KICK, RoomPermissionType.BAN, ) + RoomPermissionsSection.ManageSpace -> persistentListOf( + RoomPermissionType.SPACE_MANAGE_ROOMS, + ) } - } - @AssistedFactory - interface Factory { - fun create(section: ChangeRoomPermissionsSection): ChangeRoomPermissionsPresenter + + private fun RoomPermissionsSection.shouldShow(isSpace: Boolean): Boolean { + return when (this) { + RoomPermissionsSection.EditDetails -> true + RoomPermissionsSection.ManageMembers -> true + RoomPermissionsSection.MessagesAndContent -> !isSpace + RoomPermissionsSection.ManageSpace -> isSpace + } + } + + internal fun buildItems(isSpace: Boolean) = + RoomPermissionsSection.entries + .filter { section -> section.shouldShow(isSpace) } + .associateWith { itemsForSection(it) } + .toImmutableMap() } - private val items: ImmutableList = itemsForSection(section) + private val itemsBySection = buildItems(isSpace = room.info().isSpace) private var initialPermissions by mutableStateOf(null) private var currentPermissions by mutableStateOf(null) - private var saveAction by mutableStateOf>(AsyncAction.Uninitialized) - private var confirmExitAction by mutableStateOf>(AsyncAction.Uninitialized) + private var saveAction by mutableStateOf>(AsyncAction.Uninitialized) @Composable override fun present(): ChangeRoomPermissionsState { @@ -77,42 +92,50 @@ class ChangeRoomPermissionsPresenter( derivedStateOf { initialPermissions != currentPermissions } } + val ownPowerLevel by remember { + room.roomInfoFlow.mapState { it.powerLevelOf(room.sessionId) } + }.collectAsState() + fun handleEvent(event: ChangeRoomPermissionsEvent) { when (event) { is ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction -> { + val powerLevel = when (event.role) { + SelectableRole.Admin -> RoomMember.Role.Admin.powerLevel + SelectableRole.Moderator -> RoomMember.Role.Moderator.powerLevel + SelectableRole.Everyone -> RoomMember.Role.User.powerLevel + } currentPermissions = when (event.action) { - RoomPermissionType.BAN -> currentPermissions?.copy(ban = event.role.powerLevel) - RoomPermissionType.INVITE -> currentPermissions?.copy(invite = event.role.powerLevel) - RoomPermissionType.KICK -> currentPermissions?.copy(kick = event.role.powerLevel) - RoomPermissionType.SEND_EVENTS -> currentPermissions?.copy(sendEvents = event.role.powerLevel) - RoomPermissionType.REDACT_EVENTS -> currentPermissions?.copy(redactEvents = event.role.powerLevel) - RoomPermissionType.ROOM_NAME -> currentPermissions?.copy(roomName = event.role.powerLevel) - RoomPermissionType.ROOM_AVATAR -> currentPermissions?.copy(roomAvatar = event.role.powerLevel) - RoomPermissionType.ROOM_TOPIC -> currentPermissions?.copy(roomTopic = event.role.powerLevel) + RoomPermissionType.BAN -> currentPermissions?.copy(ban = powerLevel) + RoomPermissionType.INVITE -> currentPermissions?.copy(invite = powerLevel) + RoomPermissionType.KICK -> currentPermissions?.copy(kick = powerLevel) + RoomPermissionType.SEND_EVENTS -> currentPermissions?.copy(eventsDefault = powerLevel) + RoomPermissionType.REDACT_EVENTS -> currentPermissions?.copy(redactEvents = powerLevel) + RoomPermissionType.ROOM_NAME -> currentPermissions?.copy(roomName = powerLevel) + RoomPermissionType.ROOM_AVATAR -> currentPermissions?.copy(roomAvatar = powerLevel) + RoomPermissionType.ROOM_TOPIC -> currentPermissions?.copy(roomTopic = powerLevel) + RoomPermissionType.SPACE_MANAGE_ROOMS -> currentPermissions?.copy(spaceChild = powerLevel) } } is ChangeRoomPermissionsEvent.Save -> coroutineScope.save() is ChangeRoomPermissionsEvent.Exit -> { - confirmExitAction = if (!hasChanges || confirmExitAction.isConfirming()) { - AsyncAction.Success(Unit) + saveAction = if (!hasChanges || saveAction == AsyncAction.ConfirmingCancellation) { + AsyncAction.Success(false) } else { - AsyncAction.ConfirmingNoParams + AsyncAction.ConfirmingCancellation } } is ChangeRoomPermissionsEvent.ResetPendingActions -> { saveAction = AsyncAction.Uninitialized - confirmExitAction = AsyncAction.Uninitialized } } } return ChangeRoomPermissionsState( - section = section, + ownPowerLevel = ownPowerLevel, currentPermissions = currentPermissions, - items = items, + itemsBySection = itemsBySection, hasChanges = hasChanges, saveAction = saveAction, - confirmExitAction = confirmExitAction, - eventSink = { handleEvent(it) } + eventSink = ::handleEvent, ) } @@ -132,7 +155,7 @@ class ChangeRoomPermissionsPresenter( .onSuccess { analyticsService.trackPermissionChangeAnalytics(initialPermissions, updatedRoomPowerLevels) initialPermissions = currentPermissions - saveAction = AsyncAction.Success(Unit) + saveAction = AsyncAction.Success(true) } .onFailure { saveAction = AsyncAction.Failure(it) diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsState.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsState.kt new file mode 100644 index 0000000000..535f2b4a71 --- /dev/null +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsState.kt @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.rolesandpermissions.impl.permissions + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.ui.res.stringResource +import io.element.android.features.rolesandpermissions.impl.R +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.designsystem.components.preferences.DropdownOption +import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableMap +import kotlinx.collections.immutable.persistentListOf + +data class ChangeRoomPermissionsState( + private val ownPowerLevel: Long, + val currentPermissions: RoomPowerLevelsValues?, + val itemsBySection: ImmutableMap>, + val hasChanges: Boolean, + val saveAction: AsyncAction, + val eventSink: (ChangeRoomPermissionsEvent) -> Unit, +) { + private val ownRole = RoomMember.Role.forPowerLevel(ownPowerLevel) + + // Roles that the user can select based on their own role + val selectableRoles: ImmutableList = when (ownRole) { + is RoomMember.Role.Owner, + RoomMember.Role.Admin -> persistentListOf(SelectableRole.Admin, SelectableRole.Moderator, SelectableRole.Everyone) + RoomMember.Role.Moderator -> persistentListOf(SelectableRole.Moderator, SelectableRole.Everyone) + RoomMember.Role.User -> persistentListOf(SelectableRole.Everyone) + } + + fun selectedRoleForType(type: RoomPermissionType): SelectableRole? { + val powerLevel = currentPowerLevelForType(type = type) ?: return null + return when (RoomMember.Role.forPowerLevel(powerLevel)) { + is RoomMember.Role.Owner, + RoomMember.Role.Admin -> SelectableRole.Admin + RoomMember.Role.Moderator -> SelectableRole.Moderator + RoomMember.Role.User -> SelectableRole.Everyone + } + } + + fun canChangePermission(type: RoomPermissionType): Boolean { + val currentPowerLevel = currentPowerLevelForType(type) ?: return false + return ownPowerLevel >= currentPowerLevel + } + + private fun currentPowerLevelForType(type: RoomPermissionType): Long? { + if (currentPermissions == null) return null + return when (type) { + RoomPermissionType.BAN -> currentPermissions.ban + RoomPermissionType.INVITE -> currentPermissions.invite + RoomPermissionType.KICK -> currentPermissions.kick + RoomPermissionType.SEND_EVENTS -> currentPermissions.eventsDefault + RoomPermissionType.REDACT_EVENTS -> currentPermissions.redactEvents + RoomPermissionType.ROOM_NAME -> currentPermissions.roomName + RoomPermissionType.ROOM_AVATAR -> currentPermissions.roomAvatar + RoomPermissionType.ROOM_TOPIC -> currentPermissions.roomTopic + RoomPermissionType.SPACE_MANAGE_ROOMS -> currentPermissions.spaceChild + } + } +} + +enum class RoomPermissionsSection { + ManageMembers, + EditDetails, + MessagesAndContent, + ManageSpace +} + +enum class SelectableRole : DropdownOption { + Admin { + @Composable + @ReadOnlyComposable + override fun getText(): String = stringResource(R.string.screen_room_member_list_role_administrator) + }, + Moderator { + @Composable + @ReadOnlyComposable + override fun getText(): String = stringResource(R.string.screen_room_member_list_role_moderator) + }, + Everyone { + @Composable + @ReadOnlyComposable + override fun getText(): String = stringResource(R.string.screen_room_change_permissions_everyone) + } +} + +enum class RoomPermissionType { + BAN, + INVITE, + KICK, + SEND_EVENTS, + REDACT_EVENTS, + ROOM_NAME, + ROOM_AVATAR, + ROOM_TOPIC, + SPACE_MANAGE_ROOMS, +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsStateProvider.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsStateProvider.kt similarity index 54% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsStateProvider.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsStateProvider.kt index 07d3455a90..2760272d8a 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsStateProvider.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsStateProvider.kt @@ -1,54 +1,49 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions +package io.element.android.features.rolesandpermissions.impl.permissions import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues -import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableMap class ChangeRoomPermissionsStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.RoomDetails), - aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.MessagesAndContent), - aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.MembershipModeration), - aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true), - aChangeRoomPermissionsState(section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true, saveAction = AsyncAction.Loading), + aChangeRoomPermissionsState(), + aChangeRoomPermissionsState(ownPowerLevel = RoomMember.Role.Moderator.powerLevel), + aChangeRoomPermissionsState(hasChanges = true), + aChangeRoomPermissionsState(hasChanges = true, saveAction = AsyncAction.Loading), aChangeRoomPermissionsState( - section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true, saveAction = AsyncAction.Failure(IllegalStateException("Failed to save changes")) ), - aChangeRoomPermissionsState( - section = ChangeRoomPermissionsSection.RoomDetails, - hasChanges = true, - confirmExitAction = AsyncAction.ConfirmingNoParams, - ), + aChangeRoomPermissionsState(hasChanges = true, saveAction = AsyncAction.ConfirmingCancellation), + aChangeRoomPermissionsState(itemsBySection = ChangeRoomPermissionsPresenter.buildItems(isSpace = true)), ) } internal fun aChangeRoomPermissionsState( - section: ChangeRoomPermissionsSection, + ownPowerLevel: Long = RoomMember.Role.Admin.powerLevel, currentPermissions: RoomPowerLevelsValues = previewPermissions(), - items: List = ChangeRoomPermissionsPresenter.itemsForSection(section), + itemsBySection: Map> = ChangeRoomPermissionsPresenter.buildItems(false), hasChanges: Boolean = false, - saveAction: AsyncAction = AsyncAction.Uninitialized, - confirmExitAction: AsyncAction = AsyncAction.Uninitialized, + saveAction: AsyncAction = AsyncAction.Uninitialized, eventSink: (ChangeRoomPermissionsEvent) -> Unit = {}, ) = ChangeRoomPermissionsState( - section = section, + ownPowerLevel = ownPowerLevel, currentPermissions = currentPermissions, - items = items.toImmutableList(), + itemsBySection = itemsBySection.toImmutableMap(), hasChanges = hasChanges, saveAction = saveAction, - confirmExitAction = confirmExitAction, eventSink = eventSink, ) @@ -60,10 +55,13 @@ private fun previewPermissions(): RoomPowerLevelsValues { ban = RoomMember.Role.User.powerLevel, // MessagesAndContent section redactEvents = RoomMember.Role.Moderator.powerLevel, - sendEvents = RoomMember.Role.Admin.powerLevel, + eventsDefault = RoomMember.Role.Admin.powerLevel, // RoomDetails section roomName = RoomMember.Role.Admin.powerLevel, roomAvatar = RoomMember.Role.Moderator.powerLevel, roomTopic = RoomMember.Role.User.powerLevel, + // SpaceManagement section + spaceChild = RoomMember.Role.Moderator.powerLevel, + stateDefault = RoomMember.Role.Moderator.powerLevel, ) } diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt new file mode 100644 index 0000000000..529df0d50d --- /dev/null +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.rolesandpermissions.impl.permissions + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.features.rolesandpermissions.impl.R +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.designsystem.components.async.AsyncActionView +import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.dialogs.SaveChangesDialog +import io.element.android.libraries.designsystem.components.preferences.PreferenceDropdown +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.ListSectionHeader +import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.TextButton +import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.ui.strings.CommonStrings + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ChangeRoomPermissionsView( + state: ChangeRoomPermissionsState, + onComplete: (Boolean) -> Unit, + modifier: Modifier = Modifier, +) { + BackHandler { + state.eventSink(ChangeRoomPermissionsEvent.Exit) + } + Scaffold( + modifier = modifier, + topBar = { + TopAppBar( + titleStr = stringResource(R.string.screen_room_roles_and_permissions_permissions_header), + navigationIcon = { + BackButton(onClick = { state.eventSink(ChangeRoomPermissionsEvent.Exit) }) + }, + actions = { + TextButton( + text = stringResource(CommonStrings.action_save), + onClick = { state.eventSink(ChangeRoomPermissionsEvent.Save) }, + enabled = state.hasChanges, + ) + } + ) + } + ) { padding -> + LazyColumn( + modifier = Modifier + .padding(padding) + .fillMaxSize() + ) { + state.itemsBySection.onEachIndexed { index, (section, items) -> + item { + ListSectionHeader(titleForSection(section), hasDivider = index > 0) + } + for (permissionType in items) { + item { + PreferenceDropdown( + title = titleForType(permissionType), + selectedOption = state.selectedRoleForType(permissionType), + options = state.selectableRoles, + enabled = state.canChangePermission(permissionType), + onSelectOption = { role -> + state.eventSink( + ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction( + action = permissionType, + role = role + ) + ) + } + ) + } + } + } + } + } + + AsyncActionView( + async = state.saveAction, + onSuccess = { onComplete(it) }, + confirmationDialog = { confirming -> + when (confirming) { + is AsyncAction.ConfirmingCancellation -> { + SaveChangesDialog( + onSaveClick = { state.eventSink(ChangeRoomPermissionsEvent.Save) }, + onDiscardClick = { state.eventSink(ChangeRoomPermissionsEvent.Exit) }, + onDismiss = { state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions) }, + ) + } + } + }, + onErrorDismiss = { state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions) } + ) +} + +@Composable +private fun titleForSection(section: RoomPermissionsSection): String = when (section) { + RoomPermissionsSection.EditDetails -> stringResource(R.string.screen_room_change_permissions_room_details) + RoomPermissionsSection.MessagesAndContent -> stringResource(R.string.screen_room_change_permissions_messages_and_content) + RoomPermissionsSection.ManageMembers -> stringResource(R.string.screen_room_change_permissions_member_moderation) + RoomPermissionsSection.ManageSpace -> stringResource(R.string.screen_room_change_permissions_manage_space) +} + +@Composable +private fun titleForType(type: RoomPermissionType): String = when (type) { + RoomPermissionType.INVITE -> stringResource(R.string.screen_room_change_permissions_invite_people) + RoomPermissionType.KICK -> stringResource(R.string.screen_room_change_permissions_remove_people) + RoomPermissionType.BAN -> stringResource(R.string.screen_room_change_permissions_ban_people) + RoomPermissionType.SEND_EVENTS -> stringResource(R.string.screen_room_change_permissions_send_messages) + RoomPermissionType.REDACT_EVENTS -> stringResource(R.string.screen_room_change_permissions_delete_messages) + RoomPermissionType.ROOM_NAME -> stringResource(R.string.screen_room_change_permissions_room_name) + RoomPermissionType.ROOM_AVATAR -> stringResource(R.string.screen_room_change_permissions_room_avatar) + RoomPermissionType.ROOM_TOPIC -> stringResource(R.string.screen_room_change_permissions_room_topic) + RoomPermissionType.SPACE_MANAGE_ROOMS -> stringResource(R.string.screen_room_change_permissions_manage_space_rooms) +} + +@PreviewsDayNight +@Composable +internal fun ChangeRoomPermissionsViewPreview(@PreviewParameter(ChangeRoomPermissionsStateProvider::class) state: ChangeRoomPermissionsState) { + ElementPreview { + ChangeRoomPermissionsView( + state = state, + onComplete = {}, + ) + } +} diff --git a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesEvent.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesEvent.kt similarity index 68% rename from features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesEvent.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesEvent.kt index ab8dbc8f22..2867273e80 100644 --- a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesEvent.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesEvent.kt @@ -1,11 +1,12 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import io.element.android.libraries.matrix.api.user.MatrixUser @@ -15,7 +16,5 @@ sealed interface ChangeRolesEvent { data class UserSelectionToggled(val matrixUser: MatrixUser) : ChangeRolesEvent data object Save : ChangeRolesEvent data object Exit : ChangeRolesEvent - data object CancelExit : ChangeRolesEvent - data object ClearError : ChangeRolesEvent - data object CancelSave : ChangeRolesEvent + data object CloseDialog : ChangeRolesEvent } diff --git a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesNode.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesNode.kt similarity index 81% rename from features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesNode.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesNode.kt index 1b9c790b25..8baa5f589b 100644 --- a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesNode.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesNode.kt @@ -1,11 +1,12 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -17,10 +18,11 @@ import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode -import io.element.android.features.changeroommemberroes.api.ChangeRoomMemberRolesListType +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.appyx.launchMolecule import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.room.RoomMember import kotlinx.coroutines.flow.first @@ -40,17 +42,17 @@ class ChangeRolesNode( private val presenter = presenterFactory.create(inputs.listType.toRoomMemberRole()) private val stateFlow = launchMolecule { presenter.present() } - suspend fun waitForRoleChanged() { - stateFlow.first { it.savingState.isSuccess() } + suspend fun waitForCompletion(): Boolean { + val successState = stateFlow.first { it.savingState.isSuccess() } + return successState.savingState.dataOrNull().orFalse() } @Composable override fun View(modifier: Modifier) { val state by stateFlow.collectAsState() ChangeRolesView( - modifier = modifier, state = state, - navigateUp = this::navigateUp, + modifier = modifier, ) } } diff --git a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesPresenter.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenter.kt similarity index 56% rename from features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesPresenter.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenter.kt index 632d93f0e0..3989a76df3 100644 --- a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesPresenter.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenter.kt @@ -1,31 +1,34 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedInject import im.vector.app.features.analytics.plan.RoomModeration +import io.element.android.features.rolesandpermissions.impl.RoomMemberListDataSource import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.designsystem.theme.components.SearchBarResultState +import io.element.android.libraries.di.annotations.RoomCoroutineScope import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.RoomMember @@ -33,6 +36,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange import io.element.android.libraries.matrix.api.room.powerlevels.usersWithRole import io.element.android.libraries.matrix.api.room.toMatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.ui.model.powerLevelOf import io.element.android.libraries.matrix.ui.model.roleOf import io.element.android.libraries.matrix.ui.room.PowerLevelRoomMemberComparator import io.element.android.services.analytics.api.AnalyticsService @@ -40,6 +44,8 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -49,18 +55,19 @@ import kotlinx.coroutines.launch class ChangeRolesPresenter( @Assisted private val role: RoomMember.Role, private val room: JoinedRoom, - private val dispatchers: CoroutineDispatchers, + private val dataSource: RoomMemberListDataSource, private val analyticsService: AnalyticsService, + @RoomCoroutineScope private val roomCoroutineScope: CoroutineScope, ) : Presenter { @AssistedFactory fun interface Factory { fun create(role: RoomMember.Role): ChangeRolesPresenter } + private val powerLevelRoomMemberComparator = PowerLevelRoomMemberComparator() + @Composable override fun present(): ChangeRolesState { - val coroutineScope = rememberCoroutineScope() - val dataSource = remember { RoomMemberListDataSource(room, dispatchers) } var query by rememberSaveable { mutableStateOf(null) } var searchActive by rememberSaveable { mutableStateOf(false) } var searchResults by remember { @@ -69,20 +76,30 @@ class ChangeRolesPresenter( val selectedUsers = remember { mutableStateOf>(persistentListOf()) } - val exitState: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } - val saveState: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } + val saveState: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val usersWithRole = produceState>(initialValue = persistentListOf()) { - room.usersWithRole(role).map { members -> members.map { it.toMatrixUser() } } - .onEach { users -> - val previous = value - value = users.toImmutableList() - // Users who were selected but didn't have the role, so their role change was pending - val toAdd = selectedUsers.value.filter { user -> users.none { it.userId == user.userId } && previous.none { it.userId == user.userId } } - // Users who no longer have the role - val toRemove = previous.filter { user -> users.none { it.userId == user.userId } }.toSet() - selectedUsers.value = (users + toAdd - toRemove).toImmutableList() - } - .launchIn(this) + // If the role is admin, we need to include the owners as well since they implicitly have admin role + val owners = if (role == RoomMember.Role.Admin) { + room.usersWithRole { role -> role is RoomMember.Role.Owner } + } else { + flowOf(persistentListOf()) + } + combine( + owners, + room.usersWithRole { it == role }, + ) { owners, users -> + owners + users + }.map { members -> members.map { it.toMatrixUser() } } + .onEach { users -> + val previous = value + value = users.toImmutableList() + // Users who were selected but didn't have the role, so their role change was pending + val toAdd = selectedUsers.value.filter { user -> users.none { it.userId == user.userId } && previous.none { it.userId == user.userId } } + // Users who no longer have the role + val toRemove = previous.filter { user -> users.none { it.userId == user.userId } }.toSet() + selectedUsers.value = (users + toAdd - toRemove).toImmutableList() + } + .launchIn(this) } val roomMemberState by room.membersStateFlow.collectAsState() @@ -100,14 +117,18 @@ class ChangeRolesPresenter( } } - val hasPendingChanges = usersWithRole.value != selectedUsers.value + val hasPendingChanges by remember { + derivedStateOf { + usersWithRole.value.toSet() != selectedUsers.value.toSet() + } + } val roomInfo by room.roomInfoFlow.collectAsState() fun canChangeMemberRole(userId: UserId): Boolean { - // This is used to group the - val currentUserRole = roomInfo.roleOf(room.sessionId) - val otherUserRole = roomInfo.roleOf(userId) - return currentUserRole.powerLevel > otherUserRole.powerLevel + val currentUserPowerLevel = roomInfo.powerLevelOf(room.sessionId) + val otherUserPowerLevel = roomInfo.powerLevelOf(userId) + return currentUserPowerLevel > otherUserPowerLevel && + currentUserPowerLevel >= role.powerLevel } fun handleEvent(event: ChangeRolesEvent) { @@ -131,38 +152,36 @@ class ChangeRolesPresenter( is ChangeRolesEvent.Save -> { val currentUserIsAdmin = roomInfo.roleOf(room.sessionId) == RoomMember.Role.Admin val isModifyingAdmins = role == RoomMember.Role.Admin - val hasChanges = selectedUsers != usersWithRole val isConfirming = saveState.value.isConfirming() val modifyingOwners = role is RoomMember.Role.Owner - - val needsConfirmation = (modifyingOwners || currentUserIsAdmin && isModifyingAdmins) && hasChanges && !isConfirming - + val confirmationValue = if (hasPendingChanges && !isConfirming) { + when { + modifyingOwners -> ConfirmingModifyingOwners + currentUserIsAdmin && isModifyingAdmins -> ConfirmingModifyingAdmins + else -> null + } + } else { + null + } when { - needsConfirmation -> { - // Confirm modifying users - saveState.value = AsyncAction.ConfirmingNoParams + confirmationValue != null -> { + saveState.value = confirmationValue } !saveState.value.isLoading() -> { - coroutineScope.save(usersWithRole.value, selectedUsers, saveState) + roomCoroutineScope.save(usersWithRole.value, selectedUsers, saveState) } } } - is ChangeRolesEvent.ClearError -> { - saveState.value = AsyncAction.Uninitialized - } is ChangeRolesEvent.Exit -> { - exitState.value = if (exitState.value.isUninitialized() && hasPendingChanges) { + saveState.value = if (saveState.value.isUninitialized() && hasPendingChanges) { // Has pending changes, confirm exit - AsyncAction.ConfirmingNoParams + AsyncAction.ConfirmingCancellation } else { // No pending changes, exit immediately - AsyncAction.Success(Unit) + AsyncAction.Success(false) } } - is ChangeRolesEvent.CancelExit -> { - exitState.value = AsyncAction.Uninitialized - } - is ChangeRolesEvent.CancelSave -> { + is ChangeRolesEvent.CloseDialog -> { saveState.value = AsyncAction.Uninitialized } } @@ -174,7 +193,6 @@ class ChangeRolesPresenter( searchResults = searchResults, selectedUsers = selectedUsers.value, hasPendingChanges = hasPendingChanges, - exitState = exitState.value, savingState = saveState.value, canChangeMemberRole = ::canChangeMemberRole, eventSink = ::handleEvent, @@ -182,55 +200,35 @@ class ChangeRolesPresenter( } private fun List.groupedByRole(): MembersByRole { - val groupedMembers = MembersByRole(this) - return MembersByRole( - owners = groupedMembers.owners.sorted(), - admins = groupedMembers.admins.sorted(), - moderators = groupedMembers.moderators.sorted(), - members = groupedMembers.members.sorted(), - ) - } - - private fun Iterable.sorted(): ImmutableList { - return sortedWith(PowerLevelRoomMemberComparator()).toImmutableList() + return MembersByRole(this, powerLevelRoomMemberComparator) } private fun CoroutineScope.save( usersWithRole: ImmutableList, selectedUsers: MutableState>, - saveState: MutableState>, + saveState: MutableState>, ) = launch { - saveState.value = AsyncAction.Loading - - val toAdd = selectedUsers.value - usersWithRole - val toRemove = usersWithRole - selectedUsers.value - - val changes: List = buildList { - for (selectedUser in toAdd) { - analyticsService.capture(RoomModeration(RoomModeration.Action.ChangeMemberRole, role.toAnalyticsMemberRole())) - add(UserRoleChange(selectedUser.userId, role)) - } - for (selectedUser in toRemove) { - analyticsService.capture(RoomModeration(RoomModeration.Action.ChangeMemberRole, RoomModeration.Role.User)) - add(UserRoleChange(selectedUser.userId, RoomMember.Role.User)) + runUpdatingState(saveState) { + val toAdd = selectedUsers.value - usersWithRole + val toRemove = usersWithRole - selectedUsers.value + val changes: List = buildList { + for (selectedUser in toAdd) { + analyticsService.capture(RoomModeration(RoomModeration.Action.ChangeMemberRole, role.toAnalyticsMemberRole())) + add(UserRoleChange(selectedUser.userId, role)) + } + for (selectedUser in toRemove) { + analyticsService.capture(RoomModeration(RoomModeration.Action.ChangeMemberRole, RoomModeration.Role.User)) + add(UserRoleChange(selectedUser.userId, RoomMember.Role.User)) + } } + room.updateUsersRoles(changes).map { true } } + } - room.updateUsersRoles(changes) - .onFailure { - saveState.value = AsyncAction.Failure(it) - } - .onSuccess { - saveState.value = AsyncAction.Success(Unit) - // Asynchronously reload the room members - launch { room.updateMembers() } - } + internal fun RoomMember.Role.toAnalyticsMemberRole(): RoomModeration.Role = when (this) { + is RoomMember.Role.Owner -> RoomModeration.Role.Administrator // TODO - distinguish creator from admin + RoomMember.Role.Admin -> RoomModeration.Role.Administrator + RoomMember.Role.Moderator -> RoomModeration.Role.Moderator + RoomMember.Role.User -> RoomModeration.Role.User } } - -internal fun RoomMember.Role.toAnalyticsMemberRole(): RoomModeration.Role = when (this) { - is RoomMember.Role.Owner -> RoomModeration.Role.Administrator // TODO - distinguish creator from admin - RoomMember.Role.Admin -> RoomModeration.Role.Administrator - RoomMember.Role.Moderator -> RoomModeration.Role.Moderator - RoomMember.Role.User -> RoomModeration.Role.User -} diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesState.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesState.kt new file mode 100644 index 0000000000..71fef01fa7 --- /dev/null +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesState.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.rolesandpermissions.impl.roles + +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.designsystem.theme.components.SearchBarResultState +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.user.MatrixUser +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList + +data class ChangeRolesState( + val role: RoomMember.Role, + val query: String?, + val isSearchActive: Boolean, + val searchResults: SearchBarResultState, + val selectedUsers: ImmutableList, + val hasPendingChanges: Boolean, + val savingState: AsyncAction, + val canChangeMemberRole: (UserId) -> Boolean, + val eventSink: (ChangeRolesEvent) -> Unit, +) + +data class MembersByRole( + val owners: ImmutableList = persistentListOf(), + val admins: ImmutableList = persistentListOf(), + val moderators: ImmutableList = persistentListOf(), + val members: ImmutableList = persistentListOf(), +) { + constructor(members: List, comparator: Comparator) : this( + owners = members.filterAndSort(comparator) { it.role is RoomMember.Role.Owner }, + admins = members.filterAndSort(comparator) { it.role == RoomMember.Role.Admin }, + moderators = members.filterAndSort(comparator) { it.role == RoomMember.Role.Moderator }, + members = members.filterAndSort(comparator) { it.role == RoomMember.Role.User }, + ) + + fun isEmpty() = owners.isEmpty() && admins.isEmpty() && moderators.isEmpty() && members.isEmpty() +} + +private fun Iterable.filterAndSort( + comparator: Comparator, + predicate: (RoomMember) -> Boolean, +): ImmutableList { + return filter(predicate).sortedWith(comparator).toImmutableList() +} diff --git a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesStateProvider.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesStateProvider.kt similarity index 77% rename from features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesStateProvider.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesStateProvider.kt index 2041c0f447..654259c02a 100644 --- a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesStateProvider.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesStateProvider.kt @@ -1,11 +1,12 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncAction @@ -16,6 +17,7 @@ import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.ui.components.aMatrixUser import io.element.android.libraries.matrix.ui.components.aMatrixUserList +import io.element.android.libraries.matrix.ui.room.PowerLevelRoomMemberComparator import io.element.android.libraries.previewutils.room.aRoomMember import io.element.android.libraries.previewutils.room.aRoomMemberList import kotlinx.collections.immutable.ImmutableList @@ -35,16 +37,23 @@ class ChangeRolesStateProvider : PreviewParameterProvider { aChangeRolesStateWithSelectedUsers().copy( query = "Alice", isSearchActive = true, - searchResults = SearchBarResultState.Results(MembersByRole(aRoomMemberList().take(1).toImmutableList())), + searchResults = SearchBarResultState.Results( + MembersByRole( + members = aRoomMemberList().take(1), + comparator = PowerLevelRoomMemberComparator(), + ) + ), selectedUsers = aMatrixUserList().take(1).toImmutableList(), ), - aChangeRolesStateWithSelectedUsers().copy(exitState = AsyncAction.ConfirmingNoParams), - aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.ConfirmingNoParams), + aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.ConfirmingCancellation), + aChangeRolesStateWithSelectedUsers().copy(savingState = ConfirmingModifyingAdmins), aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Loading), - aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Success(Unit)), + aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Success(true)), aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Failure(Exception("boom"))), - aChangeRolesStateWithOwners(), - aChangeRolesStateWithOwners().copy(role = RoomMember.Role.Owner(isCreator = false)), + aChangeRolesStateWithOwners(role = RoomMember.Role.Admin), + aChangeRolesStateWithOwners(role = RoomMember.Role.Owner(isCreator = false)), + aChangeRolesStateWithOwners(role = RoomMember.Role.Owner(isCreator = false)) + .copy(savingState = ConfirmingModifyingOwners), ) } @@ -55,8 +64,7 @@ internal fun aChangeRolesState( searchResults: SearchBarResultState = SearchBarResultState.NoResultsFound(), selectedUsers: ImmutableList = persistentListOf(), hasPendingChanges: Boolean = false, - exitState: AsyncAction = AsyncAction.Uninitialized, - savingState: AsyncAction = AsyncAction.Uninitialized, + savingState: AsyncAction = AsyncAction.Uninitialized, canRemoveMember: (UserId) -> Boolean = { true }, eventSink: (ChangeRolesEvent) -> Unit = {}, ) = ChangeRolesState( @@ -66,7 +74,6 @@ internal fun aChangeRolesState( searchResults = searchResults, selectedUsers = selectedUsers, hasPendingChanges = hasPendingChanges, - exitState = exitState, savingState = savingState, canChangeMemberRole = canRemoveMember, eventSink = eventSink, @@ -82,15 +89,23 @@ internal fun aChangeRolesStateWithSelectedUsers() = aChangeRolesState( } else { roomMember } - } + }, + comparator = PowerLevelRoomMemberComparator(), ) ), hasPendingChanges = true, canRemoveMember = { it != UserId("@alice:server.org") }, ) -internal fun aChangeRolesStateWithOwners() = aChangeRolesState( - role = RoomMember.Role.Admin, +internal fun aChangeRolesStateWithOwners( + role: RoomMember.Role = RoomMember.Role.Admin, + selectedUsers: List = listOf( + aMatrixUser(id = "@alice:server.org", displayName = "Alice"), + aMatrixUser(id = "@bob:server.org", displayName = "Bob"), + aMatrixUser(id = "@carol:server.org", displayName = "Carol"), + ), +) = aChangeRolesState( + role = role, searchResults = SearchBarResultState.Results( MembersByRole( members = persistentListOf( @@ -114,7 +129,8 @@ internal fun aChangeRolesStateWithOwners() = aChangeRolesState( displayName = "David", role = RoomMember.Role.User, ), - ) + ), + comparator = PowerLevelRoomMemberComparator(), ), ), canRemoveMember = { userId -> @@ -126,9 +142,5 @@ internal fun aChangeRolesStateWithOwners() = aChangeRolesState( else -> false } }, - selectedUsers = persistentListOf( - aMatrixUser(id = "@alice:server.org", displayName = "Alice"), - aMatrixUser(id = "@bob:server.org", displayName = "Bob"), - aMatrixUser(id = "@carol:server.org", displayName = "Carol"), - ) + selectedUsers = selectedUsers.toImmutableList(), ) diff --git a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesView.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesView.kt similarity index 88% rename from features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesView.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesView.kt index c6b70a82f4..20abc70bb7 100644 --- a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesView.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesView.kt @@ -1,11 +1,12 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility @@ -29,9 +30,6 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -40,10 +38,9 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme +import io.element.android.features.rolesandpermissions.impl.R import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.async.AsyncActionView -import io.element.android.libraries.designsystem.components.async.AsyncIndicator import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost import io.element.android.libraries.designsystem.components.async.rememberAsyncIndicatorState import io.element.android.libraries.designsystem.components.avatar.Avatar @@ -52,7 +49,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarType import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog -import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog +import io.element.android.libraries.designsystem.components.dialogs.SaveChangesDialog import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Checkbox @@ -79,10 +76,8 @@ import kotlinx.collections.immutable.ImmutableList @Composable fun ChangeRolesView( state: ChangeRolesState, - navigateUp: () -> Unit, modifier: Modifier = Modifier, ) { - val latestNavigateUp by rememberUpdatedState(newValue = navigateUp) BackHandler(enabled = !state.isSearchActive) { state.eventSink(ChangeRolesEvent.Exit) } @@ -148,7 +143,7 @@ fun ChangeRolesView( SearchResultsList( currentRole = state.role, lazyListState = lazyListState, - searchResults = (state.searchResults as? SearchBarResultState.Results)?.results ?: MembersByRole(emptyList()), + searchResults = (state.searchResults as? SearchBarResultState.Results)?.results ?: MembersByRole(), selectedUsers = state.selectedUsers, canRemoveMember = state.canChangeMemberRole, onToggleSelection = { state.eventSink(ChangeRolesEvent.UserSelectionToggled(it.toMatrixUser())) }, @@ -170,63 +165,45 @@ fun ChangeRolesView( val asyncIndicatorState = rememberAsyncIndicatorState() AsyncIndicatorHost(modifier = Modifier.statusBarsPadding(), asyncIndicatorState) - AsyncActionView( - async = state.exitState, - onSuccess = { latestNavigateUp() }, - confirmationDialog = { - ConfirmationDialog( - title = stringResource(CommonStrings.dialog_unsaved_changes_title), - content = stringResource(CommonStrings.dialog_unsaved_changes_description_android), - onSubmitClick = { state.eventSink(ChangeRolesEvent.Exit) }, - onDismiss = { state.eventSink(ChangeRolesEvent.CancelExit) } - ) - }, - onErrorDismiss = { /* Cannot happen */ }, - ) - - when (state.savingState) { - is AsyncAction.Confirming -> { - when (state.role) { - is RoomMember.Role.Owner -> { + async = state.savingState, + onSuccess = {}, + confirmationDialog = { confirming -> + when (confirming) { + is AsyncAction.ConfirmingCancellation -> { + SaveChangesDialog( + onSaveClick = { state.eventSink(ChangeRolesEvent.Save) }, + onDiscardClick = { state.eventSink(ChangeRolesEvent.Exit) }, + onDismiss = { state.eventSink(ChangeRolesEvent.CloseDialog) }, + ) + } + is ConfirmingModifyingOwners -> { ConfirmationDialog( title = stringResource(R.string.screen_room_change_role_confirm_change_owners_title), content = stringResource(R.string.screen_room_change_role_confirm_change_owners_description), submitText = stringResource(CommonStrings.action_continue), onSubmitClick = { state.eventSink(ChangeRolesEvent.Save) }, - onDismiss = { state.eventSink(ChangeRolesEvent.ClearError) }, + onDismiss = { state.eventSink(ChangeRolesEvent.CloseDialog) }, destructiveSubmit = true, ) } - is RoomMember.Role.Admin -> { + is ConfirmingModifyingAdmins -> { ConfirmationDialog( title = stringResource(R.string.screen_room_change_role_confirm_add_admin_title), content = stringResource(R.string.screen_room_change_role_confirm_add_admin_description), onSubmitClick = { state.eventSink(ChangeRolesEvent.Save) }, - onDismiss = { state.eventSink(ChangeRolesEvent.ClearError) } + onDismiss = { state.eventSink(ChangeRolesEvent.CloseDialog) } ) } - else -> Unit // No confirmation needed for Moderator or User roles } - } - is AsyncAction.Loading -> { - ProgressDialog() - } - is AsyncAction.Failure -> { - ErrorDialog( - content = stringResource(CommonStrings.error_unknown), - onSubmit = { state.eventSink(ChangeRolesEvent.ClearError) } - ) - } - is AsyncAction.Success -> { - LaunchedEffect(state.savingState) { - asyncIndicatorState.enqueue(durationMs = AsyncIndicator.DURATION_SHORT) { - AsyncIndicator.Custom(text = stringResource(CommonStrings.common_saved_changes)) - } - } - } - else -> Unit - } + }, + errorMessage = { + stringResource(CommonStrings.error_unknown) + }, + onErrorDismiss = { + state.eventSink(ChangeRolesEvent.CloseDialog) + }, + ) } } @@ -394,7 +371,7 @@ private fun MemberRow( if (isPending) { Text( modifier = Modifier.padding(start = 8.dp), - text = stringResource(id = R.string.screen_room_member_list_pending_header_title), + text = stringResource(id = R.string.screen_room_member_list_pending_status), style = ElementTheme.typography.fontBodySmRegular.copy(fontStyle = FontStyle.Italic), color = ElementTheme.colors.textSecondary ) @@ -420,8 +397,7 @@ private fun MemberRow( internal fun ChangeRolesViewPreview(@PreviewParameter(ChangeRolesStateProvider::class) state: ChangeRolesState) { ElementPreview { ChangeRolesView( - state = state, - navigateUp = {}, + state = state ) } } diff --git a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRoomMemberRolesRootNode.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRoomMemberRolesRootNode.kt similarity index 85% rename from features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRoomMemberRolesRootNode.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRoomMemberRolesRootNode.kt index 2c3f77f208..458e8fda23 100644 --- a/features/changeroommemberroles/impl/src/main/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRoomMemberRolesRootNode.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRoomMemberRolesRootNode.kt @@ -1,11 +1,12 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import android.os.Parcelable import androidx.compose.runtime.Composable @@ -20,8 +21,8 @@ import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.appnav.di.RoomGraphFactory -import io.element.android.features.changeroommemberroes.api.ChangeRoomMemberRolesEntryPoint -import io.element.android.features.changeroommemberroes.api.ChangeRoomMemberRolesListType +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.inputs @@ -70,7 +71,7 @@ class ChangeRoomMemberRolesRootNode( override val roomId: RoomId = inputs.joinedRoom.roomId - override suspend fun waitForRoleChanged() { - waitForChildAttached().waitForRoleChanged() + override suspend fun waitForCompletion(): Boolean { + return waitForChildAttached().waitForCompletion() } } diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ConfirmingModifyingAdmins.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ConfirmingModifyingAdmins.kt new file mode 100644 index 0000000000..10d46abbc3 --- /dev/null +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ConfirmingModifyingAdmins.kt @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.rolesandpermissions.impl.roles + +import io.element.android.libraries.architecture.AsyncAction + +data object ConfirmingModifyingAdmins : AsyncAction.Confirming diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ConfirmingModifyingOwners.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ConfirmingModifyingOwners.kt new file mode 100644 index 0000000000..9799f68408 --- /dev/null +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ConfirmingModifyingOwners.kt @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.rolesandpermissions.impl.roles + +import io.element.android.libraries.architecture.AsyncAction + +data object ConfirmingModifyingOwners : AsyncAction.Confirming diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/DefaultChangeRoomMemberRolesEntyPoint.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/DefaultChangeRoomMemberRolesEntyPoint.kt new file mode 100644 index 0000000000..a56c0d59e7 --- /dev/null +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/DefaultChangeRoomMemberRolesEntyPoint.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.rolesandpermissions.impl.roles + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import dev.zacsweers.metro.ContributesBinding +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.room.JoinedRoom + +@ContributesBinding(SessionScope::class) +class DefaultChangeRoomMemberRolesEntyPoint : ChangeRoomMemberRolesEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + room: JoinedRoom, + listType: ChangeRoomMemberRolesListType, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf( + ChangeRoomMemberRolesRootNode.Inputs(joinedRoom = room, listType = listType), + ) + ) + } +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsEvents.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsEvents.kt similarity index 76% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsEvents.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsEvents.kt index 8b98f78d97..69fad02c25 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsEvents.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsEvents.kt @@ -1,11 +1,12 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions +package io.element.android.features.rolesandpermissions.impl.root import io.element.android.libraries.matrix.api.room.RoomMember diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsNode.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsNode.kt similarity index 52% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsNode.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsNode.kt index a430b0f6a5..da69ee52a9 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsNode.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsNode.kt @@ -1,32 +1,24 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions +package io.element.android.features.rolesandpermissions.impl.root import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier -import androidx.lifecycle.lifecycleScope import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.RoomScope -import io.element.android.libraries.matrix.api.room.BaseRoom -import io.element.android.libraries.matrix.api.room.RoomMember -import io.element.android.libraries.matrix.ui.model.roleOf -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.take -import kotlinx.coroutines.launch @ContributesNode(RoomScope::class) @AssistedInject @@ -34,18 +26,16 @@ class RolesAndPermissionsNode( @Assisted buildContext: BuildContext, @Assisted plugins: List, private val presenter: RolesAndPermissionsPresenter, - private val room: BaseRoom, ) : Node(buildContext, plugins = plugins), RolesAndPermissionsNavigator { interface Callback : Plugin, RolesAndPermissionsNavigator { override fun openAdminList() override fun openModeratorList() - override fun openEditRoomDetailsPermissions() - override fun openMessagesAndContentPermissions() - override fun openModerationPermissions() + override fun openEditPermissions() + override fun onBackClick() {} } - private val callback = plugins().first() + private val callback: Callback = callback() @Stable private val navigator = object : RolesAndPermissionsNavigator by callback { @@ -54,22 +44,6 @@ class RolesAndPermissionsNode( } } - override fun onBuilt() { - super.onBuilt() - - // If the user is not an admin anymore, exit this section since they won't have permissions to use it - lifecycleScope.launch { - room.roomInfoFlow - .filter { info -> - val role = info.roleOf(room.sessionId) - role != RoomMember.Role.Admin && role !is RoomMember.Role.Owner - } - .take(1) - .onEach { navigateUp() } - .collect() - } - } - @Composable override fun View(modifier: Modifier) { val state = presenter.present() @@ -85,7 +59,5 @@ interface RolesAndPermissionsNavigator { fun onBackClick() {} fun openAdminList() {} fun openModeratorList() {} - fun openEditRoomDetailsPermissions() {} - fun openMessagesAndContentPermissions() {} - fun openModerationPermissions() {} + fun openEditPermissions() {} } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsPresenter.kt similarity index 69% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsPresenter.kt index 2ad4c84028..dd3a59b99e 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsPresenter.kt @@ -1,11 +1,12 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions +package io.element.android.features.rolesandpermissions.impl.root import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState @@ -21,14 +22,13 @@ import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.JoinedRoom -import io.element.android.libraries.matrix.api.room.RoomInfo import io.element.android.libraries.matrix.api.room.RoomMember -import io.element.android.libraries.matrix.api.room.activeRoomMembers import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange +import io.element.android.libraries.matrix.api.room.powerlevels.userCountWithRole import io.element.android.libraries.matrix.ui.model.roleOf import io.element.android.services.analytics.api.AnalyticsService +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -42,33 +42,24 @@ class RolesAndPermissionsPresenter( override fun present(): RolesAndPermissionsState { val coroutineScope = rememberCoroutineScope() val roomInfo by room.roomInfoFlow.collectAsState() - val roomMembers by room.membersStateFlow.collectAsState() - // Get the list of active room members (joined or invited), in order to filter members present in the power - // level state Event. - val activeRoomMemberIds by remember { - derivedStateOf { - roomMembers.activeRoomMembers().map { it.userId } - } - } val moderatorCount by remember { - derivedStateOf { - roomInfo.userCountWithRole(activeRoomMemberIds, RoomMember.Role.Moderator) - } - } + room.userCountWithRole { role -> role is RoomMember.Role.Moderator } + }.collectAsState(null) + val adminCount by remember { + room.userCountWithRole { role -> role is RoomMember.Role.Admin || role is RoomMember.Role.Owner } + }.collectAsState(null) + + val availableDemoteActions by remember { derivedStateOf { - val admins = roomInfo.userCountWithRole(activeRoomMemberIds, RoomMember.Role.Admin) - val ownersCount = if (roomInfo.privilegedCreatorRole) { - val superAdmins = roomInfo.userCountWithRole(activeRoomMemberIds, RoomMember.Role.Owner(isCreator = false)) - val creators = roomInfo.userCountWithRole(activeRoomMemberIds, RoomMember.Role.Owner(isCreator = true)) - superAdmins + creators - } else { - 0 + val currentRole = roomInfo.roleOf(room.sessionId) + when (currentRole) { + is RoomMember.Role.Admin -> persistentListOf(SelfDemoteAction.ToModerator, SelfDemoteAction.ToMember) + is RoomMember.Role.Moderator -> persistentListOf(SelfDemoteAction.ToMember) + else -> persistentListOf() } - admins + ownersCount } } - val canDemoteSelf = remember { derivedStateOf { roomInfo.roleOf(room.sessionId) !is RoomMember.Role.Owner } } val changeOwnRoleAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } val resetPermissionsAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } @@ -97,10 +88,10 @@ class RolesAndPermissionsPresenter( roomSupportsOwnerRole = roomInfo.privilegedCreatorRole, adminCount = adminCount, moderatorCount = moderatorCount, - canDemoteSelf = canDemoteSelf.value, + availableSelfDemoteActions = availableDemoteActions, changeOwnRoleAction = changeOwnRoleAction.value, resetPermissionsAction = resetPermissionsAction.value, - eventSink = { handleEvent(it) }, + eventSink = ::handleEvent, ) } @@ -121,8 +112,4 @@ class RolesAndPermissionsPresenter( room.resetPowerLevels() } } - - private fun RoomInfo.userCountWithRole(userIds: List, role: RoomMember.Role): Int { - return usersWithRole(role).filter { it in userIds }.size - } } diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsState.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsState.kt new file mode 100644 index 0000000000..626ad3b699 --- /dev/null +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsState.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.rolesandpermissions.impl.root + +import io.element.android.features.rolesandpermissions.impl.R +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.room.RoomMember +import kotlinx.collections.immutable.ImmutableList + +data class RolesAndPermissionsState( + val roomSupportsOwnerRole: Boolean, + val adminCount: Int?, + val moderatorCount: Int?, + val availableSelfDemoteActions: ImmutableList, + val changeOwnRoleAction: AsyncAction, + val resetPermissionsAction: AsyncAction, + val eventSink: (RolesAndPermissionsEvents) -> Unit, +) { + val canSelfDemote = availableSelfDemoteActions.isNotEmpty() +} + +enum class SelfDemoteAction(val role: RoomMember.Role, val titleRes: Int) { + ToModerator(RoomMember.Role.Moderator, R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator), + ToMember(RoomMember.Role.User, R.string.screen_room_roles_and_permissions_change_role_demote_to_member) +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsStateProvider.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsStateProvider.kt similarity index 83% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsStateProvider.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsStateProvider.kt index 211a16d7d1..45bd72db19 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsStateProvider.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsStateProvider.kt @@ -1,14 +1,16 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions +package io.element.android.features.rolesandpermissions.impl.root import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncAction +import kotlinx.collections.immutable.toImmutableList class RolesAndPermissionsStateProvider : PreviewParameterProvider { override val values: Sequence @@ -45,7 +47,7 @@ class RolesAndPermissionsStateProvider : PreviewParameterProvider = listOf(SelfDemoteAction.ToModerator, SelfDemoteAction.ToMember), changeOwnRoleAction: AsyncAction = AsyncAction.Uninitialized, resetPermissionsAction: AsyncAction = AsyncAction.Uninitialized, eventSink: (RolesAndPermissionsEvents) -> Unit = {}, ) = RolesAndPermissionsState( roomSupportsOwnerRole = roomSupportsOwners, adminCount = adminCount, - canDemoteSelf = canDemoteSelf, + availableSelfDemoteActions = availableSelfDemoteActions.toImmutableList(), moderatorCount = moderatorCount, changeOwnRoleAction = changeOwnRoleAction, resetPermissionsAction = resetPermissionsAction, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsView.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsView.kt similarity index 78% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsView.kt rename to features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsView.kt index b581554a92..269fdee664 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsView.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsView.kt @@ -1,11 +1,12 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions +package io.element.android.features.rolesandpermissions.impl.root import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding @@ -20,7 +21,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons -import io.element.android.features.roomdetails.impl.R +import io.element.android.features.rolesandpermissions.impl.R import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.async.AsyncActionView @@ -38,8 +39,8 @@ import io.element.android.libraries.designsystem.theme.components.ListSectionHea import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.hide -import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList @Composable fun RolesAndPermissionsView( @@ -62,41 +63,36 @@ fun RolesAndPermissionsView( ListItem( headlineContent = { Text(adminsTitle) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Admin())), - trailingContent = ListItemContent.Text("${state.adminCount}"), + trailingContent = state.adminCount?.let { adminCount -> + ListItemContent.Text("$adminCount") + }, onClick = { rolesAndPermissionsNavigator.openAdminList() }, ) ListItem( headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_moderators)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.ChatProblem())), - trailingContent = ListItemContent.Text("${state.moderatorCount}"), + trailingContent = state.moderatorCount?.let { moderationCount -> + ListItemContent.Text("$moderationCount") + }, onClick = { rolesAndPermissionsNavigator.openModeratorList() }, ) - if (state.canDemoteSelf) { + if (state.canSelfDemote) { ListItem( headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_my_role)) }, onClick = { state.eventSink(RolesAndPermissionsEvents.ChangeOwnRole) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Edit())) ) } - ListSectionHeader(title = stringResource(R.string.screen_room_roles_and_permissions_permissions_header), hasDivider = true) + HorizontalDivider() ListItem( - headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_room_details)) }, - leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Info())), - onClick = { rolesAndPermissionsNavigator.openEditRoomDetailsPermissions() }, - ) - ListItem( - headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_messages_and_content)) }, - leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Chat())), - onClick = { rolesAndPermissionsNavigator.openMessagesAndContentPermissions() }, - ) - ListItem( - headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_member_moderation)) }, - leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.User())), - onClick = { rolesAndPermissionsNavigator.openModerationPermissions() }, + headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_permissions_header)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Settings())), + onClick = { rolesAndPermissionsNavigator.openEditPermissions() }, ) HorizontalDivider() ListItem( headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_reset)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Delete())), onClick = { state.eventSink(RolesAndPermissionsEvents.ResetPermissions) }, style = ListItemStyle.Destructive, ) @@ -121,6 +117,7 @@ fun RolesAndPermissionsView( when (state.changeOwnRoleAction) { is AsyncAction.Confirming -> { ChangeOwnRoleBottomSheet( + availableDemoteActions = state.availableSelfDemoteActions, eventSink = state.eventSink, ) } @@ -140,6 +137,7 @@ fun RolesAndPermissionsView( @OptIn(ExperimentalMaterial3Api::class) @Composable private fun ChangeOwnRoleBottomSheet( + availableDemoteActions: ImmutableList, eventSink: (RolesAndPermissionsEvents) -> Unit, ) { val coroutineScope = rememberCoroutineScope() @@ -168,24 +166,17 @@ private fun ChangeOwnRoleBottomSheet( style = ElementTheme.typography.fontBodyLgRegular, color = ElementTheme.colors.textPrimary, ) - ListItem( - headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator)) }, - onClick = { - sheetState.hide(coroutineScope) { - eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator)) - } - }, - style = ListItemStyle.Destructive, - ) - ListItem( - headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_role_demote_to_member)) }, - onClick = { - sheetState.hide(coroutineScope) { - eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.User)) - } - }, - style = ListItemStyle.Destructive, - ) + for (demoteAction in availableDemoteActions) { + ListItem( + headlineContent = { Text(stringResource(demoteAction.titleRes)) }, + onClick = { + sheetState.hide(coroutineScope) { + eventSink(RolesAndPermissionsEvents.DemoteSelfTo(demoteAction.role)) + } + }, + style = ListItemStyle.Destructive, + ) + } ListItem( headlineContent = { Text(stringResource(CommonStrings.action_cancel)) }, onClick = ::dismiss, diff --git a/features/changeroommemberroles/impl/src/main/res/values-be/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-be/translations.xml similarity index 89% rename from features/changeroommemberroles/impl/src/main/res/values-be/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-be/translations.xml index 6d16b23bcc..44841d1207 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-be/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-be/translations.xml @@ -3,14 +3,12 @@ "Толькі адміністратары" "Заблакіраваць людзей" "Выдаліць паведамленні" - "Усе" "Запрашайце людзей і прымайце запыты на далучэнне" - "Мадэрацыя ўдзельнікаў" "Паведамленні і змест" "Адміністратары і мадэратары" "Выдаляйце людзей і адхіляйце запыты на далучэнне" "Змяніць аватар пакоя" - "Дэталі пакоя" + "Рэдагаваць пакой" "Змяніць назву пакоя" "Змяніць тэму пакоя" "Адправіць паведамленні" @@ -39,12 +37,10 @@ "Толькі выдаліць удзельніка" "Разблакіраваць" "Яны змогуць зноў далучыцца да гэтага пакоя, калі іх запросяць." - "Разблакіраваць удзельніка" "Заблакіраваныя" "Удзельнікі" - "У чаканні" - "Адміністратар" - "Мадэратар" + "Толькі адміністратары" + "Адміністратары і мадэратары" "Удзельнікі пакоя" "Разблакіроўка %1$s" "Адміністратары" @@ -54,7 +50,6 @@ "Мадэрацыя ўдзельнікаў" "Паведамленні і змест" "Мадэратары" - "Дазволы" "Скінуць дазволы" "Пасля скіду дазволаў вы страціце бягучыя налады." "Скінуць дазволы?" diff --git a/features/changeroommemberroles/impl/src/main/res/values-bg/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-bg/translations.xml similarity index 85% rename from features/changeroommemberroles/impl/src/main/res/values-bg/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-bg/translations.xml index 2b4718f078..b1ca5f9718 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-bg/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-bg/translations.xml @@ -2,13 +2,11 @@ "Само администратори" "Премахване на съобщения" - "Всеки" "Поканване на хора и приемане на заявки за присъединяване" - "Модериране на членове" "Съобщения и съдържание" "Администратори и модератори" "Премахване на хора и отхвърляне на заявки за присъединяване" - "Подробности за стаята" + "Редактиране на стаята" "Промяна на името на стаята" "Промяна на темата на стаята" "Изпращане на съобщения" @@ -24,17 +22,17 @@ "%1$d души" "Членове" - "Администратор" - "Модератор" + "Само администратори" + "Администратори и модератори" "Членове на стаята" "Администратори" "Промяна на моята роля" "Модериране на членове" "Съобщения и съдържание" "Модератори" - "Разрешения" "Нулиране на разрешенията" "Роли" "Подробности за стаята" + "Подробности за пространството" "Роли и разрешения" diff --git a/features/changeroommemberroles/impl/src/main/res/values-cs/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-cs/translations.xml similarity index 79% rename from features/changeroommemberroles/impl/src/main/res/values-cs/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-cs/translations.xml index a66795ea7e..87936de3a7 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-cs/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-cs/translations.xml @@ -1,19 +1,23 @@ - "Pouze správci" + "Správce" "Vykázat lidi" + "Změnit nastavení" "Odstranit zprávy" - "Všichni" - "Pozvěte lidi a přijímejte žádosti o připojení" - "Moderování členů" + "Člen" + "Pozvat přátele" + "Správa prostoru" + "Spravovat místnosti" + "Spravovat členy" "Zprávy a obsah" - "Správci a moderátoři" - "Odeberte lidi a odmítněte žádosti o připojení" + "Moderátor" + "Odebrat osoby" "Změnit avatar místnosti" - "Podrobnosti místnosti" + "Upravit podrobnosti" "Změnit název místnosti" "Změnit téma místnosti" "Odeslat zprávy" + "Oprávnění" "Upravit správce" "Tuto akci nebudete moci vrátit zpět. Upravujete oprávnění uživatele, tak aby měl stejnou úroveň jako vy." "Přidat správce?" @@ -34,6 +38,13 @@ "Máte neuložené změny." "Uložit změny?" "V této místnosti nejsou žádní vykázaní uživatelé." + + "%1$d vykázán(a)" + "%1$d vykázáni" + "%1$d vykázáných" + + "Zkontrolujte pravopis nebo zkuste nové vyhledávání" + "Žádné výsledky pro “%1$s”" "%1$d osoba" "%1$d osoby" @@ -43,10 +54,15 @@ "Pouze odebrat člena" "Zrušit vykázání" "Pokud budou pozváni, budou se moci do této místnosti znovu připojit." - "Zrušit vykázání uživatele" + "Zrušit vykázání z místnosti" "Vykázaní" "Členové" - "Nevyřízeno" + + "%1$d pozván(a)" + "%1$d pozváni" + "%1$d pozvaných" + + "Čekající" "Správce" "Moderátor" "Vlastník" @@ -67,5 +83,6 @@ "Obnovit oprávnění?" "Role" "Podrobnosti místnosti" - "Role a oprávnění" + "Detaily prostoru" + "Role a oprávnění" diff --git a/features/changeroommemberroles/impl/src/main/res/values-cy/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-cy/translations.xml similarity index 94% rename from features/changeroommemberroles/impl/src/main/res/values-cy/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-cy/translations.xml index 4740c15888..f75f908d43 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-cy/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-cy/translations.xml @@ -10,7 +10,7 @@ "Gweinyddwyr a chymedrolwyr" "Dileu pobl a gwrthod ceisiadau i ymuno" "Newid afatar ystafell" - "Manylion yr ystafell" + "Ystafell Golygu" "Newid enw\'r ystafell" "Newid pwnc yr ystafell" "Anfon negeseuon" @@ -46,12 +46,11 @@ "Dileu aelod yn unig" "Adfer" "Fyddan nhw yn gallu ymuno â\'r ystafell hon eto os cân nhw wahoddiad." - "Gwahardd defnyddiwr" + "Dad-wahardd o\'r ystafell" "Wedi\'i wahardd" "Aelodau" - "Dan ystyriaeth" - "Gweinyddwr" - "Cymedrolwr" + "Gweinyddwyr yn unig" + "Gweinyddwyr a chymedrolwyr" "Perchennog" "Aelodau\'r ystafell" "Dad-wahardd %1$s" @@ -64,7 +63,6 @@ "Negeseuon a chynnwys" "Cymedrolwyr" "Perchnogion" - "Caniatâd" "Ailosod caniatâd" "Ar ôl i chi ailosod caniatâd, byddwch yn colli\'r gosodiadau cyfredol." "Ailosod caniatâd?" diff --git a/features/changeroommemberroles/impl/src/main/res/values-da/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-da/translations.xml similarity index 80% rename from features/changeroommemberroles/impl/src/main/res/values-da/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-da/translations.xml index 122453e121..31aee3436e 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-da/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-da/translations.xml @@ -1,19 +1,23 @@ - "Kun admins" + "Administrator" "Spær brugere" + "Skift indstillinger" "Fjern beskeder" - "Alle" - "Invitér personer og acceptér anmodninger om at deltage" - "Moderation af medlemmer" + "Medlem" + "Invitér andre" + "Administrér gruppe" + "Administrer rum" + "Administrer medlemmer" "Beskeder og indhold" - "Admins og moderatorer" - "Fjern personer og afvis anmodninger om at deltage" + "Moderator" + "Fjern personer" "Skift rummets avatar" - "Detaljer om rummet" + "Redigér detaljer" "Skift rummets navn" "Skift emne for rummet" "Send beskeder" + "Tilladelser" "Redigér admins" "Du kan ikke fortryde denne handling. Du forfremmer brugeren til at have samme magtniveau som dig." "Tilføj Admin?" @@ -34,6 +38,12 @@ "Du har ændringer, der ikke er gemt." "Gem ændringer?" "Der er ingen spærrede brugere i dette rum." + + "%1$d Spærret" + "%1$d Spærret" + + "Tjek stavningen eller prøv en ny søgning" + "Ingen resultater for \"%1$s\"" "%1$d person" "%1$d personer" @@ -42,11 +52,15 @@ "Fjern kun medlem" "Fjern spærring af" "De vil være i stand til at deltage i dette rum igen, hvis de inviteres." - "Ophæv blokering af bruger fra rum" + "Fjern brugerens spærring fra rummet" "Spærret" "Medlemmer" - "Afventer" - "Admin" + + "%1$d Inviteret" + "%1$d Inviteret" + + "Afventer" + "Administrator" "Moderator" "Ejeren" "Medlemmer af rummet" @@ -66,5 +80,6 @@ "Nulstil tilladelser?" "Roller" "Detaljer om rummet" + "Detaljer om gruppe" "Roller og tilladelser" diff --git a/features/changeroommemberroles/impl/src/main/res/values-de/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-de/translations.xml similarity index 90% rename from features/changeroommemberroles/impl/src/main/res/values-de/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-de/translations.xml index 88d795d614..2767781d2b 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-de/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-de/translations.xml @@ -3,14 +3,12 @@ "Nur Admins" "Mitglieder sperren" "Nachrichten entfernen" - "Alle" "Personen einladen und Beitrittsanfragen annehmen" - "Moderation der Mitglieder" "Nachrichten senden & löschen" "Admins und Moderatoren" "Personen entfernen und Beitrittsanfragen ablehnen" "Avatar ändern" - "Chat-Details anpassen" + "Chat bearbeiten" "Chat-Namen ändern" "Chat Thema ändern" "Nachrichten senden" @@ -33,7 +31,7 @@ "Mitglieder" "Du hast nicht gespeicherte Änderungen." "Änderungen speichern?" - "In diesem Chat gibt es keine gesperrten Nutzer." + "Es gibt keine gesperrten Nutzer." "%1$d Person" "%1$d Personen" @@ -42,12 +40,11 @@ "Mitglied nur entfernen" "Sperre aufheben" "Die Nutzer können dem Chat wieder beitreten, wenn sie eingeladen werden." - "Nutzer entsperren" + "Sperre für diesen Chat aufheben" "Gesperrt" "Mitglieder" - "Ausstehend" - "Admin" - "Moderator" + "Nur Admins" + "Admins und Moderatoren" "Eigentümer" "Mitglieder" "%1$s wird entsperrt." @@ -60,7 +57,6 @@ "Nachrichten senden & löschen" "Moderatoren" "Eigentümer" - "Berechtigungen" "Rollen und Berechtigungen zurücksetzen" "Sobald du die Berechtigungen zurücksetzt, verlierst du die aktuellen Einstellungen." "Berechtigungen zurücksetzen?" diff --git a/features/changeroommemberroles/impl/src/main/res/values-el/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-el/translations.xml similarity index 90% rename from features/changeroommemberroles/impl/src/main/res/values-el/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-el/translations.xml index fbb7e6952f..8cd14feb47 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-el/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-el/translations.xml @@ -3,14 +3,12 @@ "Μόνο διαχειριστές" "Αποκλεισμός ατόμων" "Αφαίρεση μηνυμάτων" - "Όλοι" "Προσκάλεσε άτομα και αποδέξου αιτήματα συμμετοχής" - "Συντονισμός μελών" "Μηνύματα και περιεχόμενο" "Διαχειριστές και συντονιστές" "Αφαίρεση ατόμων και απόρριψη αιτημάτων συμμετοχής" "Αλλαγή εικόνας προφίλ αίθουσας" - "Λεπτομέρειες αίθουσας" + "Επεξεργασία Αίθουσας" "Αλλαγή ονόματος αίθουσας" "Αλλαγή θέματος αίθουσας" "Αποστολή μηνυμάτων" @@ -38,12 +36,11 @@ "Μόνο αφαίρεση μέλους" "Αναίρεση αποκλεισμού" "Θα μπορούν να συμμετάσχουν ξανά σε αυτή την αίθουσα, εάν προσκληθούν." - "Άρση αποκλεισμού χρήστη" + "Άρση αποκλεισμού από την αίθουσα" "Αποκλεισμένοι" "Μέλη" - "Σε αναμονή" - "Διαχειριστής" - "Συντονιστής" + "Μόνο διαχειριστές" + "Διαχειριστές και συντονιστές" "Μέλη της αίθουσας" "Άρση αποκλεισμού %1$s" "Διαχειριστές" @@ -53,7 +50,6 @@ "Συντονισμός μελών" "Μηνύματα και περιεχόμενο" "Συντονιστές" - "Άδειες" "Επαναφορά δικαιωμάτων" "Μόλις επαναφέρεις τα δικαιώματα, θα χάσεις τις τρέχουσες ρυθμίσεις." "Επαναφορά δικαιωμάτων;" diff --git a/features/changeroommemberroles/impl/src/main/res/values-es/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-es/translations.xml similarity index 89% rename from features/changeroommemberroles/impl/src/main/res/values-es/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-es/translations.xml index 128435bbc2..383ffba1eb 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-es/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-es/translations.xml @@ -3,14 +3,12 @@ "Solo administradores" "Vetar personas" "Eliminar mensajes" - "Todos" "Invitar personas y aceptar solicitudes de unión" - "Moderación de miembros" "Mensajes y contenido" "Administradores y moderadores" "Eliminar personas y rechazar solicitudes de unión" "Cambiar el avatar de la sala" - "Detalles de la sala" + "Editar sala" "Cambiar el nombre de la sala" "Cambiar el tema de la sala" "Enviar mensajes" @@ -38,12 +36,11 @@ "Solo eliminar miembro" "Quitar veto" "Podrá volver a unirse a esta sala si se le invita." - "Quitar veto al usuario" + "Eliminar veto en la sala" "Vetados" "Miembros" - "Pendiente" - "Admin" - "Moderador" + "Solo administradores" + "Administradores y moderadores" "Miembros de la sala" "Levantando veto a %1$s" "Administradores" @@ -53,7 +50,6 @@ "Moderación de miembros" "Mensajes y contenido" "Moderadores" - "Permisos" "Restablecer permisos" "Una vez que restablezca los permisos, perderá la configuración actual." "¿Restablecer los permisos?" diff --git a/features/changeroommemberroles/impl/src/main/res/values-et/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-et/translations.xml similarity index 76% rename from features/changeroommemberroles/impl/src/main/res/values-et/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-et/translations.xml index a43a9a89d0..1c1f490580 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-et/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-et/translations.xml @@ -1,19 +1,23 @@ - "Vaid peakasutajad" + "Peakasutajad" "Suhtluskeelu seadmine" + "Muuda seadistusi" "Eemalda sõnumid" - "Kõik" - "Kutsu teisi osalejaid ja vasta ise liitumiskutsetele" - "Jututoas osalejate modereerimine" + "Liikmed" + "Osalejate kutsumine" + "Halda kogukonda" + "Halda jututuba" + "Liikmete haldus" "Sõnumid ja sisu" - "Peakasutajad ja moderaatorid" - "Eemalda osalejaid jututoast ja lükka liitumiskutsed tagasi" + "Moderaatorid" + "Osalejate eemaldamine" "Jututoa tunnuspildi muutmine" - "Jututoa üksikasjad" + "Muuda üksikasju" "Jututoa nime muutmine" "Jututoa teema muutmine" "Sõnumite saatmine" + "Õigused" "Muuda peakasutajaid" "Kuna sa annad teisele kasutajale sinu õigustega võrreldes samad õigused, siis sa ei saa seda muudatust hiljem tagasi pöörata." "Lisame peakasutaja?" @@ -33,7 +37,13 @@ "Liikmed" "Sul on salvestamata muudatusi" "Kas salvestame muudatused?" - "Jututoas pole suhtluskeeluga kasutajaid" + "Suhtluskeeluga kasutajaid pole" + + "%1$d suhtluskeeluga kasutaja" + "%1$d suhtluskeeluga kasutajat" + + "Palun kontrolli otsingusõna korrektsust ja proovi siis uuesti" + "Otsingul „%1$s“ pole tulemusi" "%1$d osaleja" "%1$d osalejat" @@ -42,12 +52,16 @@ "Ainult eemalda kasutaja" "Eemalda suhtluskeeld" "Kutse olemasolul saab ta nüüd jututoaga uuesti liituda" - "Eemalda kasutaja suhtluskeeld" + "Eemalda suhtluskeeld jututoas" "Suhtluskeeluga kasutajad" "Liikmed" - "Ootel" - "Peakasutaja" - "Moderaator" + + "%1$d saatis kutse" + "%1$d saatis kutse" + + "Ootel" + "Peakasutajad" + "Moderaatorid" "Omanik" "Jututoas osalejad" "Eemaldame suhtluskeelu kasutajalt %1$s" @@ -66,5 +80,6 @@ "Kas lähtestame õigused?" "Rollid" "Jututoa üksikasjad" + "Kogukonna üksikasjad" "Rollid ja õigused" diff --git a/features/changeroommemberroles/impl/src/main/res/values-eu/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-eu/translations.xml similarity index 88% rename from features/changeroommemberroles/impl/src/main/res/values-eu/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-eu/translations.xml index 47210a0ca6..03766f73f2 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-eu/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-eu/translations.xml @@ -3,14 +3,12 @@ "Administratzaileak soilik" "Jarri debekua jendeari" "Kendu mezuak" - "Guztiak" "Gonbidatu jendea" - "Kideen moderazioa" "Mezuak eta edukiak" "Administratzaileak eta moderatzaileak" "Kendu jendea" "Aldatu gelaren abatarra" - "Gelaren xehetasunak" + "Editatu gela" "Aldatu gelaren izena" "Aldatu gelako mintzagaia" "Bidali mezuak" @@ -38,12 +36,10 @@ "Kendu kidea eta ezarri debekua" "Kendu kidea soilik" "Kendu debekua" - "Kendu debekua erabiltzaileari" "Debekatuta" "Kideak" - "Zain" - "Kudeatzailea" - "Moderatzailea" + "Administratzaileak soilik" + "Administratzaileak eta moderatzaileak" "Jabea" "Gelako kideak" "%1$s(r)i debekua kentzen" @@ -56,7 +52,6 @@ "Mezuak eta edukiak" "Moderatzaileak" "Jabeak" - "Baimenak" "Berrezarri baimenak" "Baimenak berrezarritakoan, uneko ezarpenak galduko dituzu." "Baimenak berrezarri?" diff --git a/features/changeroommemberroles/impl/src/main/res/values-fa/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-fa/translations.xml similarity index 85% rename from features/changeroommemberroles/impl/src/main/res/values-fa/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-fa/translations.xml index c5bbc9d3ff..c526d059bc 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-fa/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-fa/translations.xml @@ -10,25 +10,29 @@ "مدیرن و ناظران" "برداشتن افراد و رد درخواست‌های پیوستن" "تغییر چهرک اتاق" - "جزییات اتاق" + "ویرایش اتاق" "تغییر نام اتاق" "دگرگونی موضوع اتاق" "فرستادن پیام‌ها" "ویرایش مدیران" "قادر نخواهید بود این کنش را بازکردانید. داردید کاربر را به سطح قدرت خودتان ارتقا می‌دهید." "افزودن مدیر؟" + "انتقال مالکیت؟" "تنزل بده" "شما نمی‌توانید این تغییر را بازگردانید زیرا در حال تنزل نقش خود در اتاق هستید، اگر آخرین کاربر ممتاز در اتاق باشید، امکان دستیابی مجدد به دسترسی‌های سطح بالای اتاق غیرممکن است." "تنزل نقش شما در اتاق؟" "%1$s (منتظر)" "(منتظر)" "مدیران به صورت خودکار اجازه‌های نظارتی را دارند" + "ماکان به صورت خودکار اجازه‌های مدیریتی را دارند." "ویرایش ناظران" + "گزینش مالکان" "مدیران" "ناظم‌ها" "اعضا" "تغییراتی ذخیره نشده دارید." "ذخیرهٔ تغییرات؟" + "هیچ کاربر محرومی در این اتاق نیست." "%1$d نفر" "%1$d نفر" @@ -37,23 +41,23 @@ "تنها برداشتن عضو" "رفع انسداد" "در صورت دعوت می‌تواند دوباره به اتاق بپیوندد." - "تحریم نکردن کاربر" + "تحریم نکردن از اتاق" "محروم" "اعضا" - "منتظر" - "مدیر" - "ناظمر" + "فقط مدیران" + "مدیرن و ناظران" "مالک" "اعضای اتاق" "رفع تحریم %1$s" "مدیران" + "مدیران و مالکان" "تغییر نقشم" "تنزّل به عضو" "تنزّل به ناظم" "نظارت اعضا" "پیام‌ها و محتوا" "ناظم‌ها" - "اجازه‌ها" + "مالکان" "بازنشانی اجازه‌ها" "بازنشانی اجازه‌ها؟" "نقش‌ها" diff --git a/features/changeroommemberroles/impl/src/main/res/values-fi/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-fi/translations.xml similarity index 87% rename from features/changeroommemberroles/impl/src/main/res/values-fi/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-fi/translations.xml index 1279d466e2..29331bcd13 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-fi/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-fi/translations.xml @@ -1,19 +1,23 @@ - "Vain ylläpitäjät" + "Ylläpitäjä" "Porttikieltojen antaminen" + "Asetusten muuttaminen" "Viestien poistaminen" - "Kaikki" + "Jäsen" "Ihmisten kutsuminen ja liittymispyyntöjen hyväksyminen" - "Jäsenten valvonta" + "Tilan hallitseminen" + "Huoneiden hallitseminen" + "Jäsenien hallinta" "Viestit ja sisältö" - "Ylläpitäjät ja valvojat" + "Valvoja" "Henkilöiden poistaminen ja liittymispyyntöjen hylkääminen" "Huoneen avatarin vaihtaminen" - "Huoneen tiedot" + "Muokkaa tietoja" "Huoneen nimen vaihtaminen" "Huoneen aiheen vaihtaminen" "Viestien lähettäminen" + "Oikeudet" "Muokkaa ylläpitäjiä" "Et voi peruuttaa tätä toimenpidettä. Ylennät käyttäjän samalle oikeustasolle kuin sinä." "Lisätäänkö ylläpitäjä?" @@ -33,7 +37,7 @@ "Jäsenet" "Sinulla on tallentamattomia muutoksia" "Tallennetaanko muutokset?" - "Tässä huoneessa ei ole porttikieltoja" + "Porttikiellettyjä käyttäjiä ei ole." "%1$d henkilö" "%1$d henkilöä" @@ -42,10 +46,9 @@ "Poista vain jäsen huoneesta" "Poista porttikielto" "He voivat liittyä tähän huoneeseen uudelleen, jos heidät kutsutaan." - "Poista käyttäjän porttikielto" + "Poista porttikielto huoneesta" "Porttikiellot" "Jäsenet" - "Kutsuttu" "Ylläpitäjä" "Valvoja" "Omistaja" @@ -66,5 +69,6 @@ "Nollataanko oikeudet?" "Roolit" "Huoneen tiedot" + "Tilan tiedot" "Roolit ja oikeudet" diff --git a/features/changeroommemberroles/impl/src/main/res/values-fr/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-fr/translations.xml similarity index 78% rename from features/changeroommemberroles/impl/src/main/res/values-fr/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-fr/translations.xml index a708c9ffb5..874b6710cd 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-fr/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-fr/translations.xml @@ -1,19 +1,23 @@ - "Administrateurs seulement" + "Administrateurs" "Bannir des participants" + "Changer les paramètres" "Supprimer des messages" - "Tout le monde" - "Inviter des personnes et accepter les demandes pour rejoindre" - "Administration des membres" + "Membre" + "Inviter des personnes" + "Gérer l’espace" + "Gérer les salons" + "Gérer les membres" "Messages et contenus" - "Administrateurs et modérateurs" - "Retirer des personnes et refuser les demandes pour rejoindre" + "Modérateurs" + "Retirer des personnes" "Changer l’avatar du salon" - "Détails du salon" + "Modifier les détails" "Changer le nom du salon" "Changer le sujet du salon" "Envoyer des messages" + "Autorisations" "Modifier les administrateurs" "Vous ne pourrez pas annuler cette action. Vous êtes en train de promouvoir l’utilisateur pour qu’il ait le même niveau que vous." "Ajouter un administrateur ?" @@ -33,21 +37,31 @@ "Membres" "Vous avez des modifications non-enregistrées." "Enregistrer les changements ?" - "Il n’y a pas d’utilisateur banni dans ce salon." + "Il n’y a pas d’utilisateur banni." + + "%1$d Banni(e)" + "%1$d Banni(e)s" + + "Vérifiez la saisie ou effectuez une nouvelle recherche" + "Aucun résultat pour «%1$s»" - "%1$d personne" - "%1$d personnes" + "%1$d Personne" + "%1$d Personnes" "Bannir du salon" "Retirer le membre uniquement" "Débannir" "Il pourra rejoindre le salon à nouveau si il est invité." - "Débannir l’utilisateur" + "Débannir du salon" "Bannis" "Membres" - "En attente" - "Administrateur" - "Modérateur" + + "%1$d Invité(e)" + "%1$d Invité(e)s" + + "En attente" + "Administrateurs" + "Modérateurs" "Propriétaire" "Membres du salon" "Débannissement de %1$s" @@ -66,5 +80,6 @@ "Réinitialisation des autorisations ?" "Rôles" "Détails du salon" - "Rôles et autorisations" + "Détails de l’espace" + "Rôles & autorisations" diff --git a/features/rolesandpermissions/impl/src/main/res/values-hr/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..7f24535124 --- /dev/null +++ b/features/rolesandpermissions/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,88 @@ + + + "Administrator" + "Zabrana pristupa osobama" + "Promijeni postavke" + "Uklanjanje poruka" + "Član" + "Pozivanje osoba" + "Upravljaj prostorom" + "Upravljaj sobama" + "Upravljanje članovima" + "Poruke i sadržaj" + "Moderator" + "Uklanjanje osoba" + "Promjena avatara" + "Uredi pojedinosti" + "Promjena imena" + "Promjena teme" + "Slanje poruka" + "Dopuštenja" + "Uredi administratore" + "Nećete moći poništiti ovu radnju. Postavit ćete da korisnik ima isti položaj kao i vi." + "Dodati administratora?" + "Nećete moći poništiti ovu radnju. Prenosite vlasništvo na odabrane korisnike. Nakon što odete, to će biti trajno." + "Želite li prenijeti vlasništvo?" + "Degradiraj" + "Nećete moći poništiti ovu promjenu jer sami sebe degradirate. Ako ste posljednji privilegirani korisnik u sobi, nećete moći ponovno dobiti privilegije." + "Želite li se degradirati?" + "%1$s (na čekanju)" + "(na čekanju)" + "Administratori automatski imaju moderatorske ovlasti" + "Vlasnici automatski imaju administratorske ovlasti." + "Uredi moderatore" + "Odaberi vlasnike" + "Administratori" + "Moderatori" + "Članovi" + "Niste spremili sve promjene." + "Želite li spremiti promjene?" + "Nema zabranjenih korisnika." + + "%1$d zabranjen" + "%1$d zabranjena" + "%1$d zabranjenih" + + "Provjerite pravopis ili pokušajte s novim pretraživanjem" + "Nema rezultata za “%1$s”" + + "%1$d osoba" + "%1$d osobe" + "%1$d ljudi" + + "Zabrani korisnika" + "Samo ukloni člana" + "Poništi zabranu" + "Moći će se ponovno pridružiti ovoj sobi ako budu pozvani." + "Poništi zabranu pristupa korisniku" + "Zabranjeni" + "Članovi" + + "%1$d pozvan" + "%1$d pozvana" + "%1$d pozvanih" + + "Na čekanju" + "Administrator" + "Moderator" + "Vlasnik" + "Članovi sobe" + "Uklanja se zabrana korisniku %1$s" + "Administratori" + "Administratori i vlasnici" + "Promijeni moju ulogu" + "Degradiraj u člana" + "Degradiraj u moderatora" + "Moderiranje članova" + "Poruke i sadržaj" + "Moderatori" + "Vlasnici" + "Dopuštenja" + "Poništi dopuštenja" + "Nakon što poništite dopuštenja, izgubit ćete trenutačne postavke." + "Želite li poništiti dopuštenja?" + "Uloge" + "Pojedinosti o sobi" + "Pojedinosti o prostoru" + "Uloge i dopuštenja" + diff --git a/features/changeroommemberroles/impl/src/main/res/values-hu/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-hu/translations.xml similarity index 83% rename from features/changeroommemberroles/impl/src/main/res/values-hu/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-hu/translations.xml index 29e45978e1..4160039a0a 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-hu/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-hu/translations.xml @@ -1,19 +1,23 @@ - "Csak adminisztrátorok" + "Adminisztrátor" "Emberek kitiltása" + "Beállítások módosítása" "Üzenetek eltávolítása" - "Mindenki" - "Személyek meghívása és csatlakozási kérések elfogadása" - "Tagok moderálása" + "Tag" + "Emberek meghívása" + "Tér kezelése" + "Szobák kezelése" + "Tagok kezelése" "Üzenetek és tartalom" - "Adminisztrátorok és moderátorok" - "Emberek eltávolítása és a csatlakozási kérések elutasítása" + "Moderátor" + "Emberek eltávolítása" "Szoba profilképének módosítása" - "Szoba részletei" + "Részletek szerkesztése" "Szoba nevének módosítása" "Szoba témájának módosítása" "Üzenetek küldése" + "Jogosultságok" "Adminisztrátorok szerkesztése" "Ezt a műveletet nem fogja tudja visszavonni. Ugyanarra a szintre lépteti elő a felhasználót, mint amellyel Ön is rendelkezik." "Adminisztrátor hozzáadása?" @@ -34,6 +38,8 @@ "Mentetlen módosításai vannak." "Menti a módosításokat?" "Ebben a szobában nincsenek kitiltott felhasználók." + "Ellenőrizze a helyesírást, vagy próbáljon meg egy új keresést" + "Nincs találat a következőre: „%1$s\"" "%1$d személy" "%1$d személy" @@ -42,10 +48,10 @@ "Csak a tag eltávolítása" "Tiltás feloldása" "Ehhez a szobához is csatlakozhat, ha meghívják." - "Felhasználó tiltásának feloldása" + "Visszaengedés a szobába" "Kitiltva" "Tagok" - "Függőben" + "Függőben" "Adminisztrátor" "Moderátor" "Tulajdonos" @@ -66,5 +72,6 @@ "Jogosultságok visszaállítása?" "Szerepkörök" "Szoba részletei" + "Tér részletei" "Szerepkörök és jogosultságok" diff --git a/features/changeroommemberroles/impl/src/main/res/values-in/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-in/translations.xml similarity index 89% rename from features/changeroommemberroles/impl/src/main/res/values-in/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-in/translations.xml index 3bc68c154e..2a1cd15ac0 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-in/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-in/translations.xml @@ -3,14 +3,12 @@ "Hanya admin" "Cekal orang-orang" "Hilangkan pesan" - "Semua orang" "Undang orang-orang dan terima permintaan untuk bergabung" - "Moderasi anggota" "Pesan dan konten" "Admin dan moderator" "Keluarkan orang-orang dan tolak permintaan untuk bergabung" "Ubah avatar ruangan" - "Detail ruangan" + "Sunting Ruangan" "Ubah nama ruangan" "Ubah topik ruangan" "Kirim pesan" @@ -37,12 +35,11 @@ "Hanya keluarkan anggota" "Batalkan pencekalan" "Pengguna dapat bergabung ke ruangan ini lagi jika diundang." - "Batalkan pencekalan pengguna" + "Batalkan cekalan dari ruangan" "Tercekal" "Anggota" - "Tertunda" - "Admin" - "Moderator" + "Hanya admin" + "Admin dan moderator" "Anggota ruangan" "Membatalkan cekalan %1$s" "Admin" @@ -52,7 +49,6 @@ "Moderasi anggota" "Pesan dan konten" "Moderator" - "Perizinan" "Atur ulang perizinan" "Setelah Anda mengatur ulang perizinan, Anda akan kehilangan pengaturan Anda saat ini." "Atur ulang perizinan?" diff --git a/features/changeroommemberroles/impl/src/main/res/values-it/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-it/translations.xml similarity index 79% rename from features/changeroommemberroles/impl/src/main/res/values-it/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-it/translations.xml index c6ea02ebb8..b1dea12151 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-it/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-it/translations.xml @@ -1,19 +1,23 @@ - "Solo amministratori" + "Amministratore" "Escludi membri" + "Modifica impostazioni" "Rimuovi messaggi" - "Tutti" - "Invita persone e accetta richieste di partecipazione" - "Moderazione dei membri" + "Membro" + "Invita persone" + "Gestire lo spazio" + "Gestisci le stanze" + "Gestisci membri" "Messaggi e contenuti" - "Amministratori e moderatori" - "Rimuovi le persone e rifiuta le richieste di partecipazione" + "Moderatore" + "Rimuovi membri" "Cambia avatar della stanza" - "Dettagli della stanza" + "Modifica dettagli" "Cambia il nome della stanza" "Cambiare l\'argomento della stanza" "Inviare messaggi" + "Autorizzazioni" "Modifica amministratori" "Non potrai annullare questa azione. Stai promuovendo l\'utente al tuo stesso livello di potere." "Aggiungi amministratore?" @@ -33,19 +37,29 @@ "Membri" "Hai delle modifiche non salvate." "Salvare le modifiche?" - "Non ci sono utenti esclusi in questa stanza." + "Non ci sono utenti bannati." + + "%1$d Bannato" + "%1$d Bannati" + + "Controlla l\'ortografia o prova una nuova ricerca" + "Nessun risultato per “%1$s ”" - "%1$d persona" - "%1$d persone" + "%1$d Persona" + "%1$d Persone" "Rimuovi ed escludi" "Rimuovi soltanto" "Riammetti" "Potrà entrare nuovamente in questa stanza se invitato." - "Riammetti utente" + "Riammetti nella stanza" "Esclusi" "Membri" - "In attesa" + + "%1$d Invitato" + "%1$d Invitati" + + "In attesa" "Amministratore" "Moderatore" "Proprietario" @@ -66,5 +80,6 @@ "Reimpostare autorizzazioni?" "Ruoli" "Dettagli della stanza" + "Dettagli dello spazio" "Ruoli e autorizzazioni" diff --git a/features/changeroommemberroles/impl/src/main/res/values-ka/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-ka/translations.xml similarity index 90% rename from features/changeroommemberroles/impl/src/main/res/values-ka/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-ka/translations.xml index 17d80ba10b..8af9ecbe99 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-ka/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-ka/translations.xml @@ -3,14 +3,12 @@ "მხოლოდ ადმინისტრატორები" "მომხმარებლების დაბლოკვა" "შეტყობინებების წაშლა" - "ყველა" "მომხმარებლების მოწვევა და გაწევრიანების მოთხოვნების დადასტურება" - "წევრების მოდერირება" "შეტყობინებები და შინაარსი" "ადმინისტრატორები და მოდერატორები" "მომხმარებლების გაგდება და გაწევრიანების მოთხოვნების უარყოფა" "ოთახის სურათის შეცვლა" - "ოთახის დეტალები" + "ოთახის რედაქტირება" "ოთახის სახელის შეცვლა" "ოთახის თემის შეცვლა" "შეტყობინებების გაგზავნა" @@ -37,12 +35,10 @@ "მხოლოდ წევრის წაშლა" "განბლოკვა" "მოწვევის შემთხვევაში განბლოკილი მომხმარებელი ისევ შეძლებს ოთახს შეუერთდეს." - "მომხმარებლის განბლოკვა" "დაბლოკილები" "წევრები" - "მომლოდინე" - "ადმინისტრატორი" - "მოდერატორი" + "მხოლოდ ადმინისტრატორები" + "ადმინისტრატორები და მოდერატორები" "ოთახის წევრები" "%1$s-ს განბლოკვა" "ადმინისტრატორები" @@ -52,7 +48,6 @@ "წევრების მოდერირება" "შეტყობინებები და შინაარსი" "მოდერატორები" - "ნებართვები" "ნებართვების გადაყენება" "ნებართვების გადაყენების შემთხვევაში მიმდინარე პარამეტრებს დაკარგავთ." "გადავაყენოთ ცვლილებები?" diff --git a/features/changeroommemberroles/impl/src/main/res/values-ko/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-ko/translations.xml similarity index 92% rename from features/changeroommemberroles/impl/src/main/res/values-ko/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-ko/translations.xml index fd9d38664c..89b3e0baf5 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-ko/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-ko/translations.xml @@ -3,14 +3,12 @@ "관리자 전용" "사용자 차단" "메시지 삭제" - "모두" "사람들을 초대하고 가입 요청을 수락합니다" - "회원 조정" "메시지 및 콘텐츠" "관리자 및 중재자" "사람들을 제거하고 가입 요청을 거부합니다" "방 아바타 변경" - "방 세부 정보" + "방 편집" "방 이름 변경" "방 화제 변경" "메시지 보내기" @@ -41,12 +39,11 @@ "회원만 삭제할 수 있습니다." "금지 해제" "초대받으면 이 방에 다시 들어올 수 있습니다." - "사용자 차단 해제" + "방에서 차단 해제" "차단됨" "회원들" - "보류 중" - "관리자" - "중재자" + "관리자 전용" + "관리자 및 중재자" "소유자" "방 회원들" "차단 해제 %1$s" @@ -59,7 +56,6 @@ "메시지 및 콘텐츠" "중재자" "소유자" - "권한" "권한 재설정" "권한을 재설정하면 현재 설정이 모두 삭제됩니다." "권한을 재설정하시겠습니까?" diff --git a/features/changeroommemberroles/impl/src/main/res/values-lt/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-lt/translations.xml similarity index 82% rename from features/changeroommemberroles/impl/src/main/res/values-lt/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-lt/translations.xml index 80a91ac742..b82e8822f5 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-lt/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-lt/translations.xml @@ -1,10 +1,10 @@ + "Redaguoti kambarį" "%1$d asmuo" "%1$d asmenys" "%1$d asmenų" - "Laukiama" "Kambario nariai" diff --git a/features/changeroommemberroles/impl/src/main/res/values-nb/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-nb/translations.xml similarity index 89% rename from features/changeroommemberroles/impl/src/main/res/values-nb/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-nb/translations.xml index 2e3379b98a..41f475012f 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-nb/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-nb/translations.xml @@ -1,16 +1,16 @@ - "Kun for administratorer" + "Admin" "Forby folk" "Fjern meldinger" - "Alle" - "Inviter folk og godta forespørsler om å bli med" - "Moderering av medlemmer" + "Medlem" + "Inviter folk" + "Administrer medlemmer" "Meldinger og innhold" - "Administratorer og moderatorer" - "Fjern folk og avslå forespørsler om å bli med" + "Moderator" + "Fjern folk" "Endre romavatar" - "Romdetaljer" + "Rediger detaljer" "Endre romnavn" "Endre temaet til rommet" "Send meldinger" @@ -33,7 +33,7 @@ "Medlemmer" "Du har endringer som ikke er lagret." "Lagre endringer?" - "Det er ingen utestengte brukere i dette rommet." + "Det er ingen utestengte brukere." "%1$d person" "%1$d personer" @@ -42,11 +42,10 @@ "Bare fjern medlem" "Opphev utestengelse" "De vil kunne bli med i dette rommet igjen hvis de blir invitert." - "Opphev utestengelse av bruker" + "Fjern utestengelsen fra rommet" "Utestengt" "Medlemmer" - "Venter" - "Administrator" + "Admin" "Moderator" "Eier" "Medlemmer av rommet" @@ -60,7 +59,6 @@ "Meldinger og innhold" "Moderatorer" "Eiere" - "Tillatelser" "Tilbakestill tillatelser" "Når du har tilbakestilt tillatelsene, mister du gjeldende innstillinger." "Vil du tilbakestille tillatelsene?" diff --git a/features/changeroommemberroles/impl/src/main/res/values-nl/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-nl/translations.xml similarity index 89% rename from features/changeroommemberroles/impl/src/main/res/values-nl/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-nl/translations.xml index b8cdd89620..2973341189 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-nl/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-nl/translations.xml @@ -3,14 +3,12 @@ "Alleen beheerders" "Personen verbannen" "Berichten verwijderen" - "Iedereen" "Nodig personen uit en accepteer verzoeken om deel te nemen" - "Moderatie van leden" "Berichten en inhoud" "Beheerders en moderators" "Verwijder personen en weiger verzoeken om deel te nemen" "Kamerafbeelding wijzigen" - "Kamergegevens" + "Kamer bewerken" "Kamernaam wijzigen" "Kameronderwerp wijzigen" "Berichten verzenden" @@ -38,12 +36,10 @@ "Alleen lid verwijderen" "Ontbannen" "Ze kunnen opnieuw tot de kamer toetreden als ze worden uitgenodigd." - "Ontban gebruiker" "Verbannen" "Leden" - "In behandeling" - "Beheerder" - "Moderator" + "Alleen beheerders" + "Beheerders en moderators" "Kamerleden" "%1$s ontbannen" "Beheerders" @@ -53,7 +49,6 @@ "Moderatie van leden" "Berichten en inhoud" "Moderators" - "Rechten" "Rechten opnieuw instellen" "Als je de rechten opnieuw instelt, raak je de huidige instellingen kwijt." "Rechten opnieuw instellen?" diff --git a/features/changeroommemberroles/impl/src/main/res/values-pl/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-pl/translations.xml similarity index 90% rename from features/changeroommemberroles/impl/src/main/res/values-pl/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-pl/translations.xml index ad4c7239d7..44659c47cd 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-pl/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-pl/translations.xml @@ -2,15 +2,13 @@ "Tylko administratorzy" "Banowanie osób" - "Usuwanie wiadomości" - "Wszyscy" + "Usuń wiadomości" "Zapraszanie osób i akceptowanie próśb o dołączenie" - "Moderacja członków" "Wiadomości i zawartość" "Administratorzy i moderatorzy" "Usuwanie osób i odrzucanie próśb o dołączenie" "Zmień awatar pokoju" - "Szczegóły pokoju" + "Edytuj pokój" "Zmień nazwę pokoju" "Zmień temat pokoju" "Wysyłanie wiadomości" @@ -43,12 +41,11 @@ "Tylko usuń członka" "Odbanuj" "Będą mogli ponownie dołączyć do tego pokoju, jeśli zostaną zaproszeni." - "Odbanuj użytkownika" + "Odbanuj z pokoju" "Zbanowanych" "Członków" - "Oczekujące" - "Administrator" - "Moderator" + "Tylko administratorzy" + "Administratorzy i moderatorzy" "Właściciel" "Członkowie pokoju" "Odbanowanie %1$s" @@ -61,7 +58,6 @@ "Wiadomości i zawartość" "Moderatorzy" "Właściciele" - "Uprawnienia" "Resetuj uprawnienia" "Po zresetowaniu uprawnień utracisz bieżące ustawienia." "Zresetować uprawnienia?" diff --git a/features/changeroommemberroles/impl/src/main/res/values-pt-rBR/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-pt-rBR/translations.xml similarity index 80% rename from features/changeroommemberroles/impl/src/main/res/values-pt-rBR/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-pt-rBR/translations.xml index 6d062e5a20..43fa8d83f2 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-pt-rBR/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-pt-rBR/translations.xml @@ -1,19 +1,23 @@ - "Somente administradores" + "Administradores" "Banir pessoas" + "Alterar configurações" "Remover mensagens" - "Todos" - "Convide pessoas e aceite solicitações de entrada" - "Moderação de membros" + "Membro" + "Convidar pessoas" + "Gerenciar espaço" + "Gerenciar salas" + "Gerenciar membros" "Mensagens e conteúdo" - "Administradores e moderadores" - "Remover pessoas e recusar solicitações de entrada" + "Moderador" + "Remover pessoas" "Alterar avatar da sala" - "Detalhes da sala" + "Editar detalhes" "Alterar nome da sala" "Alterar tópico da sala" "Enviar mensagens" + "Permissões" "Editar administradores" "Você não poderá desfazer essa ação. Você está promovendo o usuário a ter o mesmo nível de poder que você." "Adicionar administrador?" @@ -33,7 +37,13 @@ "Membros" "Você tem alterações não salvas." "Salvar alterações?" - "Não há usuários banidos nesta sala." + "Não há usuários banidos." + + "%1$d banido" + "%1$d banidos" + + "Confira a ortografia ou tente uma nova busca" + "Nenhum resultado para “%1$s”" "%1$d pessoa" "%1$d pessoas" @@ -42,11 +52,15 @@ "Somente remover o membro" "Desbanir" "Esta pessoa poderá entrar nesta sala novamente se for convidada." - "Desbanir usuário" + "Desbanir da sala" "Banidos" "Membros" - "Pendente" - "Administrador" + + "%1$d convidado" + "%1$d convidados" + + "Pendente" + "Administradores" "Moderador" "Proprietário" "Membros da sala" @@ -66,5 +80,6 @@ "Redefinir permissões?" "Cargos" "Detalhes da sala" + "Detalhes do espaço" "Cargos e permissões" diff --git a/features/changeroommemberroles/impl/src/main/res/values-pt/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-pt/translations.xml similarity index 90% rename from features/changeroommemberroles/impl/src/main/res/values-pt/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-pt/translations.xml index f7e93f359a..32db66ee12 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-pt/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-pt/translations.xml @@ -3,14 +3,12 @@ "Apenas administradores" "Banir pessoas" "Remover mensagens" - "Toda a gente" "Convidar pessoas e aceitar pedidos de entrada" - "Moderação de participantes" "Mensagens e conteúdo" "Administradores e moderadores" "Remover pessoas e rejeitar pedidos de entrada" "Alterar o ícone da sala" - "Detalhes da sala" + "Editar sala" "Altera o nome da sala" "Alterar a descrição da sala" "Enviar mensagens" @@ -42,12 +40,11 @@ "Remover apenas" "Anular banimento" "Poderão juntar-se novamente a esta sala se forem convidados." - "Anular banimento do utilizador" + "Desbanir da sala" "Banidos" "Participantes" - "Pendente" - "Administrador" - "Moderador" + "Apenas administradores" + "Administradores e moderadores" "Dono / Dona" "Participantes" "A anular banimento de %1$s" @@ -60,7 +57,6 @@ "Mensagens e conteúdo" "Moderadores" "Donos" - "Permissões" "Repor permissões" "Ao repores as permissões, perderás as configurações atuais." "Repor as permissões?" diff --git a/features/changeroommemberroles/impl/src/main/res/values-ro/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-ro/translations.xml similarity index 78% rename from features/changeroommemberroles/impl/src/main/res/values-ro/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-ro/translations.xml index c9e20f5a42..ac9ec710a2 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-ro/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-ro/translations.xml @@ -1,19 +1,23 @@ - "Doar administratori" + "Administrator" "Interziceți persoane" + "Modificați setările" "Ștergeți mesajele" - "Toți" - "Invitați persoane și acceptați cereri de alaturare" - "Moderarea membrilor" + "Membru" + "Invitați persoane" + "Gestionați spațiul" + "Gestionați camerele" + "Gestionați membrii" "Mesaje și conținut" - "Administratori și moderatori" - "Îndepărtați persoane și refuzați cereri de alăturare" + "Moderator" + "Îndepărtați persoane" "Schimbați avatarul camerei" - "Detaliile camerei" + "Editați detaliile" "Schimbă numele camerei" "Schimbați subiectul camerei" "Trimiteți mesaje" + "Permisiuni" "Editați administratorii" "Promovați utilizatorul să aibă același nivel de putere ca dumneavoastră. Nu veți putea anula această acțiune." "Adăugați administrator?" @@ -33,19 +37,32 @@ "Membri" "Aveți modificări nesalvate." "Salvați modificările?" - "Nu există utilizatori interziși în această cameră." + "Nu există utilizatori interziși." + + "%1$d Interzis" + "%1$d Interziși" + "%1$d Interziși" + + "Verificați ortografia sau încercați o căutare nouă" + "Niciun rezultat pentru “%1$s”" - "o persoană" + "%1$d persoană" + "%1$d persoane" "%1$d persoane" "Îndepărtați și interziceți membrul" "Doar înlăturare" "Anulare excludere" "Se vor putea alătura din nou acestei săli dacă sunt invitați." - "Anulați interzicerea utilizatorului" + "Revocati excluderea din camera" "Excluși" "Membri" - "În așteptare" + + "%1$d Invitat" + "%1$d Invitați" + "%1$d Invitați" + + "În așteptare" "Administrator" "Moderator" "Proprietar" @@ -66,5 +83,6 @@ "Resetați permisiunile?" "Roluri" "Detaliile camerei" + "Detalii spațiu" "Roluri și permisiuni" diff --git a/features/changeroommemberroles/impl/src/main/res/values-ru/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-ru/translations.xml similarity index 83% rename from features/changeroommemberroles/impl/src/main/res/values-ru/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-ru/translations.xml index 8f5e30433d..d922e58550 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-ru/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-ru/translations.xml @@ -2,18 +2,22 @@ "Только администраторы" "Блокировать людей могут" + "Изменить настройки" "Удалить сообщения" - "Все" - "Приглашать людей и принимать запросы на присоединение могут" - "Модерация участников" + "Участник" + "Пригласить людей" + "Управление пространством" + "Управление комнатами" + "Список участников" "Сообщения и содержание" - "Администраторы и модераторы" - "Удалять людей и отклонять запросы на присоединение могут" + "Модератор" + "Удалять участников" "Менять изображение комнаты могут" - "Информация о комнате" + "Редактировать комнату" "Менять название комнаты могут" "Менять тему комнаты могут" "Отправлять сообщения могут" + "Разрешения" "Редактировать роль администраторов" "Вы не сможете отменить это действие. Вы устанавливаете уровень пользователю соответствующий вашему." "Добавить администратора?" @@ -34,6 +38,8 @@ "У вас есть несохраненные изменения." "Сохранить изменения?" "В этой комнате нет заблокированных пользователей." + "Проверьте правописание или попробуйте новый поиск" + "Отсутствует результат по запросу “%1$s”" "%1$d пользователь" "%1$d пользователя" @@ -43,11 +49,11 @@ "Только удалить участника" "Разблокировать" "Они снова смогут присоединиться в эту комнату если их пригласят." - "Разбанить пользователя?" + "Разблокировать в комнате" "Заблокированные" "Участники" - "В ожидании" - "Администратор" + "В ожидании" + "Только администраторы" "Модератор" "Владелец" "Участники комнаты" @@ -67,5 +73,6 @@ "Сбросить разрешения?" "Роли" "Информация о комнате" + "Подробности о пространстве" "Роли и разрешения" diff --git a/features/changeroommemberroles/impl/src/main/res/values-sk/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-sk/translations.xml similarity index 76% rename from features/changeroommemberroles/impl/src/main/res/values-sk/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-sk/translations.xml index 8d5c6e8502..d159fb03f9 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-sk/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-sk/translations.xml @@ -1,19 +1,23 @@ - "Iba správcovia" + "Správca" "Zakázať ľudí" + "Zmeniť nastavenia" "Odstrániť správy" - "Všetci" - "Pozvite ľudí a prijmite žiadosti o pripojenie" - "Moderovanie členov" + "Člen" + "Pozvať ľudí" + "Spravovať priestor" + "Spravovať miestnosti" + "Spravovať členov" "Správy a obsah" - "Správcovia a moderátori" - "Odstrániť ľudí a odmietnuť žiadosti o pripojenie" + "Moderátor" + "Odstrániť ľudí" "Zmeniť obrázok miestnosti" - "Podrobnosti o miestnosti" + "Upraviť podrobnosti" "Zmeniť názov miestnosti" "Zmeniť tému miestnosti" "Odoslať správy" + "Povolenia" "Upraviť správcov" "Túto akciu nebudete môcť vrátiť späť. Zvyšujete úroveň používateľa na rovnakú úroveň výkonu ako máte vy." "Pridať správcu?" @@ -33,7 +37,14 @@ "Členovia" "Máte neuložené zmeny." "Uložiť zmeny?" - "V tejto miestnosti nie sú žiadni zakázaní používatelia." + "Neexistujú žiadni zablokovaní používatelia." + + "%1$d zakázaný" + "%1$d zakázaní" + "%1$d zakázaných" + + "Skontrolujte preklepy alebo skúste nové vyhľadávanie" + "Žiadne výsledky pre „%1$s“" "%1$d osoba" "%1$d osoby" @@ -43,11 +54,16 @@ "Iba odstrániť člena" "Zrušiť zákaz" "V prípade pozvania sa budú môcť znova pripojiť k tejto miestnosti." - "Zrušiť zákaz používateľa" + "Zrušiť zákaz prístupu do miestnosti" "Zakázaní" "Členovia" - "Čaká sa" - "Administrátor" + + "%1$d pozvaný" + "%1$d pozvaní" + "%1$d pozvaných" + + "Čaká na schválenie" + "Správca" "Moderátor" "Vlastník" "Členovia miestnosti" @@ -61,11 +77,12 @@ "Správy a obsah" "Moderátori" "Vlastníci" - "Oprávnenia" + "Povolenia" "Obnoviť povolenia" "Po obnovení oprávnení prídete o aktuálne nastavenia." "Obnoviť oprávnenia?" "Roly" "Podrobnosti o miestnosti" + "Podrobnosti o priestore" "Roly a povolenia" diff --git a/features/changeroommemberroles/impl/src/main/res/values-sv/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-sv/translations.xml similarity index 90% rename from features/changeroommemberroles/impl/src/main/res/values-sv/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-sv/translations.xml index a94b7384fa..d52c85de14 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-sv/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-sv/translations.xml @@ -3,14 +3,12 @@ "Endast administratörer" "Banna personer" "Ta bort meddelanden" - "Alla" "Bjuda in personer och acceptera förfrågningar om att gå med" - "Medlemsmoderering" "Meddelanden och innehåll" "Administratörer och moderatorer" "Ta bort personer och avslå förfrågningar om att gå med" "Byt rumsavatar" - "Rumsdetaljer" + "Redigera rummet" "Byt rumsnamn" "Byt rumsämne" "Skicka meddelanden" @@ -42,12 +40,11 @@ "Ta bara bort medlem" "Avbanna" "Denne kommer kunna gå med i rummet igen om denne bjuds in" - "Avbanna användare" + "Avbanna från rummet" "Bannade" "Medlemmar" - "Väntar" - "Admin" - "Moderator" + "Endast administratörer" + "Administratörer och moderatorer" "Ägare" "Rumsmedlemmar" "Avbannar %1$s" @@ -60,7 +57,6 @@ "Meddelanden och innehåll" "Moderatorer" "Ägare" - "Behörigheter" "Återställ behörigheter" "När du har återställt behörigheterna kommer du att förlora de aktuella inställningarna." "Återställ behörigheter?" diff --git a/features/changeroommemberroles/impl/src/main/res/values-tr/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-tr/translations.xml similarity index 89% rename from features/changeroommemberroles/impl/src/main/res/values-tr/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-tr/translations.xml index 923320e8de..6e53f48610 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-tr/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-tr/translations.xml @@ -3,14 +3,12 @@ "Yalnızca yöneticiler" "İnsanları yasakla" "Mesajları kaldır" - "Herkes" "Kişileri davet etme ve katılma isteklerini kabul etme" - "Üye moderasyonu" "Mesajlar ve içerik" "Yöneticiler ve moderatörler" "Kişileri kaldırma ve katılma isteklerini reddetme" "Oda resmini değiştir" - "Oda bilgileri" + "Odayı Düzenle" "Oda adını değiştir" "Oda konusunu değiştir" "Mesaj gönder" @@ -38,12 +36,10 @@ "Yalnızca üyeyi kaldır" "Yasağı Kaldır" "Davet edildikleri takdirde bu odaya tekrar katılabileceklerdir." - "Kullanıcının yasağını kaldır" "Yasaklandı" "Üyeler" - "Beklemede" - "Yönetici" - "Moderatör" + "Yalnızca yöneticiler" + "Yöneticiler ve moderatörler" "Oda üyeleri" "Yasak kaldırılıyor %1$s" "Yöneticiler" @@ -53,7 +49,6 @@ "Üye moderasyonu" "Mesajlar ve içerik" "Moderatörler" - "İzinler" "İzinleri sıfırla" "İzinleri sıfırladığınızda, mevcut ayarları kaybedersiniz." "İzinleri sıfırla?" diff --git a/features/changeroommemberroles/impl/src/main/res/values-uk/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-uk/translations.xml similarity index 91% rename from features/changeroommemberroles/impl/src/main/res/values-uk/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-uk/translations.xml index 9c7b3c74f6..9eae3a94e6 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-uk/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-uk/translations.xml @@ -3,14 +3,12 @@ "Тільки для адміністраторів" "Заблоковувати людей" "Вилучати повідомлення" - "Усі" "Запрошувати людей і приймати запити на приєднання" - "Модерація учасників" "Повідомлення та зміст" "Адміністратори та модератори" "Вилучати людей і відхиляти запити на приєднання" "Змінювати аватар кімнати" - "Деталі кімнати" + "Редагувати кімнату" "Змінювати назву кімнати" "Змінювати тему кімнати" "Надсилати повідомлення" @@ -43,12 +41,11 @@ "Лише вилучити учасника" "Розблокувати" "Вони зможуть знову приєднатися до цієї кімнати, якщо їх запросять." - "Розблокувати користувача" + "Розблокувати в кімнаті" "Заблоковані" "Учасники" - "На розгляді" - "Адміністратор" - "Модератор" + "Тільки для адміністраторів" + "Адміністратори та модератори" "Власник" "Учасники кімнати" "Розблокування %1$s" @@ -61,7 +58,6 @@ "Повідомлення та зміст" "Модератори" "Власники" - "Дозволи" "Скинути дозволи" "Після скидання дозволів ви втратите поточні налаштування." "Скинути дозволи?" diff --git a/features/changeroommemberroles/impl/src/main/res/values-ur/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-ur/translations.xml similarity index 89% rename from features/changeroommemberroles/impl/src/main/res/values-ur/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-ur/translations.xml index db49a1493b..8e6e47f4aa 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-ur/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-ur/translations.xml @@ -3,14 +3,12 @@ "صرف منتظمین" "لوگوں کو محظور کریں" "پیغامات ہٹائیں" - "ہر کوئی" "لوگوں کو مدعو کریں اور شمولیت کی درخواستیں قبول کریں" - "ارکان کا اعتدال" "پیغامات اور مواد" "منتظمین اور ناظمین" "لوگوں کو ہٹا دیں اور شمولیت کی درخواستیں مسترد کریں" "کمرے کا اوتار بدلیں" - "کمرے کی تفصیلات" + "کمرے میں ترمیم کریں" "کمرے کا نام بدلیں" "کمرے کا موضوع بدلیں" "پیغامات بھیجیں" @@ -38,12 +36,10 @@ "رکن کو صرف ہٹائیں" "غیر محظور کریں" "اگر وہ مدعو کیا جائیں تو وہ دوبارہ اس کمرے میں شامل ہوسکیں گے۔" - "صارف کو غیر محظور کریں" "محظور" "اراکین" - "زیر التواء" - "منتظم" - "ناظم" + "صرف منتظمین" + "منتظمین اور ناظمین" "کمرے کے ارکان" "%1$s کو غیر محظور کر رہا ہے" "منتظمین" @@ -53,7 +49,6 @@ "ارکان کا اعتدال" "پیغامات اور مواد" "ناظمین" - "اجازتیں" "اجازتیں بحال کریں" "ایک بار جب آپ اجازتیں بحال کردیں گے، آپ موجودہ ترتیبات کھو دیں گے۔" "اجازتیں بحال کریں؟" diff --git a/features/changeroommemberroles/impl/src/main/res/values-uz/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-uz/translations.xml similarity index 82% rename from features/changeroommemberroles/impl/src/main/res/values-uz/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-uz/translations.xml index 7d3d6d5727..03f94969b5 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-uz/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-uz/translations.xml @@ -3,27 +3,30 @@ "Faqat adminlar" "Odamlarni taqiqlash" "Xabarlarni olib tashlash" - "Har kim" "Odamlarni taklif qiling va qo‘shilish so‘rovlarini qabul qiling" - "Aʻzo moderatsiyasi" + "A’zolarni boshqarish" "Xabarlar va kontent" "Adminlar va moderatorlar" "Odamlarni olib tashlash va qoʻshilish soʻrovlarini rad etish" "Xona avatarini oʻzgartirish" - "Xona tafsilotlari" + "Tafsilotlarni tahrirlash" "Xona nomini oʻzgartirish" "Xona mavzusini almashtirish" "Xabarlar yuborish" "Administratorlarni tahrirlash" "Bu amalni bekor qila olmaysiz. Siz foydalanuvchini o‘zingiz bilan bir xil quvvat darajasiga ega bo‘lishga undayapsiz." "Admin qo‘shilsinmi?" + "Bu amalni bekor qila olmaysiz. Siz egalikni tanlangan foydalanuvchilarga o‘tkazmoqdasiz. Tark etsangiz, bu doimiy bo‘ladi." + "Egalik huquqini o‘tkazasizmi?" "Pastga tushirish" "Siz oʻzingizni imtiyozlardan mahrum qilayotganingiz sababli, bu o‘zgarishni bekor qila olmaysiz. Agar xonadagi so‘nggi imtiyozli foydalanuvchi bo‘lsangiz, imtiyozlarni qayta tiklash imkonsiz bo‘ladi." "O‘z darajangizni pasaytirmoqchimisiz?" "%1$s (Jarayonda)" "(Kutilmoqda)" "Administratorlar avtomatik ravishda moderator imtiyozlariga ega" + "Egalar avtomatik ravishda administrator huquqlariga ega." "Moderatorlarni tahrirlash" + "Egalarni tanlang" "Adminlar" "Moderatorlar" "Azolar" @@ -38,22 +41,23 @@ "Faqat aʻzoni olib tashlash" "Taqiqni bekor qilish" "Agar taklif qilinsa, ular bu xonaga qayta qo‘shilishlari mumkin." - "Foydalanuvchini blokdan chiqarish" + "Xonadan taqiqni olib tashlash" "Taqiqlangan" "Azolar" - "Kutilmoqda" - "Admin" - "Moderator" + "Faqat adminlar" + "Adminlar va moderatorlar" + "Egasi" "Xona a\'zolari" "Taqiqni bekor qilish %1$s" "Adminlar" + "Adminlar va egalari" "Rolimni o‘zgartirish" "Aʼzolikka tushirish" "Moderatorga pasaytirish" "Aʻzo moderatsiyasi" "Xabarlar va kontent" "Moderatorlar" - "Ruxsatlar" + "Egalari" "Ruxsatlarni tiklash" "Ruxsatlarni asliga qaytargach, joriy sozlamalarni yoʻqotasiz." "Ruxsatlar asliga qaytarilsinmi?" diff --git a/features/changeroommemberroles/impl/src/main/res/values-zh-rTW/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-zh-rTW/translations.xml similarity index 81% rename from features/changeroommemberroles/impl/src/main/res/values-zh-rTW/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-zh-rTW/translations.xml index 2ec5ec64c5..7bc662e2fc 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-zh-rTW/translations.xml @@ -1,19 +1,23 @@ - "僅限管理員" + "管理員" "管理黑名單" + "變更設定" "移除訊息" - "所有人" - "邀請夥伴並接受加入請求" - "成員管理" + "成員" + "邀請夥伴" + "管理空間" + "管理聊天室" + "管理成員" "訊息與內容" - "管理員和版主" - "移除夥伴並拒絕加入請求" + "版主" + "移除夥伴" "變更聊天室大頭照" - "聊天室資訊" + "編輯詳細資訊" "變更聊天室名稱" "變更聊天室主題" "傳送訊息" + "權限" "編輯管理員" "您將無法復原此動作。您正將使用者提昇至與您相同的權力等級。" "要新增管理員嗎?" @@ -33,18 +37,20 @@ "成員" "您有尚未儲存的變更" "是否儲存變更?" - "此聊天室沒有黑名單。" + "沒有被封鎖的使用者。" + "檢查拼字或嘗試新搜尋" + "找不到「%1$s」" - "%1$d 位夥伴" + "%1$d 個人" "踢出並加入黑名單" "僅移除成員" "解除黑名單" "如果收到邀請,他們能再次加入聊天室。" - "解除黑名單" + "從聊天室解除封鎖" "黑名單" "成員" - "待定" + "擱置中" "管理員" "版主" "擁有者" @@ -65,5 +71,6 @@ "確定要重設權限嗎?" "身份" "聊天室資訊" - "身份與權限" + "空間詳細資訊" + "角色與權限" diff --git a/features/changeroommemberroles/impl/src/main/res/values-zh/translations.xml b/features/rolesandpermissions/impl/src/main/res/values-zh/translations.xml similarity index 88% rename from features/changeroommemberroles/impl/src/main/res/values-zh/translations.xml rename to features/rolesandpermissions/impl/src/main/res/values-zh/translations.xml index eeea0a7b35..d2882d8ab4 100644 --- a/features/changeroommemberroles/impl/src/main/res/values-zh/translations.xml +++ b/features/rolesandpermissions/impl/src/main/res/values-zh/translations.xml @@ -3,14 +3,12 @@ "仅限管理员" "封禁成员" "移除消息" - "所有人" "邀请他人及接受加入请求" - "成员权限" "消息和内容" "管理员和协管员" "移除成员及拒绝加入请求" "更改聊天室头像" - "聊天室详情" + "编辑聊天室" "更改聊天室名称" "更改聊天室主题" "发送消息" @@ -33,7 +31,7 @@ "成员" "您有未保存的更改。" "保存更改?" - "此聊天室里没有被封禁的用户。" + "没有被封禁的用户。" "%1$d 人" @@ -41,12 +39,11 @@ "仅移除成员" "取消封禁" "如果受到邀请,他们可以重新加入聊天室。" - "解封用户" + "从房间取消解封" "已封禁用户" "成员" - "待处理" - "管理员" - "协管员" + "仅限管理员" + "管理员和协管员" "所有者" "聊天室成员" "解除封禁 %1$s" @@ -59,11 +56,11 @@ "消息和内容" "协管员" "所有者" - "权限" "重置权限" "重置权限后,您将丢失当前设置。" "重置权限?" "角色" "聊天室详情" + "空间详情" "角色与权限" diff --git a/features/changeroommemberroles/impl/src/main/res/values/localazy.xml b/features/rolesandpermissions/impl/src/main/res/values/localazy.xml similarity index 77% rename from features/changeroommemberroles/impl/src/main/res/values/localazy.xml rename to features/rolesandpermissions/impl/src/main/res/values/localazy.xml index 6e4918cde2..e5ab3f1cd7 100644 --- a/features/changeroommemberroles/impl/src/main/res/values/localazy.xml +++ b/features/rolesandpermissions/impl/src/main/res/values/localazy.xml @@ -1,19 +1,23 @@ - "Admins only" + "Admin" "Ban people" + "Change settings" "Remove messages" - "Everyone" - "Invite people and accept requests to join" - "Member moderation" + "Member" + "Invite people" + "Manage space" + "Manage rooms" + "Manage members" "Messages and content" - "Admins and moderators" - "Remove people and decline requests to join" - "Change room avatar" - "Room details" - "Change room name" - "Change room topic" + "Moderator" + "Remove people" + "Change avatar" + "Edit details" + "Change name" + "Change topic" "Send messages" + "Permissions" "Edit Admins" "You will not be able to undo this action. You are promoting the user to have the same power level as you." "Add Admin?" @@ -33,19 +37,29 @@ "Members" "You have unsaved changes." "Save changes?" - "There are no banned users in this room." - - "%1$d person" - "%1$d people" + "There are no banned users." + + "%1$d Banned" + "%1$d Banned" - "Ban from room" + "Check the spelling or try a new search" + "No results for “%1$s”" + + "%1$d Person" + "%1$d People" + + "Ban user" "Only remove member" "Unban" "They will be able to join this room again if invited." "Unban user" "Banned" "Members" - "Pending" + + "%1$d Invited" + "%1$d Invited" + + "Pending" "Admin" "Moderator" "Owner" @@ -66,5 +80,6 @@ "Reset permissions?" "Roles" "Room details" - "Roles and permissions" + "Space details" + "Roles & permissions" diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsPresenterTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenterTest.kt similarity index 68% rename from features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsPresenterTest.kt rename to features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenterTest.kt index c59931140a..ba7d47adb2 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsPresenterTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenterTest.kt @@ -1,11 +1,12 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions +package io.element.android.features.rolesandpermissions.impl.permissions import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow @@ -15,33 +16,34 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.RoomModeration import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMember.Role.Admin import io.element.android.libraries.matrix.api.room.RoomMember.Role.Moderator -import io.element.android.libraries.matrix.api.room.RoomMember.Role.User +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues +import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom +import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.defaultRoomPowerLevelValues import io.element.android.services.analytics.test.FakeAnalyticsService +import kotlinx.collections.immutable.persistentMapOf import kotlinx.coroutines.test.runTest import org.junit.Test -class ChangeBaseRoomPermissionsPresenterTest { +class ChangeRoomPermissionsPresenterTest { @Test fun `present - initial state`() = runTest { - val section = ChangeRoomPermissionsSection.RoomDetails - val presenter = createChangeRoomPermissionsPresenter(section = section) + val presenter = createChangeRoomPermissionsPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { // Initial state, no permissions loaded awaitItem().run { - assertThat(this.section).isEqualTo(section) assertThat(this.currentPermissions).isNull() - assertThat(this.items).isNotEmpty() + assertThat(this.itemsBySection).isNotEmpty() assertThat(this.hasChanges).isFalse() assertThat(this.saveAction).isEqualTo(AsyncAction.Uninitialized) - assertThat(this.confirmExitAction).isEqualTo(AsyncAction.Uninitialized) } // Updated state, permissions loaded @@ -50,42 +52,22 @@ class ChangeBaseRoomPermissionsPresenterTest { } @Test - fun `present - RoomDetails section contains the right items`() = runTest { - val section = ChangeRoomPermissionsSection.RoomDetails - val presenter = createChangeRoomPermissionsPresenter(section = section) + fun `present - items by section are correct for room`() = runTest { + val presenter = createChangeRoomPermissionsPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - assertThat(awaitUpdatedItem().items).containsExactly( + val itemsBySection = awaitUpdatedItem().itemsBySection + assertThat(itemsBySection[RoomPermissionsSection.EditDetails]).containsExactly( RoomPermissionType.ROOM_NAME, RoomPermissionType.ROOM_AVATAR, RoomPermissionType.ROOM_TOPIC, ) - } - } - - @Test - fun `present - MessagesAndContent section contains the right items`() = runTest { - val section = ChangeRoomPermissionsSection.MessagesAndContent - val presenter = createChangeRoomPermissionsPresenter(section = section) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - assertThat(awaitUpdatedItem().items).containsExactly( + assertThat(itemsBySection[RoomPermissionsSection.MessagesAndContent]).containsExactly( RoomPermissionType.SEND_EVENTS, RoomPermissionType.REDACT_EVENTS, ) - } - } - - @Test - fun `present - MembershipModeration section contains the right items`() = runTest { - val section = ChangeRoomPermissionsSection.MembershipModeration - val presenter = createChangeRoomPermissionsPresenter(section = section) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - assertThat(awaitUpdatedItem().items).containsExactly( + assertThat(itemsBySection[RoomPermissionsSection.ManageMembers]).containsExactly( RoomPermissionType.INVITE, RoomPermissionType.KICK, RoomPermissionType.BAN, @@ -93,6 +75,28 @@ class ChangeBaseRoomPermissionsPresenterTest { } } + @Test + fun `present - check canChangePermissions and selectableOptions for moderator`() = runTest { + val room = FakeJoinedRoom( + baseRoom = FakeBaseRoom( + initialRoomInfo = initialRoomInfo(role = Moderator), + powerLevelsResult = { Result.success(defaultPermissions()) } + ), + ) + val presenter = createChangeRoomPermissionsPresenter(room = room) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val state = awaitUpdatedItem() + assertThat(state.selectableRoles).containsExactly(SelectableRole.Moderator, SelectableRole.Everyone) + for (sectionItems in state.itemsBySection.values) { + for (permissionType in sectionItems) { + assertThat(state.canChangePermission(permissionType)).isTrue() + } + } + } + } + @Test fun `present - ChangeMinimumRoleForAction updates the current permissions and hasChanges`() = runTest { val presenter = createChangeRoomPermissionsPresenter() @@ -100,13 +104,13 @@ class ChangeBaseRoomPermissionsPresenterTest { presenter.present() }.test { val state = awaitUpdatedItem() - assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel) + assertThat(state.currentPermissions?.roomName).isEqualTo(Moderator.powerLevel) assertThat(state.hasChanges).isFalse() - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, SelectableRole.Admin)) awaitItem().run { - assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel) + assertThat(currentPermissions?.roomName).isEqualTo(Admin.powerLevel) assertThat(hasChanges).isTrue() } } @@ -119,29 +123,32 @@ class ChangeBaseRoomPermissionsPresenterTest { presenter.present() }.test { val state = awaitUpdatedItem() + val initialPermissions = defaultPermissions() - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, SelectableRole.Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, SelectableRole.Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, SelectableRole.Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, SelectableRole.Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, SelectableRole.Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, SelectableRole.Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, SelectableRole.Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, SelectableRole.Moderator)) - val items = cancelAndConsumeRemainingEvents() + val itemsBySection = cancelAndConsumeRemainingEvents() - (items.last() as? Event.Item)?.value?.run { + (itemsBySection.last() as? Event.Item)?.value?.run { assertThat(currentPermissions).isEqualTo( RoomPowerLevelsValues( invite = Moderator.powerLevel, kick = Moderator.powerLevel, ban = Moderator.powerLevel, + stateDefault = Moderator.powerLevel, redactEvents = Moderator.powerLevel, - sendEvents = Moderator.powerLevel, + eventsDefault = Moderator.powerLevel, roomName = Moderator.powerLevel, roomAvatar = Moderator.powerLevel, roomTopic = Moderator.powerLevel, + spaceChild = initialPermissions.spaceChild ) ) } @@ -162,17 +169,17 @@ class ChangeBaseRoomPermissionsPresenterTest { presenter.present() }.test { val state = awaitUpdatedItem() - assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel) + assertThat(state.currentPermissions?.roomName).isEqualTo(Moderator.powerLevel) assertThat(state.hasChanges).isFalse() - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, Moderator)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, User)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, Admin)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, Admin)) - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, Admin)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, SelectableRole.Admin)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, SelectableRole.Admin)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, SelectableRole.Admin)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, SelectableRole.Admin)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, SelectableRole.Admin)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, SelectableRole.Admin)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, SelectableRole.Admin)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, SelectableRole.Admin)) skipItems(7) assertThat(awaitItem().hasChanges).isTrue() @@ -181,16 +188,16 @@ class ChangeBaseRoomPermissionsPresenterTest { assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Loading) assertThat(awaitItem().hasChanges).isFalse() awaitItem().run { - assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel) - assertThat(saveAction).isEqualTo(AsyncAction.Success(Unit)) + assertThat(currentPermissions?.roomName).isEqualTo(Admin.powerLevel) + assertThat(saveAction).isEqualTo(AsyncAction.Success(true)) } assertThat(analyticsService.capturedEvents).containsExactlyElementsIn( listOf( - RoomModeration(RoomModeration.Action.ChangePermissionsRoomName, RoomModeration.Role.Moderator), - RoomModeration(RoomModeration.Action.ChangePermissionsRoomAvatar, RoomModeration.Role.Moderator), - RoomModeration(RoomModeration.Action.ChangePermissionsRoomTopic, RoomModeration.Role.Moderator), - RoomModeration(RoomModeration.Action.ChangePermissionsSendMessages, RoomModeration.Role.Moderator), - RoomModeration(RoomModeration.Action.ChangePermissionsRedactMessages, RoomModeration.Role.User), + RoomModeration(RoomModeration.Action.ChangePermissionsRoomName, RoomModeration.Role.Administrator), + RoomModeration(RoomModeration.Action.ChangePermissionsRoomAvatar, RoomModeration.Role.Administrator), + RoomModeration(RoomModeration.Action.ChangePermissionsRoomTopic, RoomModeration.Role.Administrator), + RoomModeration(RoomModeration.Action.ChangePermissionsSendMessages, RoomModeration.Role.Administrator), + RoomModeration(RoomModeration.Action.ChangePermissionsRedactMessages, RoomModeration.Role.Administrator), RoomModeration(RoomModeration.Action.ChangePermissionsKickMembers, RoomModeration.Role.Administrator), RoomModeration(RoomModeration.Action.ChangePermissionsBanMembers, RoomModeration.Role.Administrator), RoomModeration(RoomModeration.Action.ChangePermissionsInviteUsers, RoomModeration.Role.Administrator), @@ -227,17 +234,17 @@ class ChangeBaseRoomPermissionsPresenterTest { presenter.present() }.test { val state = awaitUpdatedItem() - assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel) + assertThat(state.currentPermissions?.roomName).isEqualTo(Moderator.powerLevel) assertThat(state.hasChanges).isFalse() - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, SelectableRole.Admin)) assertThat(awaitItem().hasChanges).isTrue() state.eventSink(ChangeRoomPermissionsEvent.Save) assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Loading) awaitItem().run { - assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel) + assertThat(currentPermissions?.roomName).isEqualTo(Admin.powerLevel) // Couldn't save the changes, so they're still pending assertThat(hasChanges).isTrue() assertThat(saveAction).isInstanceOf(AsyncAction.Failure::class.java) @@ -245,7 +252,7 @@ class ChangeBaseRoomPermissionsPresenterTest { state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions) awaitItem().run { - assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel) + assertThat(currentPermissions?.roomName).isEqualTo(Admin.powerLevel) assertThat(saveAction).isEqualTo(AsyncAction.Uninitialized) assertThat(hasChanges).isTrue() } @@ -259,14 +266,14 @@ class ChangeBaseRoomPermissionsPresenterTest { presenter.present() }.test { val state = awaitUpdatedItem() - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator)) + state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, SelectableRole.Admin)) assertThat(awaitItem().hasChanges).isTrue() state.eventSink(ChangeRoomPermissionsEvent.Exit) - assertThat(awaitItem().confirmExitAction).isEqualTo(AsyncAction.ConfirmingNoParams) + assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.ConfirmingCancellation) state.eventSink(ChangeRoomPermissionsEvent.Exit) - assertThat(awaitItem().confirmExitAction).isEqualTo(AsyncAction.Success(Unit)) + assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Success(false)) } } @@ -280,22 +287,30 @@ class ChangeBaseRoomPermissionsPresenterTest { state.eventSink(ChangeRoomPermissionsEvent.Exit) - assertThat(awaitItem().confirmExitAction).isEqualTo(AsyncAction.Success(Unit)) + assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Success(false)) } } private fun createChangeRoomPermissionsPresenter( - section: ChangeRoomPermissionsSection = ChangeRoomPermissionsSection.RoomDetails, room: FakeJoinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom(powerLevelsResult = { Result.success(defaultPermissions()) }), + baseRoom = FakeBaseRoom( + initialRoomInfo = initialRoomInfo(), + powerLevelsResult = { Result.success(defaultPermissions()) } + ), ), analyticsService: FakeAnalyticsService = FakeAnalyticsService(), ) = ChangeRoomPermissionsPresenter( - section = section, room = room, analyticsService = analyticsService, ) + private fun initialRoomInfo(role: RoomMember.Role = Admin) = aRoomInfo( + roomPowerLevels = RoomPowerLevels( + values = defaultPermissions(), + users = persistentMapOf(A_SESSION_ID to role.powerLevel), + ) + ) + private fun defaultPermissions() = defaultRoomPowerLevelValues() private suspend fun TurbineTestContext.awaitUpdatedItem(): ChangeRoomPermissionsState { diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsViewTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsViewTest.kt similarity index 61% rename from features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsViewTest.kt rename to features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsViewTest.kt index f4ab1ef1a9..f28c9c150f 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeBaseRoomPermissionsViewTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsViewTest.kt @@ -1,44 +1,44 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions +package io.element.android.features.rolesandpermissions.impl.permissions import androidx.activity.ComponentActivity import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule -import androidx.compose.ui.test.onAllNodesWithText -import androidx.compose.ui.test.onFirst -import androidx.compose.ui.test.performClick import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.element.android.features.roomdetails.impl.R +import io.element.android.features.rolesandpermissions.impl.R import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.ui.strings.CommonStrings -import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn -import io.element.android.tests.testutils.clickOnFirst -import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.pressBackKey +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentMapOf import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -class ChangeBaseRoomPermissionsViewTest { +class ChangeRoomPermissionsViewTest { @get:Rule val rule = createAndroidComposeRule() @Test fun `click on back icon invokes Exit`() { val recorder = EventsRecorder() rule.setChangeRoomPermissionsRule( - eventsRecorder = recorder, + state = aChangeRoomPermissionsState( + eventSink = recorder + ) ) rule.pressBack() recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) @@ -48,7 +48,9 @@ class ChangeBaseRoomPermissionsViewTest { fun `click on back key invokes Exit`() { val recorder = EventsRecorder() rule.setChangeRoomPermissionsRule( - eventsRecorder = recorder, + state = aChangeRoomPermissionsState( + eventSink = recorder + ) ) rule.pressBackKey() recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) @@ -59,11 +61,9 @@ class ChangeBaseRoomPermissionsViewTest { val recorder = EventsRecorder() rule.setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( - section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true, eventSink = recorder, ), - eventsRecorder = recorder, ) rule.pressBackKey() recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) @@ -74,12 +74,10 @@ class ChangeBaseRoomPermissionsViewTest { val recorder = EventsRecorder() rule.setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( - section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true, - confirmExitAction = AsyncAction.ConfirmingNoParams, + saveAction = AsyncAction.ConfirmingCancellation, eventSink = recorder, ), - eventsRecorder = recorder, ) rule.clickOn(CommonStrings.action_discard) recorder.assertSingle(ChangeRoomPermissionsEvent.Exit) @@ -90,14 +88,12 @@ class ChangeBaseRoomPermissionsViewTest { val recorder = EventsRecorder() rule.setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( - section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true, - confirmExitAction = AsyncAction.ConfirmingNoParams, + saveAction = AsyncAction.ConfirmingCancellation, eventSink = recorder, ), - eventsRecorder = recorder, ) - rule.clickOnFirst(CommonStrings.action_save) + rule.clickOn(CommonStrings.action_save, inDialog = true) recorder.assertSingle(ChangeRoomPermissionsEvent.Save) } @@ -105,21 +101,19 @@ class ChangeBaseRoomPermissionsViewTest { fun `click on a role item triggers ChangeRole event`() { val recorder = EventsRecorder() rule.setChangeRoomPermissionsRule( - eventsRecorder = recorder, - ) - val admins = rule.activity.getText(R.string.screen_room_change_permissions_administrators).toString() - val moderators = rule.activity.getText(R.string.screen_room_change_permissions_moderators).toString() - val users = rule.activity.getText(R.string.screen_room_change_permissions_everyone).toString() - rule.onAllNodesWithText(admins).onFirst().performClick() - rule.onAllNodesWithText(moderators).onFirst().performClick() - rule.onAllNodesWithText(users).onFirst().performClick() - recorder.assertList( - listOf( - ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.Admin), - ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.Moderator), - ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.User), + state = aChangeRoomPermissionsState( + itemsBySection = persistentMapOf( + // Makes sure there is only one item to click on + RoomPermissionsSection.EditDetails to persistentListOf(RoomPermissionType.ROOM_NAME) + ), + eventSink = recorder, ) ) + rule.clickOn(R.string.screen_room_change_permissions_room_name) + rule.clickOn(R.string.screen_room_change_permissions_everyone) + recorder.assertSingle( + ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, SelectableRole.Everyone), + ) } @Test @@ -127,11 +121,9 @@ class ChangeBaseRoomPermissionsViewTest { val recorder = EventsRecorder() rule.setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( - section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true, eventSink = recorder, ), - eventsRecorder = recorder, ) rule.clickOn(CommonStrings.action_save) recorder.assertSingle(ChangeRoomPermissionsEvent.Save) @@ -139,14 +131,27 @@ class ChangeBaseRoomPermissionsViewTest { @Test fun `a successful save exits the screen`() { - ensureCalledOnce { callback -> + ensureCalledOnceWithParam(true) { callback -> rule.setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( - section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true, - saveAction = AsyncAction.Success(Unit), + saveAction = AsyncAction.Success(true), ), - onBackClick = callback + onComplete = callback, + ) + rule.clickOn(CommonStrings.action_save) + } + } + + @Test + fun `a cancellation exits the screen`() { + ensureCalledOnceWithParam(false) { callback -> + rule.setChangeRoomPermissionsRule( + state = aChangeRoomPermissionsState( + hasChanges = true, + saveAction = AsyncAction.Success(false), + ), + onComplete = callback, ) rule.clickOn(CommonStrings.action_save) } @@ -157,12 +162,10 @@ class ChangeBaseRoomPermissionsViewTest { val recorder = EventsRecorder() rule.setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( - section = ChangeRoomPermissionsSection.RoomDetails, hasChanges = true, saveAction = AsyncAction.Failure(IllegalStateException("Failed to set room power levels")), eventSink = recorder, ), - eventsRecorder = recorder, ) rule.clickOn(CommonStrings.action_ok) recorder.assertSingle(ChangeRoomPermissionsEvent.ResetPendingActions) @@ -170,17 +173,13 @@ class ChangeBaseRoomPermissionsViewTest { } private fun AndroidComposeTestRule.setChangeRoomPermissionsRule( - eventsRecorder: EventsRecorder = EventsRecorder(expectEvents = false), - state: ChangeRoomPermissionsState = aChangeRoomPermissionsState( - section = ChangeRoomPermissionsSection.RoomDetails, - eventSink = eventsRecorder, - ), - onBackClick: () -> Unit = EnsureNeverCalled(), + state: ChangeRoomPermissionsState = aChangeRoomPermissionsState(), + onComplete: (Boolean) -> Unit = EnsureNeverCalledWithParam(), ) { setContent { ChangeRoomPermissionsView( state = state, - onBackClick = onBackClick, + onComplete = onComplete, ) } } diff --git a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesNodeTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesNodeTest.kt similarity index 78% rename from features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesNodeTest.kt rename to features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesNodeTest.kt index 5a47f52e89..f47869e20d 100644 --- a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesNodeTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesNodeTest.kt @@ -1,14 +1,15 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import com.google.common.truth.Truth.assertThat -import io.element.android.features.changeroommemberroes.api.ChangeRoomMemberRolesListType +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType import io.element.android.libraries.matrix.api.room.RoomMember import org.junit.Test diff --git a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesPresenterTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenterTest.kt similarity index 66% rename from features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesPresenterTest.kt rename to features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenterTest.kt index 41b6acd60c..8747585354 100644 --- a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesPresenterTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesPresenterTest.kt @@ -1,17 +1,16 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.RoomModeration +import io.element.android.features.rolesandpermissions.impl.RoomMemberListDataSource import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.designsystem.theme.components.SearchBarResultState @@ -19,7 +18,9 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembersState import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels +import io.element.android.libraries.matrix.api.room.toMatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.A_USER_ID_3 @@ -27,10 +28,13 @@ import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomMember +import io.element.android.libraries.matrix.test.room.anAlice import io.element.android.libraries.matrix.test.room.defaultRoomPowerLevelValues import io.element.android.libraries.previewutils.room.aRoomMemberList import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.tests.testutils.test import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableMap @@ -42,9 +46,7 @@ class ChangeRolesPresenterTest { @Test fun `present - initial state`() = runTest { val presenter = createChangeRolesPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { with(awaitItem()) { assertThat(role).isEqualTo(RoomMember.Role.Admin) assertThat(query).isNull() @@ -52,7 +54,6 @@ class ChangeRolesPresenterTest { assertThat(searchResults).isInstanceOf(SearchBarResultState.Initial::class.java) assertThat(selectedUsers).isEmpty() assertThat(hasPendingChanges).isFalse() - assertThat(exitState).isEqualTo(AsyncAction.Uninitialized) assertThat(savingState).isEqualTo(AsyncAction.Uninitialized) } cancelAndIgnoreRemainingEvents() @@ -65,16 +66,14 @@ class ChangeRolesPresenterTest { givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) } val presenter = createChangeRolesPresenter(room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) + presenter.test { + skipItems(2) assertThat(awaitItem().searchResults).isInstanceOf(SearchBarResultState.Results::class.java) } } @Test - fun `present - canChangeRole of users with lower power level unless they are owners`() = runTest { + fun `present - canChangeRole of users with lower power level unless they are owners - privilegedCreatorRole is true`() = runTest { val creatorUserId = UserId("@creator:matrix.org") val superAdminUserId = UserId("@super_admin:matrix.org") @@ -82,6 +81,7 @@ class ChangeRolesPresenterTest { // User is a creator, so they can change roles of other members. So is `creatorUserId`. givenRoomInfo( aRoomInfo( + privilegedCreatorRole = true, roomCreators = listOf(sessionId, creatorUserId), roomPowerLevels = RoomPowerLevels( defaultRoomPowerLevelValues(), @@ -99,40 +99,84 @@ class ChangeRolesPresenterTest { val roomMemberList = aRoomMemberList() + listOf( // Owner - superadmin - aRoomMember(userId = superAdminUserId, role = RoomMember.Role.Owner(isCreator = true)), + aRoomMember(userId = superAdminUserId, role = RoomMember.Role.Owner(isCreator = false)), // Owner - creator aRoomMember(userId = creatorUserId, role = RoomMember.Role.Owner(isCreator = true)) ) givenRoomMembersState(RoomMembersState.Ready(roomMemberList.toImmutableList())) } val presenter = createChangeRolesPresenter(room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) awaitItem().run { assertThat(canChangeMemberRole(A_USER_ID_2)).isTrue() // Admin assertThat(canChangeMemberRole(A_USER_ID_3)).isTrue() // Moderator + assertThat(canChangeMemberRole(superAdminUserId)).isTrue() // Super admin + assertThat(canChangeMemberRole(creatorUserId)).isFalse() // Owner + } + } + } + + @Test + fun `present - canChangeRole of users with lower power level unless they are owners - privilegedCreatorRole is false`() = runTest { + val creatorUserId = UserId("@creator:matrix.org") + val superAdminUserId = UserId("@super_admin:matrix.org") + + val room = FakeJoinedRoom().apply { + // User is a creator, so they can change roles of other members. So is `creatorUserId`. + givenRoomInfo( + aRoomInfo( + privilegedCreatorRole = false, + roomCreators = listOf(sessionId, creatorUserId), + roomPowerLevels = RoomPowerLevels( + defaultRoomPowerLevelValues(), + users = persistentMapOf( + // Creator is an admin + sessionId to RoomMember.Role.Admin.powerLevel, + creatorUserId to RoomMember.Role.Admin.powerLevel, + // bob is Admin + A_USER_ID_2 to RoomMember.Role.Admin.powerLevel, + // carol is Moderator + A_USER_ID_3 to RoomMember.Role.Moderator.powerLevel, + // super_admin is Owner - Superadmin + superAdminUserId to RoomMember.Role.Owner(isCreator = false).powerLevel, + ) + ) + ) + ) + + val roomMemberList = aRoomMemberList() + listOf( + // Owner - superadmin + aRoomMember(userId = superAdminUserId, role = RoomMember.Role.Owner(isCreator = false)), + // Owner - creator + aRoomMember(userId = creatorUserId, role = RoomMember.Role.Owner(isCreator = true)) + ) + givenRoomMembersState(RoomMembersState.Ready(roomMemberList.toImmutableList())) + } + val presenter = createChangeRolesPresenter(room = room) + presenter.test { + skipItems(1) + awaitItem().run { + assertThat(canChangeMemberRole(A_USER_ID_2)).isFalse() // Creator cannot update Admin in this case + assertThat(canChangeMemberRole(A_USER_ID_3)).isTrue() // Moderator assertThat(canChangeMemberRole(creatorUserId)).isFalse() // Owner } } } @Test - fun `present - when modifying admins, creators are displayed too`() = runTest { + fun `present - when modifying admins, creators are displayed too - privilegedCreatorRole is true`() = runTest { val room = FakeJoinedRoom().apply { val creatorUserId = UserId("@creator:matrix.org") val memberList = aRoomMemberList() .plus(aRoomMember(displayName = "CREATOR", role = RoomMember.Role.Owner(isCreator = true), userId = creatorUserId)) .toImmutableList() - givenRoomInfo(aRoomInfo(roomCreators = listOf(creatorUserId))) + givenRoomInfo(aRoomInfo(roomCreators = listOf(creatorUserId), privilegedCreatorRole = true)) givenRoomMembersState(RoomMembersState.Ready(memberList)) } val presenter = createChangeRolesPresenter(room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) + presenter.test { + skipItems(2) awaitItem().searchResults.run { assertThat(this).isInstanceOf(SearchBarResultState.Results::class.java) val results = (this as SearchBarResultState.Results).results @@ -149,9 +193,8 @@ class ChangeRolesPresenterTest { givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) } val presenter = createChangeRolesPresenter(room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { + skipItems(1) val initialState = awaitItem() initialState.eventSink(ChangeRolesEvent.ToggleSearchActive) @@ -168,9 +211,8 @@ class ChangeRolesPresenterTest { givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) } val presenter = createChangeRolesPresenter(room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { + skipItems(1) val initialState = awaitItem() val initialResults = (awaitItem().searchResults as? SearchBarResultState.Results)?.results assertThat(initialResults?.members).hasSize(8) @@ -194,10 +236,8 @@ class ChangeRolesPresenterTest { givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) } val presenter = createChangeRolesPresenter(room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) + presenter.test { + skipItems(2) val initialResults = (awaitItem().searchResults as? SearchBarResultState.Results)?.results assertThat(initialResults?.members).hasSize(8) assertThat(initialResults?.moderators).hasSize(1) @@ -216,48 +256,48 @@ class ChangeRolesPresenterTest { @Test fun `present - UserSelectionToggle adds and removes users from the selected user list`() = runTest { + val roomMemberList = aRoomMemberList() val room = FakeJoinedRoom().apply { - givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin))) + givenRoomMembersState(RoomMembersState.Ready(roomMemberList)) + givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsFromRoomMemberList(roomMemberList))) } val presenter = createChangeRolesPresenter(room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val userMember = roomMemberList.first { it.role == RoomMember.Role.User } + presenter.test { skipItems(1) val initialState = awaitItem() assertThat(initialState.selectedUsers).hasSize(1) - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) + initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(userMember.toMatrixUser())) assertThat(awaitItem().selectedUsers).hasSize(2) - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) + initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(userMember.toMatrixUser())) assertThat(awaitItem().selectedUsers).hasSize(1) } } @Test fun `present - hasPendingChanges is true when the initial selected users don't match the new ones`() = runTest { + val roomMemberList = aRoomMemberList() val room = FakeJoinedRoom().apply { - givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin))) + givenRoomMembersState(RoomMembersState.Ready(roomMemberList)) + givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsFromRoomMemberList(roomMemberList))) } val presenter = createChangeRolesPresenter(room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val userMember = roomMemberList.first { it.role == RoomMember.Role.User } + presenter.test { skipItems(1) val initialState = awaitItem() assertThat(initialState.hasPendingChanges).isFalse() assertThat(initialState.selectedUsers).hasSize(1) - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) + initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(userMember.toMatrixUser())) with(awaitItem()) { assertThat(selectedUsers).hasSize(2) assertThat(hasPendingChanges).isTrue() } - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) + initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(userMember.toMatrixUser())) with(awaitItem()) { assertThat(selectedUsers).hasSize(1) assertThat(hasPendingChanges).isFalse() @@ -266,65 +306,62 @@ class ChangeRolesPresenterTest { } @Test - fun `present - Exit will display success if no pending changes`() = runTest { + fun `present - Exit will display success false if no pending changes`() = runTest { + val roomMemberList = aRoomMemberList() val room = FakeJoinedRoom().apply { - givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin))) + givenRoomMembersState(RoomMembersState.Ready(roomMemberList)) + givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsFromRoomMemberList(roomMemberList))) } val presenter = createChangeRolesPresenter(room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() assertThat(initialState.hasPendingChanges).isFalse() - assertThat(initialState.exitState).isEqualTo(AsyncAction.Uninitialized) + assertThat(initialState.savingState).isEqualTo(AsyncAction.Uninitialized) initialState.eventSink(ChangeRolesEvent.Exit) - assertThat(awaitItem().exitState).isEqualTo(AsyncAction.Success(Unit)) + assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(false)) } } @Test - fun `present - CancelExit will remove exit confirmation`() = runTest { + fun `present - CloseDialog will remove exit confirmation`() = runTest { + val roomMemberList = aRoomMemberList() val room = FakeJoinedRoom().apply { - givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin))) + givenRoomMembersState(RoomMembersState.Ready(roomMemberList)) + givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsFromRoomMemberList(roomMemberList))) } val presenter = createChangeRolesPresenter(room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() assertThat(initialState.hasPendingChanges).isFalse() - assertThat(initialState.exitState).isEqualTo(AsyncAction.Uninitialized) + assertThat(initialState.savingState).isEqualTo(AsyncAction.Uninitialized) initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) awaitItem().eventSink(ChangeRolesEvent.Exit) val confirmingState = awaitItem() - assertThat(confirmingState.exitState).isEqualTo(AsyncAction.ConfirmingNoParams) + assertThat(confirmingState.savingState).isEqualTo(AsyncAction.ConfirmingCancellation) - confirmingState.eventSink(ChangeRolesEvent.CancelExit) - assertThat(awaitItem().exitState).isEqualTo(AsyncAction.Uninitialized) + confirmingState.eventSink(ChangeRolesEvent.CloseDialog) + assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Uninitialized) } } @Test fun `present - Exit will display a confirmation dialog if there are pending changes, calling it again will actually exit`() = runTest { + val roomMemberList = aRoomMemberList() val room = FakeJoinedRoom().apply { - givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin))) + givenRoomMembersState(RoomMembersState.Ready(roomMemberList)) + givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsFromRoomMemberList(roomMemberList))) } val presenter = createChangeRolesPresenter(room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() assertThat(initialState.hasPendingChanges).isFalse() - assertThat(initialState.exitState).isEqualTo(AsyncAction.Uninitialized) + assertThat(initialState.savingState).isEqualTo(AsyncAction.Uninitialized) initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) val updatedState = awaitItem() @@ -332,55 +369,47 @@ class ChangeRolesPresenterTest { skipItems(1) updatedState.eventSink(ChangeRolesEvent.Exit) - assertThat(awaitItem().exitState).isEqualTo(AsyncAction.ConfirmingNoParams) + assertThat(awaitItem().savingState).isEqualTo(AsyncAction.ConfirmingCancellation) updatedState.eventSink(ChangeRolesEvent.Exit) - assertThat(awaitItem().exitState).isEqualTo(AsyncAction.Success(Unit)) + assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(false)) } } @Test fun `present - Save will display a confirmation when adding admins`() = runTest { + val roomMemberList = aRoomMemberList() val room = FakeJoinedRoom( updateUserRoleResult = { Result.success(Unit) }, baseRoom = FakeBaseRoom(updateMembersResult = { Result.success(Unit) }), ).apply { - givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin))) + givenRoomMembersState(RoomMembersState.Ready(roomMemberList)) + givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsFromRoomMemberList(roomMemberList))) } val presenter = createChangeRolesPresenter(role = RoomMember.Role.Admin, room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) + presenter.test { + skipItems(2) val initialState = awaitItem() assertThat(initialState.selectedUsers).hasSize(1) - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) awaitItem().eventSink(ChangeRolesEvent.Save) val confirmingState = awaitItem() - assertThat(confirmingState.savingState).isEqualTo(AsyncAction.ConfirmingNoParams) - + assertThat(confirmingState.savingState).isEqualTo(ConfirmingModifyingAdmins) confirmingState.eventSink(ChangeRolesEvent.Save) - - val loadingState = awaitItem() - assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java) - skipItems(1) - - assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(Unit)) + assertThat(awaitItem().savingState).isInstanceOf(AsyncAction.Loading::class.java) + assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(true)) } } @Test - fun `present - CancelSave will remove the confirmation dialog`() = runTest { + fun `present - CloseDialog will remove the confirmation dialog`() = runTest { + val roomMemberList = aRoomMemberList() val room = FakeJoinedRoom().apply { - givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin))) + givenRoomMembersState(RoomMembersState.Ready(roomMemberList)) + givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsFromRoomMemberList(roomMemberList))) } val presenter = createChangeRolesPresenter(role = RoomMember.Role.Admin, room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() assertThat(initialState.selectedUsers).hasSize(1) @@ -389,9 +418,9 @@ class ChangeRolesPresenterTest { awaitItem().eventSink(ChangeRolesEvent.Save) val confirmingState = awaitItem() - assertThat(confirmingState.savingState).isEqualTo(AsyncAction.ConfirmingNoParams) + assertThat(confirmingState.savingState).isEqualTo(ConfirmingModifyingAdmins) - confirmingState.eventSink(ChangeRolesEvent.CancelSave) + confirmingState.eventSink(ChangeRolesEvent.CloseDialog) assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Uninitialized) } } @@ -399,34 +428,31 @@ class ChangeRolesPresenterTest { @Test fun `present - Save will just save the data for moderators`() = runTest { val analyticsService = FakeAnalyticsService() + val roomMemberList = aRoomMemberList() val room = FakeJoinedRoom( updateUserRoleResult = { Result.success(Unit) }, baseRoom = FakeBaseRoom(updateMembersResult = { Result.success(Unit) }), ).apply { - givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Moderator))) + givenRoomMembersState(RoomMembersState.Ready(roomMemberList)) + givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsFromRoomMemberList(roomMemberList))) } val presenter = createChangeRolesPresenter( role = RoomMember.Role.Moderator, room = room, analyticsService = analyticsService ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) + val userMember = roomMemberList.first { it.role == RoomMember.Role.User } + presenter.test { + skipItems(2) val initialState = awaitItem() assertThat(initialState.selectedUsers).hasSize(1) - - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) - - awaitItem().eventSink(ChangeRolesEvent.Save) - - val loadingState = awaitItem() - assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java) - skipItems(1) - - assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(Unit)) + initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(userMember.toMatrixUser())) + awaitItem().also { + assertThat(it.selectedUsers).hasSize(2) + it.eventSink(ChangeRolesEvent.Save) + } + assertThat(awaitItem().savingState).isInstanceOf(AsyncAction.Loading::class.java) + assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(true)) assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.ChangeMemberRole, RoomModeration.Role.Moderator)) } } @@ -454,17 +480,14 @@ class ChangeRolesPresenterTest { room = room, analyticsService = analyticsService ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) + presenter.test { val initialState = awaitItem() - assertThat(initialState.selectedUsers).hasSize(1) - + assertThat(initialState.selectedUsers).isEmpty() initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) - - awaitItem().eventSink(ChangeRolesEvent.Save) - + awaitItem().also { + assertThat(it.selectedUsers).hasSize(1) + it.eventSink(ChangeRolesEvent.Save) + } assertThat(awaitItem().savingState.isConfirming()).isTrue() } } @@ -472,80 +495,83 @@ class ChangeRolesPresenterTest { @Test fun `present - Save will just save the changes if the current user is a room creator and the selected users are not`() = runTest { val analyticsService = FakeAnalyticsService() + val alice = anAlice() + val me = aRoomMember(displayName = "CREATOR", role = RoomMember.Role.Owner(isCreator = true), userId = A_SESSION_ID) val room = FakeJoinedRoom( updateUserRoleResult = { Result.success(Unit) }, baseRoom = FakeBaseRoom(updateMembersResult = { Result.success(Unit) }), ).apply { - givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - givenRoomInfo( - aRoomInfo( - roomCreators = listOf(sessionId), - roomPowerLevels = roomPowerLevelsWithRole(role = RoomMember.Role.Admin, userId = A_USER_ID_2) - ) - ) + val roomMemberList = persistentListOf(alice, me) + givenRoomMembersState(RoomMembersState.Ready(roomMemberList)) } val presenter = createChangeRolesPresenter( role = RoomMember.Role.Admin, room = room, analyticsService = analyticsService ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) + presenter.test { + skipItems(2) val initialState = awaitItem() - assertThat(initialState.selectedUsers).hasSize(1) - - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) - - awaitItem().eventSink(ChangeRolesEvent.Save) - + assertThat(initialState.selectedUsers).hasSize(2) + initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(alice.toMatrixUser())) + awaitItem().also { + assertThat(it.selectedUsers).hasSize(1) + it.eventSink(ChangeRolesEvent.Save) + } val loadingState = awaitItem() assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java) - skipItems(1) - - assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(Unit)) + assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(true)) assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.ChangeMemberRole, RoomModeration.Role.User)) } } @Test - fun `present - Save can handle failures and ClearError clears them`() = runTest { + fun `present - Save can handle failures and CloseDialog clears them`() = runTest { + val roomMemberList = aRoomMemberList() val room = FakeJoinedRoom( updateUserRoleResult = { Result.failure(IllegalStateException("Failed")) } ).apply { - givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(role = RoomMember.Role.Moderator, userId = A_USER_ID))) + givenRoomMembersState(RoomMembersState.Ready(roomMemberList)) + givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsFromRoomMemberList(roomMemberList))) } val presenter = createChangeRolesPresenter(role = RoomMember.Role.Moderator, room = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) + val userMember = roomMemberList.first { it.role == RoomMember.Role.User } + presenter.test { + skipItems(2) val initialState = awaitItem() assertThat(initialState.selectedUsers).hasSize(1) - - initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2))) - - awaitItem().eventSink(ChangeRolesEvent.Save) + initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(userMember.toMatrixUser())) + awaitItem().also { + assertThat(it.selectedUsers).hasSize(2) + it.eventSink(ChangeRolesEvent.Save) + } val loadingState = awaitItem() assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java) - skipItems(1) val failedState = awaitItem() assertThat(failedState.savingState).isInstanceOf(AsyncAction.Failure::class.java) - - failedState.eventSink(ChangeRolesEvent.ClearError) + failedState.eventSink(ChangeRolesEvent.CloseDialog) assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Uninitialized) } } - private fun roomPowerLevelsWithRole( - role: RoomMember.Role, - userId: UserId = A_USER_ID, + @Test + fun `test analytics mapping`() = runTest { + val presenter = createChangeRolesPresenter() + with(presenter) { + assertThat(RoomMember.Role.User.toAnalyticsMemberRole()).isEqualTo(RoomModeration.Role.User) + assertThat(RoomMember.Role.Moderator.toAnalyticsMemberRole()).isEqualTo(RoomModeration.Role.Moderator) + assertThat(RoomMember.Role.Admin.toAnalyticsMemberRole()).isEqualTo(RoomModeration.Role.Administrator) + assertThat(RoomMember.Role.Owner(isCreator = false).toAnalyticsMemberRole()).isEqualTo(RoomModeration.Role.Administrator) + assertThat(RoomMember.Role.Owner(isCreator = true).toAnalyticsMemberRole()).isEqualTo(RoomModeration.Role.Administrator) + } + } + + private fun roomPowerLevelsFromRoomMemberList( + roomMemberList: List, ): RoomPowerLevels { return RoomPowerLevels( values = defaultRoomPowerLevelValues(), - users = persistentMapOf(userId to role.powerLevel) + users = roomMemberList.associate { it.userId to it.role.powerLevel }.toImmutableMap() ) } @@ -566,7 +592,8 @@ internal fun TestScope.createChangeRolesPresenter( return ChangeRolesPresenter( role = role, room = room, - dispatchers = dispatchers, + dataSource = RoomMemberListDataSource(room, dispatchers), analyticsService = analyticsService, + roomCoroutineScope = this, ) } diff --git a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesViewTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesViewTest.kt similarity index 86% rename from features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesViewTest.kt rename to features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesViewTest.kt index fb1dc38aae..39967f9160 100644 --- a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/ChangeRolesViewTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesViewTest.kt @@ -1,11 +1,12 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import androidx.activity.ComponentActivity import androidx.compose.ui.test.junit4.AndroidComposeTestRule @@ -23,7 +24,6 @@ import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.toMatrixUser import io.element.android.libraries.matrix.ui.components.aMatrixUserList import io.element.android.libraries.ui.strings.CommonStrings -import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn @@ -50,7 +50,6 @@ class ChangeRolesViewTest { ), ) }.exceptionOrNull() - assertThat(exception).isNotNull() } @@ -63,9 +62,7 @@ class ChangeRolesViewTest { eventSink = eventsRecorder, ), ) - rule.pressBackKey() - eventsRecorder.assertSingle(ChangeRolesEvent.ToggleSearchActive) } @@ -78,9 +75,7 @@ class ChangeRolesViewTest { eventSink = eventsRecorder, ), ) - rule.pressBackKey() - eventsRecorder.assertList(listOf(ChangeRolesEvent.QueryChanged(""), ChangeRolesEvent.Exit)) } @@ -93,9 +88,7 @@ class ChangeRolesViewTest { eventSink = eventsRecorder, ), ) - rule.pressBack() - eventsRecorder.assertList(listOf(ChangeRolesEvent.QueryChanged(""), ChangeRolesEvent.Exit)) } @@ -108,9 +101,7 @@ class ChangeRolesViewTest { eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_save) - eventsRecorder.assertList(listOf(ChangeRolesEvent.QueryChanged(""), ChangeRolesEvent.Save)) } @@ -123,58 +114,50 @@ class ChangeRolesViewTest { eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_save) - eventsRecorder.assertList(listOf(ChangeRolesEvent.QueryChanged(""))) } @Test - fun `exit confirmation dialog - submit exits the screen`() { + fun `exit confirmation dialog - discard exits the screen`() { val eventsRecorder = EventsRecorder() rule.setChangeRolesContent( state = aChangeRolesState( isSearchActive = true, - exitState = AsyncAction.ConfirmingNoParams, + savingState = AsyncAction.ConfirmingCancellation, eventSink = eventsRecorder, ), ) - - rule.clickOn(CommonStrings.action_ok) - + rule.clickOn(CommonStrings.action_discard) eventsRecorder.assertSingle(ChangeRolesEvent.Exit) } @Test - fun `exit confirmation dialog - cancel removes the dialog`() { + fun `exit confirmation dialog - save emits the save event`() { val eventsRecorder = EventsRecorder() rule.setChangeRolesContent( state = aChangeRolesState( isSearchActive = true, - exitState = AsyncAction.ConfirmingNoParams, + savingState = AsyncAction.ConfirmingCancellation, eventSink = eventsRecorder, ), ) - - rule.clickOn(CommonStrings.action_cancel) - - eventsRecorder.assertSingle(ChangeRolesEvent.CancelExit) + rule.clickOn(CommonStrings.action_save) + eventsRecorder.assertSingle(ChangeRolesEvent.Save) } @Test - fun `save confirmation dialog - submit saves the changes`() { + fun `save admins confirmation dialog - submit saves the changes`() { val eventsRecorder = EventsRecorder() rule.setChangeRolesContent( state = aChangeRolesState( role = RoomMember.Role.Admin, isSearchActive = true, - savingState = AsyncAction.ConfirmingNoParams, + savingState = ConfirmingModifyingAdmins, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_ok) - eventsRecorder.assertSingle(ChangeRolesEvent.Save) } @@ -185,31 +168,42 @@ class ChangeRolesViewTest { state = aChangeRolesState( role = RoomMember.Role.Owner(isCreator = false), isSearchActive = true, - savingState = AsyncAction.ConfirmingNoParams, + savingState = ConfirmingModifyingOwners, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_continue) - eventsRecorder.assertSingle(ChangeRolesEvent.Save) } @Test - fun `save confirmation dialog - cancel removes the dialog`() { + fun `save admins confirmation dialog - cancel removes the dialog`() { val eventsRecorder = EventsRecorder() rule.setChangeRolesContent( state = aChangeRolesState( role = RoomMember.Role.Admin, isSearchActive = true, - savingState = AsyncAction.ConfirmingNoParams, + savingState = ConfirmingModifyingAdmins, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_cancel) + eventsRecorder.assertSingle(ChangeRolesEvent.CloseDialog) + } - eventsRecorder.assertSingle(ChangeRolesEvent.ClearError) + @Test + fun `save owners confirmation dialog - cancel removes the dialog`() { + val eventsRecorder = EventsRecorder() + rule.setChangeRolesContent( + state = aChangeRolesState( + role = RoomMember.Role.Owner(isCreator = false), + isSearchActive = true, + savingState = ConfirmingModifyingOwners, + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.action_cancel) + eventsRecorder.assertSingle(ChangeRolesEvent.CloseDialog) } @Test @@ -222,10 +216,8 @@ class ChangeRolesViewTest { eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_ok) - - eventsRecorder.assertSingle(ChangeRolesEvent.ClearError) + eventsRecorder.assertSingle(ChangeRolesEvent.CloseDialog) } @Test @@ -306,12 +298,10 @@ class ChangeRolesViewTest { private fun AndroidComposeTestRule.setChangeRolesContent( state: ChangeRolesState, - onBackClick: () -> Unit = EnsureNeverCalled(), ) { setContent { ChangeRolesView( state = state, - navigateUp = onBackClick, ) } } diff --git a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/DefaultChangeRoomMemberRolesEntyPointTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/DefaultChangeRoomMemberRolesEntyPointTest.kt similarity index 77% rename from features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/DefaultChangeRoomMemberRolesEntyPointTest.kt rename to features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/DefaultChangeRoomMemberRolesEntyPointTest.kt index 621af8edaf..7d190b85f4 100644 --- a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/DefaultChangeRoomMemberRolesEntyPointTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/DefaultChangeRoomMemberRolesEntyPointTest.kt @@ -1,16 +1,17 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import androidx.test.ext.junit.runners.AndroidJUnit4 import com.bumble.appyx.core.modality.BuildContext import com.google.common.truth.Truth.assertThat -import io.element.android.features.changeroommemberroes.api.ChangeRoomMemberRolesListType +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.tests.testutils.node.TestParentNode import kotlinx.coroutines.test.runTest @@ -31,10 +32,12 @@ class DefaultChangeRoomMemberRolesEntyPointTest { } val room = FakeJoinedRoom() val listType = ChangeRoomMemberRolesListType.Admins - val result = entryPoint.builder(parentNode, BuildContext.root(null)) - .room(FakeJoinedRoom()) - .listType(listType) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + room = FakeJoinedRoom(), + listType = listType, + ) assertThat(result).isInstanceOf(ChangeRoomMemberRolesRootNode::class.java) // Search for the Inputs plugin val input = result.plugins.filterIsInstance().single() diff --git a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/MembersByRoleTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/MembersByRoleTest.kt similarity index 83% rename from features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/MembersByRoleTest.kt rename to features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/MembersByRoleTest.kt index a2e134f807..66a06cddc3 100644 --- a/features/changeroommemberroles/impl/src/test/kotlin/io/element/android/features/changeroommemberroles/impl/MembersByRoleTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/MembersByRoleTest.kt @@ -1,11 +1,12 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.changeroommemberroles.impl +package io.element.android.features.rolesandpermissions.impl.roles import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.room.RoomMember @@ -17,6 +18,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID_5 import io.element.android.libraries.matrix.test.A_USER_ID_6 import io.element.android.libraries.matrix.test.A_USER_ID_7 import io.element.android.libraries.matrix.test.room.aRoomMember +import io.element.android.libraries.matrix.ui.room.PowerLevelRoomMemberComparator import kotlinx.collections.immutable.persistentListOf import org.junit.Test @@ -32,7 +34,7 @@ class MembersByRoleTest { aRoomMember(A_USER_ID_6, displayName = "Justin", role = RoomMember.Role.Owner(isCreator = true)), aRoomMember(A_USER_ID_7, displayName = "Mallory", role = RoomMember.Role.Owner(isCreator = false)), ) - val membersByRole = MembersByRole(members = members) + val membersByRole = MembersByRole(members = members, comparator = PowerLevelRoomMemberComparator()) assertThat(membersByRole.owners).containsExactly( aRoomMember(A_USER_ID_6, displayName = "Justin", role = RoomMember.Role.Owner(isCreator = true)), aRoomMember(A_USER_ID_7, displayName = "Mallory", role = RoomMember.Role.Owner(isCreator = false)), @@ -51,37 +53,25 @@ class MembersByRoleTest { @Test fun `isEmpty - only returns true with no members of any role`() { - val emptyMembersByRole = MembersByRole(emptyList()) + val emptyMembersByRole = MembersByRole() assertThat(emptyMembersByRole.isEmpty()).isTrue() val membersByRoleWithOwners = MembersByRole( owners = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.Admin)), - admins = persistentListOf(), - moderators = persistentListOf(), - members = persistentListOf(), ) assertThat(membersByRoleWithOwners.isEmpty()).isFalse() val membersByRoleWithAdmins = MembersByRole( - owners = persistentListOf(), admins = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.Admin)), - moderators = persistentListOf(), - members = persistentListOf(), ) assertThat(membersByRoleWithAdmins.isEmpty()).isFalse() val membersByRoleWithModerators = MembersByRole( - owners = persistentListOf(), - admins = persistentListOf(), moderators = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.Moderator)), - members = persistentListOf(), ) assertThat(membersByRoleWithModerators.isEmpty()).isFalse() val membersByRoleWithMembers = MembersByRole( - owners = persistentListOf(), - admins = persistentListOf(), - moderators = persistentListOf(), members = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.User)), ) assertThat(membersByRoleWithMembers.isEmpty()).isFalse() diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionPresenterTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionPresenterTest.kt similarity index 76% rename from features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionPresenterTest.kt rename to features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionPresenterTest.kt index 734cf4696a..54bd6b7987 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionPresenterTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionPresenterTest.kt @@ -1,22 +1,24 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions +package io.element.android.features.rolesandpermissions.impl.root -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.RoomModeration import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.room.RoomMembersState +import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom +import io.element.android.libraries.matrix.test.room.aRoomMemberList import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.tests.testutils.test import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher @@ -29,12 +31,10 @@ class RolesAndPermissionPresenterTest { @Test fun `present - initial state`() = runTest { val presenter = createRolesAndPermissionsPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { with(awaitItem()) { - assertThat(adminCount).isEqualTo(0) - assertThat(moderatorCount).isEqualTo(0) + assertThat(adminCount).isNull() + assertThat(moderatorCount).isNull() assertThat(changeOwnRoleAction).isEqualTo(AsyncAction.Uninitialized) } } @@ -43,12 +43,9 @@ class RolesAndPermissionPresenterTest { @Test fun `present - ChangeOwnRole presents a confirmation dialog`() = runTest { val presenter = createRolesAndPermissionsPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() initialState.eventSink(RolesAndPermissionsEvents.ChangeOwnRole) - assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.ConfirmingNoParams) } } @@ -59,12 +56,11 @@ class RolesAndPermissionPresenterTest { val presenter = createRolesAndPermissionsPresenter( dispatchers = testCoroutineDispatchers(), room = FakeJoinedRoom( + baseRoom = FakeBaseRoom(updateMembersResult = {}), updateUserRoleResult = { Result.success(Unit) } ), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() initialState.eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator)) @@ -80,12 +76,11 @@ class RolesAndPermissionPresenterTest { @Test fun `present - DemoteSelfTo can handle failures and clean them`() = runTest(StandardTestDispatcher()) { val room = FakeJoinedRoom( + baseRoom = FakeBaseRoom(updateMembersResult = {}), updateUserRoleResult = { Result.failure(Exception("Failed to update role")) } ) val presenter = createRolesAndPermissionsPresenter(room = room, dispatchers = testCoroutineDispatchers()) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() initialState.eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator)) @@ -103,9 +98,7 @@ class RolesAndPermissionPresenterTest { @Test fun `present - CancelPendingAction dismisses confirmation dialog too`() = runTest { val presenter = createRolesAndPermissionsPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() initialState.eventSink(RolesAndPermissionsEvents.ChangeOwnRole) awaitItem().eventSink(RolesAndPermissionsEvents.CancelPendingAction) @@ -120,12 +113,11 @@ class RolesAndPermissionPresenterTest { val presenter = createRolesAndPermissionsPresenter( analyticsService = analyticsService, room = FakeJoinedRoom( + baseRoom = FakeBaseRoom(updateMembersResult = {}), resetPowerLevelsResult = { Result.success(Unit) } ) ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() initialState.eventSink(RolesAndPermissionsEvents.ResetPermissions) // Confirmation @@ -140,9 +132,7 @@ class RolesAndPermissionPresenterTest { @Test fun `present - ResetPermissions confirmation can be cancelled`() = runTest { val presenter = createRolesAndPermissionsPresenter() - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { val initialState = awaitItem() initialState.eventSink(RolesAndPermissionsEvents.ResetPermissions) awaitItem().eventSink(RolesAndPermissionsEvents.CancelPendingAction) @@ -151,8 +141,26 @@ class RolesAndPermissionPresenterTest { } } + @Test + fun `present - admins and moderator counts are updated when members changes`() = runTest { + val room = FakeJoinedRoom( + baseRoom = FakeBaseRoom(updateMembersResult = {}), + ) + val presenter = createRolesAndPermissionsPresenter(room = room) + presenter.test { + val initialState = awaitItem() + assertThat(initialState.adminCount).isNull() + assertThat(initialState.moderatorCount).isNull() + room.givenRoomMembersState(state = RoomMembersState.Ready(aRoomMemberList())) + skipItems(1) + val finalState = awaitItem() + assertThat(finalState.adminCount).isEqualTo(1) + assertThat(finalState.moderatorCount).isEqualTo(1) + } + } + private fun TestScope.createRolesAndPermissionsPresenter( - room: FakeJoinedRoom = FakeJoinedRoom(), + room: FakeJoinedRoom = FakeJoinedRoom(baseRoom = FakeBaseRoom(updateMembersResult = {})), dispatchers: CoroutineDispatchers = testCoroutineDispatchers(), analyticsService: FakeAnalyticsService = FakeAnalyticsService() ): RolesAndPermissionsPresenter { diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsViewTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsViewTest.kt similarity index 89% rename from features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsViewTest.kt rename to features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsViewTest.kt index 4aa68bc047..e08ae205b7 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsViewTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/root/RolesAndPermissionsViewTest.kt @@ -1,17 +1,18 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.rolesandpermissions +package io.element.android.features.rolesandpermissions.impl.root import androidx.activity.ComponentActivity import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.element.android.features.roomdetails.impl.R +import io.element.android.features.rolesandpermissions.impl.R import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.ui.strings.CommonStrings @@ -83,14 +84,12 @@ class RolesAndPermissionsViewTest { @Test @Config(qualifiers = "h640dp") - fun `tapping on any of the permission items open the change permissions screen`() { - ensureCalledTimes(3) { callback -> + fun `tapping permission item open the change permissions screen`() { + ensureCalledTimes(1) { callback -> rule.setRolesAndPermissionsView( - openPermissionScreens = callback, + openEditPermissions = callback, ) - rule.clickOn(R.string.screen_room_roles_and_permissions_room_details) - rule.clickOn(R.string.screen_room_roles_and_permissions_messages_and_content) - rule.clickOn(R.string.screen_room_roles_and_permissions_member_moderation) + rule.clickOn(R.string.screen_room_roles_and_permissions_permissions_header) } } @@ -184,7 +183,7 @@ private fun AndroidComposeTestRule.setRoles goBack: () -> Unit = EnsureNeverCalled(), openAdminList: () -> Unit = EnsureNeverCalled(), openModeratorList: () -> Unit = EnsureNeverCalled(), - openPermissionScreens: () -> Unit = EnsureNeverCalled(), + openEditPermissions: () -> Unit = EnsureNeverCalled(), ) { setSafeContent { RolesAndPermissionsView( @@ -193,9 +192,7 @@ private fun AndroidComposeTestRule.setRoles override fun onBackClick() = goBack() override fun openAdminList() = openAdminList() override fun openModeratorList() = openModeratorList() - override fun openEditRoomDetailsPermissions() = openPermissionScreens() - override fun openModerationPermissions() = openPermissionScreens() - override fun openMessagesAndContentPermissions() = openPermissionScreens() + override fun openEditPermissions() = openEditPermissions() } ) } diff --git a/features/rolesandpermissions/test/build.gradle.kts b/features/rolesandpermissions/test/build.gradle.kts new file mode 100644 index 0000000000..f2b5d05133 --- /dev/null +++ b/features/rolesandpermissions/test/build.gradle.kts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.rolesandpermissions.test" +} + +dependencies { + implementation(projects.features.rolesandpermissions.api) + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) + implementation(projects.tests.testutils) +} diff --git a/features/rolesandpermissions/test/src/main/kotlin/io/element/android/features/changeroommemberroles/test/FakeChangeRoomMemberRolesEntryPoint.kt b/features/rolesandpermissions/test/src/main/kotlin/io/element/android/features/changeroommemberroles/test/FakeChangeRoomMemberRolesEntryPoint.kt new file mode 100644 index 0000000000..0526afc938 --- /dev/null +++ b/features/rolesandpermissions/test/src/main/kotlin/io/element/android/features/changeroommemberroles/test/FakeChangeRoomMemberRolesEntryPoint.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.changeroommemberroles.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType +import io.element.android.libraries.matrix.api.room.JoinedRoom +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeChangeRoomMemberRolesEntryPoint : ChangeRoomMemberRolesEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + room: JoinedRoom, + listType: ChangeRoomMemberRolesListType, + ): Node { + lambdaError() + } +} diff --git a/features/rolesandpermissions/test/src/main/kotlin/io/element/android/features/changeroommemberroles/test/FakeRolesAndPermissionsEntryPoint.kt b/features/rolesandpermissions/test/src/main/kotlin/io/element/android/features/changeroommemberroles/test/FakeRolesAndPermissionsEntryPoint.kt new file mode 100644 index 0000000000..62cf3285f3 --- /dev/null +++ b/features/rolesandpermissions/test/src/main/kotlin/io/element/android/features/changeroommemberroles/test/FakeRolesAndPermissionsEntryPoint.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.changeroommemberroles.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.rolesandpermissions.api.RolesAndPermissionsEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeRolesAndPermissionsEntryPoint : RolesAndPermissionsEntryPoint { + override fun createNode(parentNode: Node, buildContext: BuildContext, callback: RolesAndPermissionsEntryPoint.Callback): Node { + lambdaError() + } +} diff --git a/features/roomaliasresolver/api/build.gradle.kts b/features/roomaliasresolver/api/build.gradle.kts index e21d7e7862..2c4184e534 100644 --- a/features/roomaliasresolver/api/build.gradle.kts +++ b/features/roomaliasresolver/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomaliasresolver/api/src/main/kotlin/io/element/android/features/roomaliasesolver/api/RoomAliasResolverEntryPoint.kt b/features/roomaliasresolver/api/src/main/kotlin/io/element/android/features/roomaliasesolver/api/RoomAliasResolverEntryPoint.kt index 420bcfa97c..ce0270033c 100644 --- a/features/roomaliasresolver/api/src/main/kotlin/io/element/android/features/roomaliasesolver/api/RoomAliasResolverEntryPoint.kt +++ b/features/roomaliasresolver/api/src/main/kotlin/io/element/android/features/roomaliasesolver/api/RoomAliasResolverEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,13 +17,12 @@ import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias interface RoomAliasResolverEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - - interface NodeBuilder { - fun callback(callback: Callback): NodeBuilder - fun params(params: Params): NodeBuilder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: Params, + callback: Callback, + ): Node interface Callback : Plugin { fun onAliasResolved(data: ResolvedRoomAlias) diff --git a/features/roomaliasresolver/impl/build.gradle.kts b/features/roomaliasresolver/impl/build.gradle.kts index 45cf32f661..1860c77017 100644 --- a/features/roomaliasresolver/impl/build.gradle.kts +++ b/features/roomaliasresolver/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/DefaultRoomAliasResolverEntryPoint.kt b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/DefaultRoomAliasResolverEntryPoint.kt index 9c2d3d2cdf..c6f8966131 100644 --- a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/DefaultRoomAliasResolverEntryPoint.kt +++ b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/DefaultRoomAliasResolverEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,33 +10,22 @@ package io.element.android.features.roomaliasresolver.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultRoomAliasResolverEntryPoint : RoomAliasResolverEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): RoomAliasResolverEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : RoomAliasResolverEntryPoint.NodeBuilder { - override fun callback(callback: RoomAliasResolverEntryPoint.Callback): RoomAliasResolverEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun params(params: RoomAliasResolverEntryPoint.Params): RoomAliasResolverEntryPoint.NodeBuilder { - plugins += params - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: RoomAliasResolverEntryPoint.Params, + callback: RoomAliasResolverEntryPoint.Callback, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf(params, callback), + ) } } diff --git a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverEvents.kt b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverEvents.kt index 34a96eaeb8..11b92ba244 100644 --- a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverEvents.kt +++ b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverNode.kt b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverNode.kt index d7b3242def..041d43cb26 100644 --- a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverNode.kt +++ b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,14 +13,13 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.roomaliasesolver.api.RoomAliasResolverEntryPoint +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope -import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias @ContributesNode(SessionScope::class) @AssistedInject @@ -28,22 +28,19 @@ class RoomAliasResolverNode( @Assisted plugins: List, presenterFactory: RoomAliasResolverPresenter.Factory, ) : Node(buildContext, plugins = plugins) { + private val callback: RoomAliasResolverEntryPoint.Callback = callback() private val inputs = inputs() private val presenter = presenterFactory.create( inputs.roomAlias ) - private fun onAliasResolved(data: ResolvedRoomAlias) { - plugins().forEach { it.onAliasResolved(data) } - } - @Composable override fun View(modifier: Modifier) { val state = presenter.present() RoomAliasResolverView( state = state, - onSuccess = ::onAliasResolved, + onSuccess = callback::onAliasResolved, onBackClick = ::navigateUp, modifier = modifier ) diff --git a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverPresenter.kt b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverPresenter.kt index c8a8d18fbc..78be168e53 100644 --- a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverPresenter.kt +++ b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -44,7 +45,7 @@ class RoomAliasResolverPresenter( resolveAlias(resolveState) } - fun handleEvents(event: RoomAliasResolverEvents) { + fun handleEvent(event: RoomAliasResolverEvents) { when (event) { RoomAliasResolverEvents.Retry -> coroutineScope.resolveAlias(resolveState) RoomAliasResolverEvents.DismissError -> resolveState.value = AsyncData.Uninitialized @@ -54,7 +55,7 @@ class RoomAliasResolverPresenter( return RoomAliasResolverState( roomAlias = roomAlias, resolveState = resolveState.value, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverState.kt b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverState.kt index dc858621f7..82d13f16a5 100644 --- a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverState.kt +++ b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverState.kt @@ -1,18 +1,17 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.roomaliasresolver.impl -import androidx.compose.runtime.Immutable import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias -@Immutable data class RoomAliasResolverState( val roomAlias: RoomAlias, val resolveState: AsyncData, diff --git a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverStateProvider.kt b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverStateProvider.kt index 49786b6436..c3152bc303 100644 --- a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverStateProvider.kt +++ b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverView.kt b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverView.kt index 9d7536b2f5..a068ac7a7f 100644 --- a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverView.kt +++ b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasResolverView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/di/RoomAliasResolverModule.kt b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/di/RoomAliasResolverModule.kt index cce26ec602..8f5c9d733b 100644 --- a/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/di/RoomAliasResolverModule.kt +++ b/features/roomaliasresolver/impl/src/main/kotlin/io/element/android/features/roomaliasresolver/impl/di/RoomAliasResolverModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomaliasresolver/impl/src/main/res/values-hr/translations.xml b/features/roomaliasresolver/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..2d1e42c405 --- /dev/null +++ b/features/roomaliasresolver/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,5 @@ + + + "Nismo mogli prikazati pregled ove sobe" + "Nije uspjelo razrješavanje aliasa sobe." + diff --git a/features/roomaliasresolver/impl/src/main/res/values-uz/translations.xml b/features/roomaliasresolver/impl/src/main/res/values-uz/translations.xml index 0499fed4f5..d79cdadcc2 100644 --- a/features/roomaliasresolver/impl/src/main/res/values-uz/translations.xml +++ b/features/roomaliasresolver/impl/src/main/res/values-uz/translations.xml @@ -1,4 +1,5 @@ + "Biz bu xonani oldindan ko‘rishni ko‘rsata olmadik " "Xona taxalluslari yechilmadi." diff --git a/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/DefaultRoomAliasResolverEntryPointTest.kt b/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/DefaultRoomAliasResolverEntryPointTest.kt index 238b35017b..a519032ed2 100644 --- a/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/DefaultRoomAliasResolverEntryPointTest.kt +++ b/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/DefaultRoomAliasResolverEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -43,10 +44,12 @@ class DefaultRoomAliasResolverEntryPointTest { val params = RoomAliasResolverEntryPoint.Params( roomAlias = A_ROOM_ALIAS ) - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .params(params) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + params = params, + callback = callback, + ) assertThat(result).isInstanceOf(RoomAliasResolverNode::class.java) assertThat(result.plugins).contains(params) assertThat(result.plugins).contains(callback) diff --git a/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperPresenterTest.kt b/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperPresenterTest.kt index f90b07c91b..df8b8058be 100644 --- a/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperPresenterTest.kt +++ b/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperViewTest.kt b/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperViewTest.kt index 717380d23d..4b37f993f9 100644 --- a/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperViewTest.kt +++ b/features/roomaliasresolver/impl/src/test/kotlin/io/element/android/features/roomaliasresolver/impl/RoomAliasHelperViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomcall/api/build.gradle.kts b/features/roomcall/api/build.gradle.kts index 2b9a9124a3..54bb7bbd02 100644 --- a/features/roomcall/api/build.gradle.kts +++ b/features/roomcall/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomcall/api/src/main/kotlin/io/element/android/features/roomcall/api/RoomCallState.kt b/features/roomcall/api/src/main/kotlin/io/element/android/features/roomcall/api/RoomCallState.kt index 725915b91a..1a6b17ec89 100644 --- a/features/roomcall/api/src/main/kotlin/io/element/android/features/roomcall/api/RoomCallState.kt +++ b/features/roomcall/api/src/main/kotlin/io/element/android/features/roomcall/api/RoomCallState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomcall/api/src/main/kotlin/io/element/android/features/roomcall/api/RoomCallStateProvider.kt b/features/roomcall/api/src/main/kotlin/io/element/android/features/roomcall/api/RoomCallStateProvider.kt index de3bc4ade8..be86c2441d 100644 --- a/features/roomcall/api/src/main/kotlin/io/element/android/features/roomcall/api/RoomCallStateProvider.kt +++ b/features/roomcall/api/src/main/kotlin/io/element/android/features/roomcall/api/RoomCallStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomcall/impl/build.gradle.kts b/features/roomcall/impl/build.gradle.kts index 7a6a3cf0a6..069c890874 100644 --- a/features/roomcall/impl/build.gradle.kts +++ b/features/roomcall/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt b/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt index 2401606f34..b18a2772a3 100644 --- a/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt +++ b/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -20,7 +21,8 @@ import io.element.android.features.enterprise.api.SessionEnterpriseService import io.element.android.features.roomcall.api.RoomCallState import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.room.JoinedRoom -import io.element.android.libraries.matrix.ui.room.canCall +import io.element.android.libraries.matrix.api.room.powerlevels.canCall +import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState @Inject class RoomCallStatePresenter( @@ -34,8 +36,7 @@ class RoomCallStatePresenter( value = sessionEnterpriseService.isElementCallAvailable() } val roomInfo by room.roomInfoFlow.collectAsState() - val syncUpdateFlow = room.syncUpdateFlow.collectAsState() - val canJoinCall by room.canCall(updateKey = syncUpdateFlow.value) + val canJoinCall by room.permissionsAsState(false) { perms -> perms.canCall() } val isUserInTheCall by remember { derivedStateOf { room.sessionId in roomInfo.activeRoomCallParticipants diff --git a/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/di/RoomCallModule.kt b/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/di/RoomCallModule.kt index 3a8ac51ef6..fc649dd1f2 100644 --- a/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/di/RoomCallModule.kt +++ b/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/di/RoomCallModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomcall/impl/src/test/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenterTest.kt b/features/roomcall/impl/src/test/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenterTest.kt index 9647fddc1d..bdececf584 100644 --- a/features/roomcall/impl/src/test/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenterTest.kt +++ b/features/roomcall/impl/src/test/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,9 +15,12 @@ import io.element.android.features.call.test.FakeCurrentCallService import io.element.android.features.enterprise.test.FakeSessionEnterpriseService import io.element.android.features.roomcall.api.RoomCallState import io.element.android.libraries.matrix.api.room.JoinedRoom +import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.aRoomInfo +import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions +import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.test import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest @@ -27,7 +31,7 @@ class RoomCallStatePresenterTest { fun `present - initial state`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserJoinCallResult = { Result.success(false) }, + roomPermissions = roomPermissions(false), ) ) val presenter = createRoomCallStatePresenter(joinedRoom = room) @@ -46,7 +50,7 @@ class RoomCallStatePresenterTest { fun `present - element call not available`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserJoinCallResult = { Result.success(false) }, + roomPermissions = roomPermissions(false), ) ) val presenter = createRoomCallStatePresenter( @@ -65,7 +69,7 @@ class RoomCallStatePresenterTest { fun `present - initial state - user can join call`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserJoinCallResult = { Result.success(true) }, + roomPermissions = roomPermissions(true), ) ) val presenter = createRoomCallStatePresenter(joinedRoom = room) @@ -84,7 +88,7 @@ class RoomCallStatePresenterTest { fun `present - call is disabled if user cannot join it even if there is an ongoing call`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserJoinCallResult = { Result.success(false) }, + roomPermissions = roomPermissions(false), initialRoomInfo = aRoomInfo(hasRoomCall = true), ) ) @@ -105,7 +109,7 @@ class RoomCallStatePresenterTest { fun `present - user has joined the call on another session`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserJoinCallResult = { Result.success(true) }, + roomPermissions = roomPermissions(true), ).apply { givenRoomInfo( aRoomInfo( @@ -132,7 +136,7 @@ class RoomCallStatePresenterTest { fun `present - user has joined the call locally`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserJoinCallResult = { Result.success(true) }, + roomPermissions = roomPermissions(true), ).apply { givenRoomInfo( aRoomInfo( @@ -162,7 +166,7 @@ class RoomCallStatePresenterTest { fun `present - user leaves the call`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canUserJoinCallResult = { Result.success(true) }, + roomPermissions = roomPermissions(true), ).apply { givenRoomInfo( aRoomInfo( @@ -222,6 +226,17 @@ class RoomCallStatePresenterTest { } } + private fun roomPermissions(canJoinCall: Boolean): FakeRoomPermissions { + return FakeRoomPermissions( + canSendState = { stateEvent -> + when (stateEvent) { + StateEventType.CallMember -> canJoinCall + else -> lambdaError() + } + } + ) + } + private fun createRoomCallStatePresenter( joinedRoom: JoinedRoom, currentCallService: CurrentCallService = FakeCurrentCallService(), diff --git a/features/roomdetails/api/build.gradle.kts b/features/roomdetails/api/build.gradle.kts index f4977a938c..ce393840f8 100644 --- a/features/roomdetails/api/build.gradle.kts +++ b/features/roomdetails/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt b/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt index 803002d642..928c08324d 100644 --- a/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt +++ b/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,6 +14,7 @@ import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.permalink.PermalinkData @@ -23,6 +25,9 @@ interface RoomDetailsEntryPoint : FeatureEntryPoint { @Parcelize data object RoomDetails : InitialTarget + @Parcelize + data object RoomMemberList : InitialTarget + @Parcelize data class RoomMemberDetails(val roomMemberId: UserId) : InitialTarget @@ -33,17 +38,16 @@ interface RoomDetailsEntryPoint : FeatureEntryPoint { data class Params(val initialElement: InitialTarget) : NodeInputs interface Callback : Plugin { - fun onOpenGlobalNotificationSettings() - fun onOpenRoom(roomId: RoomId, serverNames: List) - fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean) - fun onForwardedToSingleRoom(roomId: RoomId) + fun navigateToGlobalNotificationSettings() + fun navigateToRoom(roomId: RoomId, serverNames: List) + fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) + fun startForwardEventFlow(eventId: EventId, fromPinnedEvents: Boolean) } - interface NodeBuilder { - fun params(params: Params): NodeBuilder - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } - - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder + fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: Params, + callback: Callback, + ): Node } diff --git a/features/roomdetails/impl/build.gradle.kts b/features/roomdetails/impl/build.gradle.kts index 882058ef48..2765a95a1e 100644 --- a/features/roomdetails/impl/build.gradle.kts +++ b/features/roomdetails/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -56,17 +57,29 @@ dependencies { implementation(projects.features.verifysession.api) implementation(projects.features.reportroom.api) implementation(projects.features.roommembermoderation.api) - implementation(projects.features.changeroommemberroles.api) + implementation(projects.features.rolesandpermissions.api) + implementation(projects.features.securityandprivacy.api) + implementation(projects.features.roomdetailsedit.api) implementation(projects.features.invitepeople.api) testCommonDependencies(libs, true) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.mediaupload.test) testImplementation(projects.libraries.mediapickers.test) + testImplementation(projects.libraries.mediaviewer.test) testImplementation(projects.libraries.permissions.test) testImplementation(projects.libraries.preferences.test) testImplementation(projects.libraries.usersearch.test) testImplementation(projects.libraries.featureflag.test) + testImplementation(projects.features.call.test) + testImplementation(projects.features.rolesandpermissions.test) + testImplementation(projects.features.securityandprivacy.test) + testImplementation(projects.features.roomdetailsedit.test) + testImplementation(projects.features.knockrequests.test) + testImplementation(projects.features.messages.test) + testImplementation(projects.features.poll.test) + testImplementation(projects.features.reportroom.test) testImplementation(projects.features.startchat.test) + testImplementation(projects.features.verifysession.test) testImplementation(projects.services.analytics.test) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt index 6d26ef32a1..7b2269c52c 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,36 +10,25 @@ package io.element.android.features.roomdetails.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint.InitialTarget import io.element.android.features.roomdetails.impl.RoomDetailsFlowNode.NavTarget import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultRoomDetailsEntryPoint : RoomDetailsEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): RoomDetailsEntryPoint.NodeBuilder { - return object : RoomDetailsEntryPoint.NodeBuilder { - val plugins = ArrayList() - - override fun params(params: RoomDetailsEntryPoint.Params): RoomDetailsEntryPoint.NodeBuilder { - plugins += params - return this - } - - override fun callback(callback: RoomDetailsEntryPoint.Callback): RoomDetailsEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: RoomDetailsEntryPoint.Params, + callback: RoomDetailsEntryPoint.Callback, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf(params, callback) + ) } } @@ -46,4 +36,5 @@ internal fun InitialTarget.toNavTarget() = when (this) { is InitialTarget.RoomDetails -> NavTarget.RoomDetails is InitialTarget.RoomMemberDetails -> NavTarget.RoomMemberDetails(roomMemberId) is InitialTarget.RoomNotificationSettings -> NavTarget.RoomNotificationSettings(showUserDefinedSettingStyle = true) + InitialTarget.RoomMemberList -> NavTarget.RoomMemberList } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsAction.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsAction.kt index dcceead9bf..262eb37650 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsAction.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsAction.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt index 09c4119ac1..de801e8aae 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index 2b4bf10d67..03adbded1b 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,7 +17,6 @@ import androidx.lifecycle.coroutineScope import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push @@ -27,24 +27,25 @@ import io.element.android.annotations.ContributesNode import io.element.android.appconfig.LearnMoreConfig import io.element.android.features.call.api.CallType import io.element.android.features.call.api.ElementCallEntryPoint -import io.element.android.features.changeroommemberroes.api.ChangeRoomMemberRolesEntryPoint -import io.element.android.features.changeroommemberroes.api.ChangeRoomMemberRolesListType import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.features.poll.api.history.PollHistoryEntryPoint import io.element.android.features.reportroom.api.ReportRoomEntryPoint +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint +import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType +import io.element.android.features.rolesandpermissions.api.RolesAndPermissionsEntryPoint import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint -import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditNode import io.element.android.features.roomdetails.impl.invite.RoomInviteMembersNode import io.element.android.features.roomdetails.impl.members.RoomMemberListNode import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsNode import io.element.android.features.roomdetails.impl.notificationsettings.RoomNotificationSettingsNode -import io.element.android.features.roomdetails.impl.rolesandpermissions.RolesAndPermissionsFlowNode -import io.element.android.features.roomdetails.impl.securityandprivacy.SecurityAndPrivacyFlowNode +import io.element.android.features.roomdetailsedit.api.RoomDetailsEditEntryPoint +import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyEntryPoint import io.element.android.features.userprofile.shared.UserProfileNodeHelper import io.element.android.features.verifysession.api.OutgoingVerificationEntryPoint import io.element.android.libraries.architecture.BackstackWithOverlayBox import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.overlay.operation.hide import io.element.android.libraries.architecture.overlay.operation.show @@ -82,6 +83,9 @@ class RoomDetailsFlowNode( private val outgoingVerificationEntryPoint: OutgoingVerificationEntryPoint, private val reportRoomEntryPoint: ReportRoomEntryPoint, private val changeRoomMemberRolesEntryPoint: ChangeRoomMemberRolesEntryPoint, + private val rolesAndPermissionsEntryPoint: RolesAndPermissionsEntryPoint, + private val securityAndPrivacyEntryPoint: SecurityAndPrivacyEntryPoint, + private val roomDetailsEditEntryPoint: RoomDetailsEditEntryPoint, ) : BaseFlowNode( backstack = BackStack( initialElement = plugins.filterIsInstance().first().initialElement.toNavTarget(), @@ -146,16 +150,22 @@ class RoomDetailsFlowNode( data object SelectNewOwnersWhenLeaving : NavTarget } + private val callback: RoomDetailsEntryPoint.Callback = callback() + override fun onBuilt() { super.onBuilt() - whenChildrenAttached { commonLifecycle: Lifecycle, - roomDetailsNode: RoomDetailsNode, - changeRoomMemberRolesNode: ChangeRoomMemberRolesEntryPoint.NodeProxy -> + whenChildrenAttached { + commonLifecycle: Lifecycle, + roomDetailsNode: RoomDetailsNode, + changeRoomMemberRolesNode: ChangeRoomMemberRolesEntryPoint.NodeProxy, + -> commonLifecycle.coroutineScope.launch { - changeRoomMemberRolesNode.waitForRoleChanged() + val isNewOwnerSelected = changeRoomMemberRolesNode.waitForCompletion() withContext(NonCancellable) { backstack.pop() - roomDetailsNode.onNewOwnersSelected() + if (isNewOwnerSelected) { + roomDetailsNode.onNewOwnersSelected() + } } } } @@ -165,55 +175,55 @@ class RoomDetailsFlowNode( return when (navTarget) { NavTarget.RoomDetails -> { val roomDetailsCallback = object : RoomDetailsNode.Callback { - override fun openRoomMemberList() { + override fun navigateToRoomMemberList() { backstack.push(NavTarget.RoomMemberList) } - override fun editRoomDetails() { + override fun navigateToRoomDetailsEdit() { backstack.push(NavTarget.RoomDetailsEdit) } - override fun openInviteMembers() { + override fun navigateToInviteMembers() { backstack.push(NavTarget.InviteMembers) } - override fun openRoomNotificationSettings() { + override fun navigateToRoomNotificationSettings() { backstack.push(NavTarget.RoomNotificationSettings(showUserDefinedSettingStyle = false)) } - override fun openAvatarPreview(name: String, url: String) { + override fun navigateToAvatarPreview(name: String, url: String) { overlay.show(NavTarget.AvatarPreview(name, url)) } - override fun openPollHistory() { + override fun navigateToPollHistory() { backstack.push(NavTarget.PollHistory) } - override fun openMediaGallery() { + override fun navigateToMediaGallery() { backstack.push(NavTarget.MediaGallery) } - override fun openAdminSettings() { + override fun navigateToAdminSettings() { backstack.push(NavTarget.AdminSettings) } - override fun openPinnedMessagesList() { + override fun navigateToPinnedMessagesList() { backstack.push(NavTarget.PinnedMessagesList) } - override fun openKnockRequestsList() { + override fun navigateToKnockRequestsList() { backstack.push(NavTarget.KnockRequestsList) } - override fun openSecurityAndPrivacy() { + override fun navigateToSecurityAndPrivacy() { backstack.push(NavTarget.SecurityAndPrivacy) } - override fun openDmUserProfile(userId: UserId) { + override fun navigateToRoomMemberDetails(userId: UserId) { backstack.push(NavTarget.RoomMemberDetails(userId)) } - override fun onJoinCall() { + override fun navigateToRoomCall() { val inputs = CallType.RoomCall( sessionId = room.sessionId, roomId = room.roomId, @@ -222,11 +232,11 @@ class RoomDetailsFlowNode( elementCallEntryPoint.startCall(inputs) } - override fun openReportRoom() { + override fun navigateToReportRoom() { backstack.push(NavTarget.ReportRoom) } - override fun onSelectNewOwnersWhenLeaving() { + override fun navigateToSelectNewOwnersWhenLeaving() { backstack.push(NavTarget.SelectNewOwnersWhenLeaving) } } @@ -235,11 +245,11 @@ class RoomDetailsFlowNode( NavTarget.RoomMemberList -> { val roomMemberListCallback = object : RoomMemberListNode.Callback { - override fun openRoomMemberDetails(roomMemberId: UserId) { + override fun navigateToRoomMemberDetails(roomMemberId: UserId) { backstack.push(NavTarget.RoomMemberDetails(roomMemberId)) } - override fun openInviteMembers() { + override fun navigateToInviteMembers() { backstack.push(NavTarget.InviteMembers) } } @@ -247,7 +257,7 @@ class RoomDetailsFlowNode( } NavTarget.RoomDetailsEdit -> { - createNode(buildContext) + roomDetailsEditEntryPoint.createNode(this, buildContext) } NavTarget.InviteMembers -> { @@ -257,8 +267,8 @@ class RoomDetailsFlowNode( is NavTarget.RoomNotificationSettings -> { val input = RoomNotificationSettingsNode.RoomNotificationSettingInput(navTarget.showUserDefinedSettingStyle) val callback = object : RoomNotificationSettingsNode.Callback { - override fun openGlobalNotificationSettings() { - plugins().forEach { it.onOpenGlobalNotificationSettings() } + override fun navigateToGlobalNotificationSettings() { + callback.navigateToGlobalNotificationSettings() } } createNode(buildContext, listOf(input, callback)) @@ -266,19 +276,19 @@ class RoomDetailsFlowNode( is NavTarget.RoomMemberDetails -> { val callback = object : UserProfileNodeHelper.Callback { - override fun openAvatarPreview(username: String, avatarUrl: String) { + override fun navigateToAvatarPreview(username: String, avatarUrl: String) { overlay.show(NavTarget.AvatarPreview(username, avatarUrl)) } - override fun onStartDM(roomId: RoomId) { - plugins().forEach { it.onOpenRoom(roomId, emptyList()) } + override fun navigateToRoom(roomId: RoomId) { + callback.navigateToRoom(roomId, emptyList()) } - override fun onStartCall(dmRoomId: RoomId) { + override fun startCall(dmRoomId: RoomId) { elementCallEntryPoint.startCall(CallType.RoomCall(roomId = dmRoomId, sessionId = room.sessionId)) } - override fun onVerifyUser(userId: UserId) { + override fun startVerifyUserFlow(userId: UserId) { backstack.push(NavTarget.VerifyUser(userId)) } } @@ -291,17 +301,24 @@ class RoomDetailsFlowNode( overlay.hide() } - override fun onViewInTimeline(eventId: EventId) { + override fun viewInTimeline(eventId: EventId) { + // Cannot happen + } + + override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) { // Cannot happen } } - mediaViewerEntryPoint.nodeBuilder(this, buildContext) - .avatar( - navTarget.name, - navTarget.avatarUrl, - ) - .callback(callback) - .build() + val params = mediaViewerEntryPoint.createParamsForAvatar( + filename = navTarget.name, + avatarUrl = navTarget.avatarUrl, + ) + mediaViewerEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = params, + callback = callback, + ) } is NavTarget.PollHistory -> { pollHistoryEntryPoint.createNode(this, buildContext) @@ -312,60 +329,90 @@ class RoomDetailsFlowNode( backstack.pop() } - override fun onViewInTimeline(eventId: EventId) { + override fun viewInTimeline(eventId: EventId) { val permalinkData = PermalinkData.RoomLink( roomIdOrAlias = room.roomId.toRoomIdOrAlias(), eventId = eventId, ) - plugins().forEach { - it.onPermalinkClick(permalinkData, pushToBackstack = false) - } + callback.handlePermalinkClick(permalinkData, pushToBackstack = false) + } + + override fun forward(eventId: EventId, fromPinnedEvents: Boolean) { + callback.startForwardEventFlow(eventId, fromPinnedEvents) } } - mediaGalleryEntryPoint.nodeBuilder(this, buildContext) - .callback(callback) - .build() + mediaGalleryEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + callback = callback, + ) } is NavTarget.AdminSettings -> { - createNode(buildContext) + val callback = object : RolesAndPermissionsEntryPoint.Callback { + override fun onDone() { + backstack.pop() + } + } + rolesAndPermissionsEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + callback = callback, + ) } NavTarget.PinnedMessagesList -> { val params = MessagesEntryPoint.Params( MessagesEntryPoint.InitialTarget.PinnedMessages ) val callback = object : MessagesEntryPoint.Callback { - override fun onRoomDetailsClick() = Unit + override fun navigateToRoomDetails() = Unit - override fun onUserDataClick(userId: UserId) = Unit + override fun navigateToRoomMemberDetails(userId: UserId) = Unit - override fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean) { - plugins().forEach { it.onPermalinkClick(data, pushToBackstack) } + override fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) { + callback.handlePermalinkClick(data, pushToBackstack) } - override fun onForwardedToSingleRoom(roomId: RoomId) { - plugins().forEach { it.onForwardedToSingleRoom(roomId) } + override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) { + callback.startForwardEventFlow(eventId, fromPinnedEvents) + } + + override fun navigateToRoom(roomId: RoomId) { + callback.navigateToRoom(roomId, emptyList()) } } - return messagesEntryPoint.nodeBuilder(this, buildContext) - .params(params) - .callback(callback) - .build() + return messagesEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = params, + callback = callback, + ) } NavTarget.KnockRequestsList -> { knockRequestsListEntryPoint.createNode(this, buildContext) } NavTarget.SecurityAndPrivacy -> { - createNode(buildContext) + val callback = object : SecurityAndPrivacyEntryPoint.Callback { + override fun onDone() { + backstack.pop() + } + } + securityAndPrivacyEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + callback = callback, + ) } is NavTarget.VerifyUser -> { val params = OutgoingVerificationEntryPoint.Params( showDeviceVerifiedScreen = true, verificationRequest = VerificationRequest.Outgoing.User(userId = navTarget.userId) ) - outgoingVerificationEntryPoint.nodeBuilder(this, buildContext) - .params(params) - .callback(object : OutgoingVerificationEntryPoint.Callback { + outgoingVerificationEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = params, + callback = object : OutgoingVerificationEntryPoint.Callback { override fun onDone() { backstack.pop() } @@ -374,21 +421,27 @@ class RoomDetailsFlowNode( backstack.pop() } - override fun onLearnMoreAboutEncryption() { + override fun navigateToLearnMoreAboutEncryption() { learnMoreUrl.value = LearnMoreConfig.ENCRYPTION_URL } - }) - .build() + }, + ) } is NavTarget.ReportRoom -> { - reportRoomEntryPoint.createNode(this, buildContext, room.roomId) + reportRoomEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + roomId = room.roomId, + ) } is NavTarget.SelectNewOwnersWhenLeaving -> { - changeRoomMemberRolesEntryPoint.builder(this, buildContext) - .room(room) - .listType(ChangeRoomMemberRolesListType.SelectNewOwnersWhenLeaving) - .build() + changeRoomMemberRolesEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + room = room, + listType = ChangeRoomMemberRolesListType.SelectNewOwnersWhenLeaving, + ) } } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt index 2ada71dbc8..d7b4e0d813 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,7 +19,6 @@ import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import im.vector.app.features.analytics.plan.MobileScreen @@ -26,6 +26,7 @@ import io.element.android.annotations.ContributesNode import io.element.android.features.leaveroom.api.LeaveRoomRenderer import io.element.android.libraries.androidutils.system.startSharePlainTextIntent import io.element.android.libraries.architecture.appyx.launchMolecule +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.BaseRoom @@ -46,24 +47,24 @@ class RoomDetailsNode( private val leaveRoomRenderer: LeaveRoomRenderer, ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun openRoomMemberList() - fun openInviteMembers() - fun editRoomDetails() - fun openRoomNotificationSettings() - fun openAvatarPreview(name: String, url: String) - fun openPollHistory() - fun openMediaGallery() - fun openAdminSettings() - fun openPinnedMessagesList() - fun openKnockRequestsList() - fun openSecurityAndPrivacy() - fun openDmUserProfile(userId: UserId) - fun onJoinCall() - fun openReportRoom() - fun onSelectNewOwnersWhenLeaving() + fun navigateToRoomMemberList() + fun navigateToInviteMembers() + fun navigateToRoomDetailsEdit() + fun navigateToRoomNotificationSettings() + fun navigateToAvatarPreview(name: String, url: String) + fun navigateToPollHistory() + fun navigateToMediaGallery() + fun navigateToAdminSettings() + fun navigateToPinnedMessagesList() + fun navigateToKnockRequestsList() + fun navigateToSecurityAndPrivacy() + fun navigateToRoomMemberDetails(userId: UserId) + fun navigateToRoomCall() + fun navigateToReportRoom() + fun navigateToSelectNewOwnersWhenLeaving() } - private val callback = plugins().first() + private val callback: Callback = callback() init { lifecycle.subscribe( @@ -73,30 +74,6 @@ class RoomDetailsNode( ) } - private fun openRoomMemberList() { - callback.openRoomMemberList() - } - - private fun openRoomNotificationSettings() { - callback.openRoomNotificationSettings() - } - - private fun invitePeople() { - callback.openInviteMembers() - } - - private fun openPollHistory() { - callback.openPollHistory() - } - - private fun openMediaGallery() { - callback.openMediaGallery() - } - - private fun onJoinCall() { - callback.onJoinCall() - } - private fun CoroutineScope.onShareRoom(context: Context) = launch { room.getPermalink() .onSuccess { permalink -> @@ -112,42 +89,6 @@ class RoomDetailsNode( } } - private fun onEditRoomDetails() { - callback.editRoomDetails() - } - - private fun openAvatarPreview(name: String, url: String) { - callback.openAvatarPreview(name, url) - } - - private fun openAdminSettings() { - callback.openAdminSettings() - } - - private fun openPinnedMessages() { - callback.openPinnedMessagesList() - } - - private fun openKnockRequestsLists() { - callback.openKnockRequestsList() - } - - private fun openSecurityAndPrivacy() { - callback.openSecurityAndPrivacy() - } - - private fun onProfileClick(userId: UserId) { - callback.openDmUserProfile(userId) - } - - private fun onReportRoomClick() { - callback.openReportRoom() - } - - private fun onSelectNewOwnersWhenLeaving() { - return callback.onSelectNewOwnersWhenLeaving() - } - private val stateFlow = launchMolecule { presenter.present() } fun onNewOwnersSelected() { @@ -165,34 +106,38 @@ class RoomDetailsNode( fun onActionClick(action: RoomDetailsAction) { when (action) { - RoomDetailsAction.Edit -> onEditRoomDetails() - RoomDetailsAction.AddTopic -> onEditRoomDetails() + RoomDetailsAction.Edit -> { + callback.navigateToRoomDetailsEdit() + } + RoomDetailsAction.AddTopic -> { + callback.navigateToRoomDetailsEdit() + } } } RoomDetailsView( state = state, modifier = modifier, - goBack = this::navigateUp, + goBack = ::navigateUp, onActionClick = ::onActionClick, onShareRoom = ::onShareRoom, - openRoomMemberList = ::openRoomMemberList, - openRoomNotificationSettings = ::openRoomNotificationSettings, - invitePeople = ::invitePeople, - openAvatarPreview = ::openAvatarPreview, - openPollHistory = ::openPollHistory, - openMediaGallery = ::openMediaGallery, - openAdminSettings = this::openAdminSettings, - onJoinCallClick = ::onJoinCall, - onPinnedMessagesClick = ::openPinnedMessages, - onKnockRequestsClick = ::openKnockRequestsLists, - onSecurityAndPrivacyClick = ::openSecurityAndPrivacy, - onProfileClick = ::onProfileClick, - onReportRoomClick = ::onReportRoomClick, + openRoomMemberList = callback::navigateToRoomMemberList, + openRoomNotificationSettings = callback::navigateToRoomNotificationSettings, + invitePeople = callback::navigateToInviteMembers, + openAvatarPreview = callback::navigateToAvatarPreview, + openPollHistory = callback::navigateToPollHistory, + openMediaGallery = callback::navigateToMediaGallery, + openAdminSettings = callback::navigateToAdminSettings, + onJoinCallClick = callback::navigateToRoomCall, + onPinnedMessagesClick = callback::navigateToPinnedMessagesList, + onKnockRequestsClick = callback::navigateToKnockRequestsList, + onSecurityAndPrivacyClick = callback::navigateToSecurityAndPrivacy, + onProfileClick = callback::navigateToRoomMemberDetails, + onReportRoomClick = callback::navigateToReportRoom, leaveRoomView = { leaveRoomRenderer.Render( state = state.leaveRoomState, - onSelectNewOwners = { onSelectNewOwnersWhenLeaving() }, + onSelectNewOwners = { callback.navigateToSelectNewOwnersWhenLeaving() }, modifier = Modifier ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 4408deab1e..07b76d41b9 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,6 +10,7 @@ package io.element.android.features.roomdetails.impl import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -17,11 +19,16 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import dev.zacsweers.metro.Inject import im.vector.app.features.analytics.plan.Interaction +import io.element.android.features.knockrequests.api.KnockRequestPermissions +import io.element.android.features.knockrequests.api.knockRequestPermissions import io.element.android.features.leaveroom.api.LeaveRoomEvent import io.element.android.features.leaveroom.api.LeaveRoomState import io.element.android.features.roomcall.api.RoomCallState import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter -import io.element.android.features.roomdetails.impl.securityandprivacy.permissions.securityAndPrivacyPermissionsAsState +import io.element.android.features.roomdetailsedit.api.RoomDetailsEditPermissions +import io.element.android.features.roomdetailsedit.api.roomDetailsEditPermissions +import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions +import io.element.android.features.securityandprivacy.api.securityAndPrivacyPermissions import io.element.android.libraries.androidutils.clipboard.ClipboardHelper import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers @@ -35,17 +42,13 @@ import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.RoomMember -import io.element.android.libraries.matrix.api.room.RoomMembersState -import io.element.android.libraries.matrix.api.room.StateEventType +import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.room.join.JoinRule -import io.element.android.libraries.matrix.api.room.powerlevels.canInvite -import io.element.android.libraries.matrix.api.room.powerlevels.canSendState +import io.element.android.libraries.matrix.api.room.powerlevels.canEditRolesAndPermissions +import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState import io.element.android.libraries.matrix.api.room.roomNotificationSettings -import io.element.android.libraries.matrix.ui.room.canHandleKnockRequestsAsState import io.element.android.libraries.matrix.ui.room.getCurrentRoomMember import io.element.android.libraries.matrix.ui.room.getDirectRoomMember -import io.element.android.libraries.matrix.ui.room.isDmAsState -import io.element.android.libraries.matrix.ui.room.isOwnUserAdmin import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange import io.element.android.libraries.preferences.api.store.AppPreferencesStore import io.element.android.libraries.ui.strings.CommonStrings @@ -76,8 +79,6 @@ class RoomDetailsPresenter( val scope = rememberCoroutineScope() val leaveRoomState = leaveRoomPresenter.present() val roomInfo by room.roomInfoFlow.collectAsState() - val isUserAdmin = room.isOwnUserAdmin() - val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val roomAvatar by remember { derivedStateOf { roomInfo.avatarUrl } } val roomName by remember { derivedStateOf { roomInfo.name?.trim().orEmpty() } } @@ -92,15 +93,11 @@ class RoomDetailsPresenter( observeNotificationSettings() } + val isDm = roomInfo.isDm val membersState by room.membersStateFlow.collectAsState() - val canInvite by getCanInvite(membersState) - + val permissions by getPermissions() val canonicalAlias by remember { derivedStateOf { roomInfo.canonicalAlias } } val isEncrypted by remember { derivedStateOf { roomInfo.isEncrypted == true } } - val isDm by room.isDmAsState() - val canEditName by getCanSendState(membersState, StateEventType.ROOM_NAME) - val canEditAvatar by getCanSendState(membersState, StateEventType.ROOM_AVATAR) - val canEditTopic by getCanSendState(membersState, StateEventType.ROOM_TOPIC) val dmMember by room.getDirectRoomMember(membersState) val currentMember by room.getCurrentRoomMember(membersState) val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember) @@ -108,16 +105,15 @@ class RoomDetailsPresenter( val roomCallState = roomCallStatePresenter.present() val joinedMemberCount by remember { derivedStateOf { roomInfo.joinedMembersCount } } - val topicState = remember(canEditTopic, roomTopic, roomType) { + val topicState = remember(permissions.editDetailsPermissions.canEditTopic, roomTopic, roomType) { val topic = roomTopic when { !topic.isNullOrBlank() -> RoomTopicState.ExistingTopic(topic) - canEditTopic && roomType is RoomDetailsType.Room -> RoomTopicState.CanAddTopic + permissions.editDetailsPermissions.canEditTopic && roomType is RoomDetailsType.Room -> RoomTopicState.CanAddTopic else -> RoomTopicState.Hidden } } - val canHandleKnockRequests by room.canHandleKnockRequestsAsState(syncUpdateFlow.value) val isKnockRequestsEnabled by remember { featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock) }.collectAsState(false) @@ -125,7 +121,10 @@ class RoomDetailsPresenter( room.knockRequestsFlow.collect { value = it.size } } val canShowKnockRequests by remember { - derivedStateOf { isKnockRequestsEnabled && canHandleKnockRequests && joinRule == JoinRule.Knock } + derivedStateOf { isKnockRequestsEnabled && permissions.knockRequestsPermissions.hasAny && joinRule == JoinRule.Knock } + } + val canShowSecurityAndPrivacy by remember { + derivedStateOf { !isDm && permissions.securityAndPrivacyPermissions.hasAny(isSpace = false, joinRule = joinRule) } } val isDeveloperModeEnabled by remember { appPreferencesStore.isDeveloperModeEnabledFlow() @@ -136,7 +135,7 @@ class RoomDetailsPresenter( val snackbarDispatcher = LocalSnackbarDispatcher.current val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() - fun handleEvents(event: RoomDetailsEvent) { + fun handleEvent(event: RoomDetailsEvent) { when (event) { is RoomDetailsEvent.LeaveRoom -> { leaveRoomState.eventSink(LeaveRoomEvent.LeaveRoom(room.roomId, needsConfirmation = event.needsConfirmation)) @@ -161,13 +160,6 @@ class RoomDetailsPresenter( val roomMemberDetailsState = roomMemberDetailsPresenter?.present() - val securityAndPrivacyPermissions = room.securityAndPrivacyPermissionsAsState(syncUpdateFlow.value) - val canShowSecurityAndPrivacy by remember { - derivedStateOf { - isKnockRequestsEnabled && roomType is RoomDetailsType.Room && securityAndPrivacyPermissions.value.hasAny - } - } - val hasMemberVerificationViolations by produceState(false) { room.roomMemberIdentityStateChange(waitForEncryption = true) .onEach { identities -> value = identities.any { it.identityState == IdentityState.VerificationViolation } } @@ -184,15 +176,15 @@ class RoomDetailsPresenter( roomTopic = topicState, memberCount = joinedMemberCount, isEncrypted = isEncrypted, - canInvite = canInvite, - canEdit = (canEditAvatar || canEditName || canEditTopic) && roomType == RoomDetailsType.Room, + canInvite = permissions.canInvite, + canEdit = roomType == RoomDetailsType.Room && permissions.editDetailsPermissions.hasAny, roomCallState = roomCallState, roomType = roomType, roomMemberDetailsState = roomMemberDetailsState, leaveRoomState = leaveRoomState, roomNotificationSettings = roomNotificationSettingsState.roomNotificationSettings(), isFavorite = isFavorite, - displayRolesAndPermissionsSettings = !isDm && isUserAdmin, + displayRolesAndPermissionsSettings = !isDm && permissions.canEditRolesAndPermissions, isPublic = joinRule == JoinRule.Public, heroes = roomInfo.heroes.toImmutableList(), pinnedMessagesCount = pinnedMessagesCount, @@ -204,7 +196,8 @@ class RoomDetailsPresenter( canReportRoom = canReportRoom, isTombstoned = roomInfo.successorRoom != null, showDebugInfo = isDeveloperModeEnabled, - eventSink = ::handleEvents, + roomVersion = roomInfo.roomVersion, + eventSink = ::handleEvent, ) } @@ -230,14 +223,25 @@ class RoomDetailsPresenter( } } - @Composable - private fun getCanInvite(membersState: RoomMembersState) = produceState(false, membersState) { - value = room.canInvite().getOrElse { false } - } + private data class Permissions( + val canInvite: Boolean = false, + val editDetailsPermissions: RoomDetailsEditPermissions = RoomDetailsEditPermissions.DEFAULT, + val knockRequestsPermissions: KnockRequestPermissions = KnockRequestPermissions.DEFAULT, + val securityAndPrivacyPermissions: SecurityAndPrivacyPermissions = SecurityAndPrivacyPermissions.DEFAULT, + val canEditRolesAndPermissions: Boolean = false, + ) @Composable - private fun getCanSendState(membersState: RoomMembersState, type: StateEventType) = produceState(false, membersState) { - value = room.canSendState(type).getOrElse { false } + private fun getPermissions(): State { + return room.permissionsAsState(Permissions()) { perms -> + Permissions( + canInvite = perms.canOwnUserInvite(), + editDetailsPermissions = perms.roomDetailsEditPermissions(), + knockRequestsPermissions = perms.knockRequestPermissions(), + canEditRolesAndPermissions = perms.canEditRolesAndPermissions(), + securityAndPrivacyPermissions = perms.securityAndPrivacyPermissions(), + ) + } } private fun CoroutineScope.observeNotificationSettings() { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt index 43566b86a8..2332776e96 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -48,6 +49,7 @@ data class RoomDetailsState( val canReportRoom: Boolean, val isTombstoned: Boolean, val showDebugInfo: Boolean, + val roomVersion: String?, val eventSink: (RoomDetailsEvent) -> Unit ) { val roomBadges = buildList { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt index 96d19c5ecf..783fcfaf10 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -145,6 +146,7 @@ fun aRoomDetailsState( canReportRoom = canReportRoom, isTombstoned = isTombstoned, showDebugInfo = showDebugInfo, + roomVersion = "12", eventSink = eventSink, ) 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 3f20985ab8..046c64d906 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -263,6 +264,7 @@ fun RoomDetailsView( if (state.showDebugInfo) { DebugInfoSection( roomId = state.roomId, + roomVersion = state.roomVersion, ) } } @@ -713,9 +715,13 @@ private fun OtherActionsSection( } @Composable -private fun DebugInfoSection(roomId: RoomId) { +private fun DebugInfoSection( + roomId: RoomId, + roomVersion: String?, +) { val context = LocalContext.current PreferenceCategory(showTopDivider = true) { + val toastMessage = stringResource(CommonStrings.common_copied_to_clipboard) ListItem( headlineContent = { Text("Internal room ID") @@ -731,11 +737,24 @@ private fun DebugInfoSection(roomId: RoomId) { trailingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Copy())), onClick = { context.copyToClipboard( - roomId.value, - context.getString(CommonStrings.common_copied_to_clipboard) + text = roomId.value, + toastMessage = toastMessage, ) }, ) + ListItem( + headlineContent = { + Text("Room version") + }, + supportingContent = { + Text( + text = roomVersion ?: "Unknown", + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textSecondary, + ) + }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Info())), + ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt index 9ca7b87c00..479f23f90d 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersNode.kt index 3c3acc7b4d..ea0ed1bb72 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt index f7991a6e44..cb305426bc 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/PowerLevelRoomMemberComparator.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/PowerLevelRoomMemberComparator.kt deleted file mode 100644 index 2359f809ac..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/PowerLevelRoomMemberComparator.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.roomdetails.impl.members - -import io.element.android.libraries.matrix.api.room.RoomMember -import io.element.android.libraries.matrix.ui.room.sortingName -import java.text.Collator - -// Comparator used to sort room members by power level (descending) and then by name (ascending) -internal class PowerLevelRoomMemberComparator : Comparator { - // Used to simplify and compare unicode and ASCII chars (á == a) - private val collator = Collator.getInstance().apply { - decomposition = Collator.CANONICAL_DECOMPOSITION - } - override fun compare(o1: RoomMember, o2: RoomMember): Int { - return when { - o1.powerLevel > o2.powerLevel -> return -1 - o1.powerLevel < o2.powerLevel -> return 1 - else -> { - collator.compare(o1.sortingName(), o2.sortingName()) - } - } - } -} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt deleted file mode 100644 index bbb231ccb9..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.roomdetails.impl.members - -import dev.zacsweers.metro.Inject -import io.element.android.libraries.core.bool.orFalse -import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.matrix.api.room.BaseRoom -import io.element.android.libraries.matrix.api.room.RoomMember -import io.element.android.libraries.matrix.api.room.roomMembers -import kotlinx.coroutines.withContext - -@Inject -class RoomMemberListDataSource( - private val room: BaseRoom, - private val coroutineDispatchers: CoroutineDispatchers, -) { - suspend fun search(query: String): List = withContext(coroutineDispatchers.io) { - val roomMembersState = room.membersStateFlow.value - val activeRoomMembers = roomMembersState.roomMembers() - ?.filter { it.membership.isActive() } - .orEmpty() - val filteredMembers = if (query.isBlank()) { - activeRoomMembers - } else { - activeRoomMembers.filter { member -> - member.userId.value.contains(query, ignoreCase = true) || - member.displayName?.contains(query, ignoreCase = true).orFalse() - } - } - filteredMembers - } -} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt index 1e9f4eb82c..ec1b130b3e 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,7 @@ package io.element.android.features.roomdetails.impl.members import io.element.android.libraries.matrix.api.room.RoomMember sealed interface RoomMemberListEvents { + data class ChangeSelectedSection(val section: SelectedSection) : RoomMemberListEvents data class UpdateSearchQuery(val query: String) : RoomMemberListEvents - data class OnSearchActiveChanged(val active: Boolean) : RoomMemberListEvents data class RoomMemberSelected(val roomMember: RoomMember) : RoomMemberListEvents } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt index cc7a6e2151..750b111fc3 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt @@ -1,19 +1,21 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.roomdetails.impl.members import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import im.vector.app.features.analytics.plan.MobileScreen @@ -21,6 +23,8 @@ import io.element.android.annotations.ContributesNode import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.features.roommembermoderation.api.RoomMemberModerationRenderer +import io.element.android.libraries.architecture.appyx.launchMolecule +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.UserId import io.element.android.services.analytics.api.AnalyticsService @@ -35,11 +39,12 @@ class RoomMemberListNode( private val roomMemberModerationRenderer: RoomMemberModerationRenderer, ) : Node(buildContext, plugins = plugins), RoomMemberListNavigator { interface Callback : Plugin { - fun openRoomMemberDetails(roomMemberId: UserId) - fun openInviteMembers() + fun navigateToRoomMemberDetails(roomMemberId: UserId) + fun navigateToInviteMembers() } - private val callbacks = plugins() + private val callback: Callback = callback() + private val stateFlow = launchMolecule { presenter.present() } init { lifecycle.subscribe( @@ -50,15 +55,11 @@ class RoomMemberListNode( } override fun openRoomMemberDetails(roomMemberId: UserId) { - callbacks.forEach { - it.openRoomMemberDetails(roomMemberId) - } + callback.navigateToRoomMemberDetails(roomMemberId) } override fun openInviteMembers() { - callbacks.forEach { - it.openInviteMembers() - } + callback.navigateToInviteMembers() } override fun exitRoomMemberList() { @@ -67,7 +68,7 @@ class RoomMemberListNode( @Composable override fun View(modifier: Modifier) { - val state = presenter.present() + val state by stateFlow.collectAsState() RoomMemberListView( state = state, modifier = modifier, 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 69efd04b9e..6917057a06 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,6 +10,7 @@ package io.element.android.features.roomdetails.impl.members import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -17,13 +19,12 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import dev.zacsweers.metro.Inject -import io.element.android.features.roommembermoderation.api.ModerationAction -import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents.ShowActionsForUser import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.architecture.map import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.identity.IdentityState @@ -31,42 +32,33 @@ import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembersState import io.element.android.libraries.matrix.api.room.RoomMembershipState +import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState import io.element.android.libraries.matrix.api.room.roomMembers import io.element.android.libraries.matrix.api.room.toMatrixUser -import io.element.android.libraries.matrix.ui.room.canInviteAsState +import io.element.android.libraries.matrix.ui.room.PowerLevelRoomMemberComparator import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableMap -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.withContext @Inject class RoomMemberListPresenter( private val room: JoinedRoom, - private val roomMemberListDataSource: RoomMemberListDataSource, private val coroutineDispatchers: CoroutineDispatchers, private val roomMembersModerationPresenter: Presenter, private val encryptionService: EncryptionService, ) : Presenter { + private val powerLevelRoomMemberComparator = PowerLevelRoomMemberComparator() + @Composable override fun present(): RoomMemberListState { - var roomMembers: AsyncData by remember { mutableStateOf(AsyncData.Loading()) } var searchQuery by rememberSaveable { mutableStateOf("") } - var searchResults by remember { - mutableStateOf>>(SearchBarResultState.Initial()) - } - var isSearchActive by rememberSaveable { mutableStateOf(false) } - val membersState by room.membersStateFlow.collectAsState() - val syncUpdateFlow = room.syncUpdateFlow.collectAsState() - val canInvite by room.canInviteAsState(syncUpdateFlow.value) + val canInvite by room.permissionsAsState(false) { perms -> perms.canOwnUserInvite() } val roomModerationState = roomMembersModerationPresenter.present() val roomMemberIdentityStates by produceState(persistentMapOf()) { @@ -77,13 +69,13 @@ class RoomMemberListPresenter( .launchIn(this) } - // Update the room members when the screen is loaded or the active member count changes + var selectedSection by remember { mutableStateOf(SelectedSection.MEMBERS) } + var roomMembers: AsyncData by remember { mutableStateOf(AsyncData.Loading()) } + var filteredRoomMembers: AsyncData by remember { mutableStateOf(AsyncData.Loading()) } + + // Update the room members when the screen is loaded LaunchedEffect(Unit) { - room.roomInfoFlow.map { it.activeMembersCount } - .distinctUntilChanged() - .collectLatest { - room.updateMembers() - } + room.updateMembers() } LaunchedEffect(membersState, roomMemberIdentityStates) { @@ -98,7 +90,7 @@ class RoomMemberListPresenter( } withContext(coroutineDispatchers.io) { val members = membersState.roomMembers().orEmpty().groupBy { it.membership } - val info = room.roomInfoFlow.first() + val info = room.info() if (members.getOrDefault(RoomMembershipState.JOIN, emptyList()).size < info.joinedMembersCount / 2) { // Don't display initial room member list if we have less than half of the joined members: // This result will come from the timeline loading membership events and it'll be wrong. @@ -109,7 +101,7 @@ class RoomMemberListPresenter( .map { it.withIdentityState(roomMemberIdentityStates) } .toImmutableList(), joined = members.getOrDefault(RoomMembershipState.JOIN, emptyList()) - .sortedWith(PowerLevelRoomMemberComparator()) + .sortedWith(powerLevelRoomMemberComparator) .map { it.withIdentityState(roomMemberIdentityStates) } .toImmutableList(), banned = members.getOrDefault(RoomMembershipState.BAN, emptyList()) @@ -125,69 +117,45 @@ class RoomMemberListPresenter( } } - LaunchedEffect(membersState, searchQuery, isSearchActive) { - withContext(coroutineDispatchers.io) { - searchResults = if (searchQuery.isEmpty() || !isSearchActive) { - SearchBarResultState.Initial() - } else { - val results = roomMemberListDataSource.search(searchQuery).groupBy { it.membership } - if (results.isEmpty()) { - SearchBarResultState.NoResultsFound() - } else { - val result = RoomMembers( - invited = results.getOrDefault(RoomMembershipState.INVITE, emptyList()) - .map { it.withIdentityState(roomMemberIdentityStates) } - .toImmutableList(), - joined = results.getOrDefault(RoomMembershipState.JOIN, emptyList()) - .sortedWith(PowerLevelRoomMemberComparator()) - .map { it.withIdentityState(roomMemberIdentityStates) } - .toImmutableList(), - banned = results.getOrDefault(RoomMembershipState.BAN, emptyList()) - .sortedBy { it.userId.value } - .map { it.withIdentityState(roomMemberIdentityStates) } - .toImmutableList(), - ) - SearchBarResultState.Results( - if (membersState is RoomMembersState.Pending) { - AsyncData.Loading(result) - } else { - AsyncData.Success(result) - } - ) - } + LaunchedEffect(searchQuery, roomMembers) { + filteredRoomMembers = roomMembers.map { members -> + withContext(coroutineDispatchers.io) { + members.filter(searchQuery) } } } - fun handleEvents(event: RoomMemberListEvents) { + fun handleEvent(event: RoomMemberListEvents) { when (event) { - is RoomMemberListEvents.OnSearchActiveChanged -> isSearchActive = event.active is RoomMemberListEvents.UpdateSearchQuery -> searchQuery = event.query is RoomMemberListEvents.RoomMemberSelected -> - if (event.roomMember.membership == RoomMembershipState.BAN) { - roomModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(ModerationAction.UnbanUser, event.roomMember.toMatrixUser())) - } else { - roomModerationState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(event.roomMember.toMatrixUser())) - } + roomModerationState.eventSink(ShowActionsForUser(event.roomMember.toMatrixUser())) + is RoomMemberListEvents.ChangeSelectedSection -> selectedSection = event.section } } - return RoomMemberListState( + val state = RoomMemberListState( roomMembers = roomMembers, + filteredRoomMembers = filteredRoomMembers, searchQuery = searchQuery, - searchResults = searchResults, - isSearchActive = isSearchActive, canInvite = canInvite, moderationState = roomModerationState, - eventSink = { handleEvents(it) }, + selectedSection = selectedSection, + eventSink = ::handleEvent, ) + if (!state.showBannedSection && selectedSection == SelectedSection.BANNED) { + SideEffect { + selectedSection = SelectedSection.MEMBERS + } + } + return state } private suspend fun RoomMember.withIdentityState(identityStates: ImmutableMap): RoomMemberWithIdentityState { return if (room.info().isEncrypted != true) { RoomMemberWithIdentityState(this, null) } else { - val identityState = identityStates[userId] ?: encryptionService.getUserIdentity(userId).getOrNull() + val identityState = identityStates[userId] ?: encryptionService.getUserIdentity(userId, fallbackToServer = false).getOrNull() RoomMemberWithIdentityState(this, identityState) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt index 8e492e5371..7c928fb27a 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,26 +10,57 @@ package io.element.android.features.roomdetails.impl.members import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.designsystem.theme.components.SearchBarResultState +import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.matrix.api.room.RoomMember import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList data class RoomMemberListState( - val roomMembers: AsyncData, + // Only used to know if we can show the banned section + private val roomMembers: AsyncData, + val filteredRoomMembers: AsyncData, val searchQuery: String, - val searchResults: SearchBarResultState>, - val isSearchActive: Boolean, val canInvite: Boolean, + val selectedSection: SelectedSection, val moderationState: RoomMemberModerationState, val eventSink: (RoomMemberListEvents) -> Unit, -) +) { + val showBannedSection: Boolean = moderationState.permissions.canBan && roomMembers.dataOrNull()?.banned?.isNotEmpty() == true +} + +enum class SelectedSection { + MEMBERS, + BANNED +} data class RoomMembers( val invited: ImmutableList, val joined: ImmutableList, val banned: ImmutableList, -) +) { + fun isEmpty(section: SelectedSection): Boolean { + return when (section) { + SelectedSection.MEMBERS -> invited.isEmpty() && joined.isEmpty() + SelectedSection.BANNED -> banned.isEmpty() + } + } + + fun filter(query: String): RoomMembers { + if (query.isBlank()) { + return this + } + val filterPredicate = { member: RoomMemberWithIdentityState -> + member.roomMember.userId.value.contains(query, ignoreCase = true) || + member.roomMember.displayName?.contains(query, ignoreCase = true).orFalse() + } + return RoomMembers( + invited = invited.filter(filterPredicate).toImmutableList(), + joined = joined.filter(filterPredicate).toImmutableList(), + banned = banned.filter(filterPredicate).toImmutableList(), + ) + } +} data class RoomMemberWithIdentityState( val roomMember: RoomMember, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt index a1cc9c3b92..b37738c30f 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,9 +10,10 @@ package io.element.android.features.roomdetails.impl.members import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.designsystem.theme.components.SearchBarResultState +import io.element.android.libraries.architecture.map import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.matrix.api.room.RoomMember @@ -22,112 +24,75 @@ internal class RoomMemberListStateProvider : PreviewParameterProvider get() = sequenceOf( aRoomMemberListState( - roomMembers = AsyncData.Success( - RoomMembers( - invited = persistentListOf(aVictor().withIdentity(), aWalter().withIdentity()), - joined = persistentListOf(anAlice().withIdentity(), aBob().withIdentity(), aWalter().withIdentity()), - banned = persistentListOf(), - ) - ) - ), - aRoomMemberListState( - roomMembers = AsyncData.Success( - RoomMembers( - invited = persistentListOf(aVictor().withIdentity(), aWalter().withIdentity()), - joined = persistentListOf( - anAlice().withIdentity(identityState = IdentityState.Verified), - aBob().withIdentity(identityState = IdentityState.PinViolation), - aWalter().withIdentity(identityState = IdentityState.VerificationViolation) - ), - banned = persistentListOf(), - ) - ) - ), - aRoomMemberListState(roomMembers = AsyncData.Loading()), - aRoomMemberListState().copy(canInvite = true), - aRoomMemberListState().copy(isSearchActive = false), - aRoomMemberListState().copy(isSearchActive = true), - aRoomMemberListState().copy(isSearchActive = true, searchQuery = "someone"), - aRoomMemberListState().copy( - isSearchActive = true, - searchQuery = "@someone:matrix.org", - searchResults = SearchBarResultState.Results( - AsyncData.Success( - RoomMembers( - invited = persistentListOf(aVictor().withIdentity()), - joined = persistentListOf(anAlice().withIdentity()), - banned = persistentListOf(), - ) - ) - ), - ), - aRoomMemberListState().copy( - isSearchActive = true, - searchQuery = "something-with-no-results", - searchResults = SearchBarResultState.NoResultsFound() + roomMembers = AsyncData.Loading(), + selectedSection = SelectedSection.MEMBERS, ), aRoomMemberListState( roomMembers = AsyncData.Failure(Exception("Error details")), + selectedSection = SelectedSection.MEMBERS, + ), + aRoomMemberListState( + roomMembers = aLoadedRoomMembers(), + selectedSection = SelectedSection.MEMBERS, + ), + aRoomMemberListState( + roomMembers = aLoadedRoomMembers(), + selectedSection = SelectedSection.BANNED, + moderationState = aRoomMemberModerationState(canBan = true), + ), + aRoomMemberListState( + roomMembers = aLoadedRoomMembers(), + canInvite = true, + selectedSection = SelectedSection.MEMBERS, + ), + aRoomMemberListState( + roomMembers = aLoadedRoomMembers(), + searchQuery = "alice", + selectedSection = SelectedSection.MEMBERS, + ), + aRoomMemberListState( + roomMembers = aLoadedRoomMembers(), + searchQuery = "something-with-no-results", + selectedSection = SelectedSection.MEMBERS, ), ) } -internal class RoomMemberListStateBannedProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf( - aRoomMemberListState( - roomMembers = AsyncData.Success( - RoomMembers( - invited = persistentListOf(), - joined = persistentListOf(), - banned = persistentListOf( - aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice").withIdentity(), - aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob").withIdentity(), - aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie").withIdentity(), - ), - ) - ), - moderationState = aRoomMemberModerationState(), - ), - aRoomMemberListState( - roomMembers = AsyncData.Loading( - RoomMembers( - invited = persistentListOf(), - joined = persistentListOf(), - banned = persistentListOf( - aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice").withIdentity(), - aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob").withIdentity(), - aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie").withIdentity(), - ), - ) - ), - moderationState = aRoomMemberModerationState(), - ), - aRoomMemberListState( - roomMembers = AsyncData.Success( - RoomMembers( - invited = persistentListOf(), - joined = persistentListOf(), - banned = persistentListOf(), - ) - ), - moderationState = aRoomMemberModerationState(), - ) - ) -} +private fun aLoadedRoomMembers() = AsyncData.Success( + RoomMembers( + invited = persistentListOf( + anInvitedVictor().withIdentity(), + anInvitedWalter().withIdentity(), + ), + joined = persistentListOf( + anAlice().withIdentity(identityState = IdentityState.Verified), + aBob().withIdentity(identityState = IdentityState.PinViolation), + aCarol().withIdentity(), + aDavid().withIdentity(), + anEve().withIdentity(identityState = IdentityState.VerificationViolation) + ), + banned = persistentListOf( + aBannedMallory().withIdentity(), + aBannedSusie().withIdentity() + ), + ) +) internal fun aRoomMemberListState( roomMembers: AsyncData = AsyncData.Loading(), - searchResults: SearchBarResultState> = SearchBarResultState.Initial(), moderationState: RoomMemberModerationState = aRoomMemberModerationState(), + selectedSection: SelectedSection = SelectedSection.MEMBERS, + searchQuery: String = "", + canInvite: Boolean = false, + eventSink: (RoomMemberListEvents) -> Unit = {}, ) = RoomMemberListState( roomMembers = roomMembers, - searchQuery = "", - searchResults = searchResults, - isSearchActive = false, - canInvite = false, + filteredRoomMembers = roomMembers.map { it.filter(searchQuery) }, + searchQuery = searchQuery, + canInvite = canInvite, moderationState = moderationState, - eventSink = {} + selectedSection = selectedSection, + eventSink = eventSink ) fun aRoomMemberModerationState( @@ -135,8 +100,10 @@ fun aRoomMemberModerationState( canKick: Boolean = false, ): RoomMemberModerationState { return object : RoomMemberModerationState { - override val canKick: Boolean = canKick - override val canBan: Boolean = canBan + override val permissions: RoomMemberModerationPermissions = RoomMemberModerationPermissions( + canBan = canBan, + canKick = canKick, + ) override val eventSink: (RoomMemberModerationEvents) -> Unit = {} } } @@ -166,21 +133,30 @@ fun aRoomMember( fun aRoomMemberList() = persistentListOf( anAlice(), aBob(), - aRoomMember(UserId("@carol:server.org"), "Carol"), - aRoomMember(UserId("@david:server.org"), "David"), - aRoomMember(UserId("@eve:server.org"), "Eve"), - aRoomMember(UserId("@justin:server.org"), "Justin"), - aRoomMember(UserId("@mallory:server.org"), "Mallory"), - aRoomMember(UserId("@susie:server.org"), "Susie"), - aVictor(), - aWalter(), + aCarol(), + aDavid(), + anEve(), + anInvitedVictor(), + anInvitedWalter(), + aBannedSusie(), + aBannedMallory(), ) +fun anEve(): RoomMember = aRoomMember(UserId("@eve:server.org"), "Eve") + +fun aDavid(): RoomMember = aRoomMember(UserId("@david:server.org"), "David") + +fun aCarol(): RoomMember = aRoomMember(UserId("@carol:server.org"), "Carol") + fun anAlice() = aRoomMember(UserId("@alice:server.org"), "Alice", role = RoomMember.Role.Admin) fun aBob() = aRoomMember(UserId("@bob:server.org"), "Bob", role = RoomMember.Role.Moderator) -fun aVictor() = aRoomMember(UserId("@victor:server.org"), "Victor", membership = RoomMembershipState.INVITE) +fun anInvitedVictor() = aRoomMember(UserId("@victor:server.org"), "Victor", membership = RoomMembershipState.INVITE) -fun aWalter() = aRoomMember(UserId("@walter:server.org"), "Walter", membership = RoomMembershipState.INVITE) +fun anInvitedWalter() = aRoomMember(UserId("@walter:server.org"), "Walter", membership = RoomMembershipState.INVITE) + +fun aBannedSusie(): RoomMember = aRoomMember(UserId("@susie:server.org"), "Susie", membership = RoomMembershipState.BAN) + +fun aBannedMallory(): RoomMember = aRoomMember(UserId("@mallory:server.org"), "Mallory", membership = RoomMembershipState.BAN) private fun RoomMember.withIdentity(identityState: IdentityState? = null) = RoomMemberWithIdentityState(this, identityState) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt index c28cdda62c..7c83f74f00 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt @@ -1,21 +1,17 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.roomdetails.impl.members import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.expandVertically -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.consumeWindowInsets @@ -29,10 +25,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -45,15 +38,17 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.roomdetails.impl.R import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule +import io.element.android.libraries.designsystem.components.BigIcon import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.form.textFieldState import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator import io.element.android.libraries.designsystem.theme.components.Scaffold -import io.element.android.libraries.designsystem.theme.components.SearchBar -import io.element.android.libraries.designsystem.theme.components.SearchBarResultState +import io.element.android.libraries.designsystem.theme.components.SearchField import io.element.android.libraries.designsystem.theme.components.SegmentedButton import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextButton @@ -67,17 +62,11 @@ import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -private enum class SelectedSection { - MEMBERS, - BANNED -} - @Composable fun RoomMemberListView( state: RoomMemberListState, navigator: RoomMemberListNavigator, modifier: Modifier = Modifier, - initialSelectedSectionIndex: Int = 0, ) { fun onSelectUser(roomMember: RoomMember) { state.eventSink(RoomMemberListEvents.RoomMemberSelected(roomMember)) @@ -86,21 +75,13 @@ fun RoomMemberListView( Scaffold( modifier = modifier, topBar = { - if (!state.isSearchActive) { - RoomMemberListTopBar( - canInvite = state.canInvite, - onBackClick = navigator::exitRoomMemberList, - onInviteClick = navigator::openInviteMembers, - ) - } + RoomMemberListTopBar( + canInvite = state.canInvite, + onBackClick = navigator::exitRoomMemberList, + onInviteClick = navigator::openInviteMembers, + ) } ) { padding -> - var selectedSection by remember { mutableStateOf(SelectedSection.entries[initialSelectedSectionIndex]) } - if (!state.moderationState.canBan && selectedSection == SelectedSection.BANNED) { - SideEffect { - selectedSection = SelectedSection.MEMBERS - } - } Column( modifier = Modifier .fillMaxWidth() @@ -108,45 +89,43 @@ fun RoomMemberListView( .consumeWindowInsets(padding), verticalArrangement = Arrangement.spacedBy(16.dp), ) { - RoomMemberSearchBar( - query = state.searchQuery, - state = state.searchResults, - active = state.isSearchActive, - placeHolderTitle = stringResource(CommonStrings.common_search_for_someone), - onActiveChange = { state.eventSink(RoomMemberListEvents.OnSearchActiveChanged(it)) }, - onTextChange = { state.eventSink(RoomMemberListEvents.UpdateSearchQuery(it)) }, - onSelectUser = ::onSelectUser, - selectedSection = selectedSection, - modifier = Modifier.fillMaxWidth(), + var searchQuery by textFieldState(state.searchQuery) + SearchField( + value = searchQuery, + onValueChange = { newQuery -> + searchQuery = newQuery + state.eventSink(RoomMemberListEvents.UpdateSearchQuery(newQuery)) + }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + placeholder = stringResource(CommonStrings.common_search_for_someone), + ) + RoomMemberList( + roomMembersData = state.filteredRoomMembers, + selectedSection = state.selectedSection, + showBannedSection = state.showBannedSection, + searchQuery = state.searchQuery, + onSelectedSectionChange = { state.eventSink(RoomMemberListEvents.ChangeSelectedSection(it)) }, + onSelectUser = ::onSelectUser, ) - - if (!state.isSearchActive) { - RoomMemberList( - roomMembers = state.roomMembers, - showMembersCount = true, - canDisplayBannedUsersControls = state.moderationState.canBan, - selectedSection = selectedSection, - onSelectedSectionChange = { selectedSection = it }, - onSelectUser = ::onSelectUser, - ) - } } } } @Composable private fun RoomMemberList( - roomMembers: AsyncData, - showMembersCount: Boolean, + roomMembersData: AsyncData, selectedSection: SelectedSection, + showBannedSection: Boolean, + searchQuery: String, onSelectedSectionChange: (SelectedSection) -> Unit, - canDisplayBannedUsersControls: Boolean, onSelectUser: (RoomMember) -> Unit, ) { LazyColumn(modifier = Modifier.fillMaxWidth(), state = rememberLazyListState()) { stickyHeader { Column { - if (canDisplayBannedUsersControls) { + AnimatedVisibility(visible = showBannedSection) { val segmentedButtonTitles = persistentListOf( stringResource(id = R.string.screen_room_member_list_mode_members), stringResource(id = R.string.screen_room_member_list_mode_banned), @@ -168,24 +147,26 @@ private fun RoomMemberList( } } } - AnimatedVisibility( - visible = roomMembers.isLoading(), - enter = fadeIn() + expandVertically(), - exit = fadeOut() + shrinkVertically(), - ) { + AnimatedVisibility(visible = roomMembersData.isLoading()) { LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) } } } - when (roomMembers) { - is AsyncData.Failure -> failureItem(roomMembers.error) + when (roomMembersData) { + is AsyncData.Failure -> failureItem(roomMembersData.error) is AsyncData.Loading, - is AsyncData.Success -> memberItems( - roomMembers = roomMembers.dataOrNull() ?: return@LazyColumn, - selectedSection = selectedSection, - onSelectUser = onSelectUser, - showMembersCount = showMembersCount, - ) + is AsyncData.Success -> { + val roomMembers = roomMembersData.dataOrNull() ?: return@LazyColumn + if (roomMembers.isEmpty(selectedSection)) { + emptySearchItem(searchQuery) + } else { + memberItems( + roomMembers = roomMembers, + selectedSection = selectedSection, + onSelectUser = onSelectUser, + ) + } + } AsyncData.Uninitialized -> Unit } } @@ -195,56 +176,47 @@ private fun LazyListScope.memberItems( roomMembers: RoomMembers, selectedSection: SelectedSection, onSelectUser: (RoomMember) -> Unit, - showMembersCount: Boolean, ) { when (selectedSection) { SelectedSection.MEMBERS -> { if (roomMembers.invited.isNotEmpty()) { - roomMemberListSection( - headerText = { stringResource(id = R.string.screen_room_member_list_pending_header_title) }, + roomMemberListSectionHeader( + text = { + val memberCount = roomMembers.invited.count() + pluralStringResource(id = R.plurals.screen_room_member_list_pending_header_title, memberCount, memberCount) + }, + ) + roomMemberListSectionItems( members = roomMembers.invited, onMemberSelected = { onSelectUser(it) } ) } if (roomMembers.joined.isNotEmpty()) { - roomMemberListSection( - headerText = { - if (showMembersCount) { - val memberCount = roomMembers.joined.count() - pluralStringResource(id = R.plurals.screen_room_member_list_header_title, count = memberCount, memberCount) - } else { - stringResource(id = R.string.screen_room_member_list_room_members_header_title) - } + roomMemberListSectionHeader( + text = { + val memberCount = roomMembers.joined.count() + pluralStringResource(id = R.plurals.screen_room_member_list_header_title, count = memberCount, memberCount) }, + ) + roomMemberListSectionItems( members = roomMembers.joined, onMemberSelected = { onSelectUser(it) } ) } } - SelectedSection.BANNED -> { // Banned users + SelectedSection.BANNED -> { if (roomMembers.banned.isNotEmpty()) { - roomMemberListSection( - headerText = null, + roomMemberListSectionHeader( + text = { + val memberCount = roomMembers.banned.count() + pluralStringResource(id = R.plurals.screen_room_member_list_banned_header_title, memberCount, memberCount) + }, + isCritical = true, + ) + roomMemberListSectionItems( members = roomMembers.banned, onMemberSelected = { onSelectUser(it) } ) - } else { - item { - Box( - Modifier - .fillParentMaxSize() - .padding(horizontal = 16.dp) - ) { - Text( - modifier = Modifier - .padding(bottom = 56.dp) - .align(Alignment.Center), - text = stringResource(id = R.string.screen_room_member_list_banned_empty), - color = ElementTheme.colors.textSecondary, - textAlign = TextAlign.Center, - ) - } - } } } } @@ -263,21 +235,25 @@ private fun LazyListScope.failureItem(failure: Throwable) { } } -private fun LazyListScope.roomMemberListSection( - headerText: @Composable (() -> String)?, +private fun LazyListScope.roomMemberListSectionHeader( + text: @Composable (() -> String), + modifier: Modifier = Modifier, + isCritical: Boolean = false, +) { + item { + Text( + modifier = modifier.padding(horizontal = 16.dp, vertical = 12.dp), + text = text(), + style = ElementTheme.typography.fontBodyLgMedium, + color = if (isCritical) ElementTheme.colors.textCriticalPrimary else ElementTheme.colors.textPrimary, + ) + } +} + +private fun LazyListScope.roomMemberListSectionItems( members: ImmutableList?, onMemberSelected: (RoomMember) -> Unit, ) { - headerText?.let { - item { - Text( - modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), - text = it(), - style = ElementTheme.typography.fontBodyLgRegular, - color = ElementTheme.colors.textSecondary, - ) - } - } items(members.orEmpty()) { matrixUser -> RoomMemberListItem( modifier = Modifier.fillMaxWidth(), @@ -287,6 +263,22 @@ private fun LazyListScope.roomMemberListSection( } } +private fun LazyListScope.emptySearchItem(searchQuery: String) { + item { + IconTitleSubtitleMolecule( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 32.dp), + iconStyle = BigIcon.Style.Default( + vectorIcon = CompoundIcons.Search(), + contentDescription = null, + ), + title = stringResource(R.string.screen_room_member_list_empty_search_title, searchQuery), + subTitle = stringResource(R.string.screen_room_member_list_empty_search_subtitle), + ) + } +} + @Composable private fun RoomMemberListItem( roomMemberWithIdentity: RoomMemberWithIdentityState, @@ -366,40 +358,6 @@ private fun RoomMemberListTopBar( ) } -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun RoomMemberSearchBar( - query: String, - state: SearchBarResultState>, - active: Boolean, - placeHolderTitle: String, - onActiveChange: (Boolean) -> Unit, - onTextChange: (String) -> Unit, - onSelectUser: (RoomMember) -> Unit, - selectedSection: SelectedSection, - modifier: Modifier = Modifier, -) { - SearchBar( - query = query, - onQueryChange = onTextChange, - active = active, - onActiveChange = onActiveChange, - modifier = modifier, - placeHolderTitle = placeHolderTitle, - resultState = state, - resultHandler = { results -> - RoomMemberList( - roomMembers = results, - showMembersCount = false, - onSelectUser = { onSelectUser(it) }, - canDisplayBannedUsersControls = false, - selectedSection = selectedSection, - onSelectedSectionChange = {}, - ) - }, - ) -} - @PreviewsDayNight @Composable internal fun RoomMemberListViewPreview(@PreviewParameter(RoomMemberListStateProvider::class) state: RoomMemberListState) = ElementPreview { @@ -408,13 +366,3 @@ internal fun RoomMemberListViewPreview(@PreviewParameter(RoomMemberListStateProv navigator = object : RoomMemberListNavigator {}, ) } - -@PreviewsDayNight -@Composable -internal fun RoomMemberListViewBannedPreview(@PreviewParameter(RoomMemberListStateBannedProvider::class) state: RoomMemberListState) = ElementPreview { - RoomMemberListView( - initialSelectedSectionIndex = 1, - state = state, - navigator = object : RoomMemberListNavigator {}, - ) -} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt index 3b364c6f92..ce798e5da9 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -62,12 +63,12 @@ class RoomMemberDetailsNode( userProfileNodeHelper.onShareUser(context, permalinkBuilder) } - fun onStartDM(roomId: RoomId) { - callback.onStartDM(roomId) + fun navigateToRoom(roomId: RoomId) { + callback.navigateToRoom(roomId) } fun onStartCall(roomId: RoomId) { - callback.onStartCall(roomId) + callback.startCall(roomId) } val state = presenter.present() @@ -77,10 +78,10 @@ class RoomMemberDetailsNode( modifier = modifier, goBack = this::navigateUp, onShareUser = ::onShareUser, - onOpenDm = ::onStartDM, + onOpenDm = ::navigateToRoom, onStartCall = ::onStartCall, - openAvatarPreview = callback::openAvatarPreview, - onVerifyClick = callback::onVerifyUser, + openAvatarPreview = callback::navigateToAvatarPreview, + onVerifyClick = callback::startVerifyUserFlow, ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt index d5cf9e85cb..f47ea0e8b3 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -111,7 +112,7 @@ class RoomMemberDetailsPresenter( } } - fun eventSink(event: UserProfileEvents) { + fun handleEvent(event: UserProfileEvents) { when (event) { UserProfileEvents.WithdrawVerification -> coroutineScope.launch { encryptionService.withdrawVerification(roomMemberId) @@ -129,7 +130,7 @@ class RoomMemberDetailsPresenter( avatarUrl = roomUserAvatar ?: userProfileState.avatarUrl, verificationState = verificationState, snackbarMessage = snackbarMessage, - eventSink = ::eventSink + eventSink = ::handleEvent, ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsEvents.kt index 3fd468d6c5..81bfd86f82 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsEvents.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsItem.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsItem.kt index 700088053a..2d5418afdc 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsItem.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt index 0e5a9b23b1..fd3ce00a79 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,12 +14,12 @@ import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.annotations.ContributesNode import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.RoomScope import io.element.android.services.analytics.api.AnalyticsService @@ -34,17 +35,16 @@ class RoomNotificationSettingsNode( data class RoomNotificationSettingInput( val showUserDefinedSettingStyle: Boolean ) : NodeInputs - interface Callback : Plugin { - fun openGlobalNotificationSettings() - } - private val inputs = inputs() - private val callbacks = plugins() - private fun openGlobalNotificationSettings() { - callbacks.forEach { it.openGlobalNotificationSettings() } + interface Callback : Plugin { + fun navigateToGlobalNotificationSettings() } + private val callback: Callback = callback() + private val inputs = inputs() + private val presenter = presenterFactory.create(inputs.showUserDefinedSettingStyle) + init { lifecycle.subscribe( onResume = { @@ -59,8 +59,8 @@ class RoomNotificationSettingsNode( RoomNotificationSettingsView( state = state, modifier = modifier, - onShowGlobalNotifications = this::openGlobalNotificationSettings, - onBackClick = this::navigateUp, + onShowGlobalNotifications = callback::navigateToGlobalNotificationSettings, + onBackClick = ::navigateUp, ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsOption.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsOption.kt index 9a9b611958..8ff1be538f 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsOption.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsOption.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsOptions.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsOptions.kt index 1b875bb81b..b721138714 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsOptions.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsOptions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt index 413e71169a..b3a88d3f6f 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -99,7 +100,7 @@ class RoomNotificationSettingsPresenter( !notificationSettingsService.canHomeServerPushEncryptedEventsToDevice().getOrDefault(true) } - fun handleEvents(event: RoomNotificationSettingsEvents) { + fun handleEvent(event: RoomNotificationSettingsEvents) { when (event) { is RoomNotificationSettingsEvents.ChangeRoomNotificationMode -> { localCoroutineScope.setRoomNotificationMode(event.mode, pendingRoomNotificationMode, pendingSetDefault, setNotificationSettingAction) @@ -135,7 +136,7 @@ class RoomNotificationSettingsPresenter( setNotificationSettingAction = setNotificationSettingAction.value, restoreDefaultAction = restoreDefaultAction.value, displayMentionsOnlyDisclaimer = shouldDisplayMentionsOnlyDisclaimer, - eventSink = { handleEvents(it) }, + eventSink = ::handleEvent, ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt index 48689ad607..a2ea5e8fa7 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt index 18e95f6ab7..603d22b502 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt index bcd2e59d3b..5a33066d1d 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsStateProvider.kt index 7512b465cb..039f489bbf 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt index 00c545b853..f66316b737 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/UserDefinedRoomNotificationSettingsView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsFlowNode.kt deleted file mode 100644 index 62689489eb..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsFlowNode.kt +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.roomdetails.impl.rolesandpermissions - -import android.os.Parcelable -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.lifecycle.coroutineScope -import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.navmodel.backstack.BackStack -import com.bumble.appyx.navmodel.backstack.operation.pop -import com.bumble.appyx.navmodel.backstack.operation.push -import dev.zacsweers.metro.Assisted -import dev.zacsweers.metro.AssistedInject -import io.element.android.annotations.ContributesNode -import io.element.android.features.changeroommemberroes.api.ChangeRoomMemberRolesEntryPoint -import io.element.android.features.changeroommemberroes.api.ChangeRoomMemberRolesListType -import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsNode -import io.element.android.features.roomdetails.impl.rolesandpermissions.permissions.ChangeRoomPermissionsSection -import io.element.android.libraries.architecture.BackstackView -import io.element.android.libraries.architecture.BaseFlowNode -import io.element.android.libraries.architecture.createNode -import io.element.android.libraries.di.RoomScope -import io.element.android.libraries.matrix.api.room.JoinedRoom -import kotlinx.coroutines.launch -import kotlinx.parcelize.Parcelize - -@ContributesNode(RoomScope::class) -@AssistedInject -class RolesAndPermissionsFlowNode( - @Assisted buildContext: BuildContext, - @Assisted plugins: List, - private val changeRoomMemberRolesEntryPoint: ChangeRoomMemberRolesEntryPoint, - private val joinedRoom: JoinedRoom, -) : BaseFlowNode( - backstack = BackStack( - initialElement = NavTarget.AdminSettings, - savedStateMap = buildContext.savedStateMap, - ), - buildContext = buildContext, - plugins = plugins, -) { - sealed interface NavTarget : Parcelable { - @Parcelize - data object AdminSettings : NavTarget - - @Parcelize - data object AdminList : NavTarget - - @Parcelize - data object ModeratorList : NavTarget - - @Parcelize - data class ChangeRoomPermissions(val section: ChangeRoomPermissionsSection) : NavTarget - } - - override fun onBuilt() { - super.onBuilt() - whenChildAttached { lifecycle, node: ChangeRoomMemberRolesEntryPoint.NodeProxy -> - lifecycle.coroutineScope.launch { - node.waitForRoleChanged() - backstack.pop() - } - } - } - - override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { - return when (navTarget) { - is NavTarget.AdminSettings -> { - val callback = object : RolesAndPermissionsNode.Callback { - override fun openAdminList() { - backstack.push(NavTarget.AdminList) - } - - override fun openModeratorList() { - backstack.push(NavTarget.ModeratorList) - } - - override fun openEditRoomDetailsPermissions() { - backstack.push(NavTarget.ChangeRoomPermissions(ChangeRoomPermissionsSection.RoomDetails)) - } - - override fun openMessagesAndContentPermissions() { - backstack.push(NavTarget.ChangeRoomPermissions(ChangeRoomPermissionsSection.MessagesAndContent)) - } - - override fun openModerationPermissions() { - backstack.push(NavTarget.ChangeRoomPermissions(ChangeRoomPermissionsSection.MembershipModeration)) - } - } - createNode( - buildContext = buildContext, - plugins = listOf(callback), - ) - } - is NavTarget.AdminList -> { - changeRoomMemberRolesEntryPoint.builder(this, buildContext) - .room(joinedRoom) - .listType(ChangeRoomMemberRolesListType.Admins) - .build() - } - is NavTarget.ModeratorList -> { - changeRoomMemberRolesEntryPoint.builder(this, buildContext) - .room(joinedRoom) - .listType(ChangeRoomMemberRolesListType.Moderators) - .build() - } - is NavTarget.ChangeRoomPermissions -> { - val inputs = ChangeRoomPermissionsNode.Inputs(navTarget.section) - createNode( - buildContext = buildContext, - plugins = listOf(inputs), - ) - } - } - } - - @Composable - override fun View(modifier: Modifier) { - BackstackView() - } -} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsState.kt deleted file mode 100644 index 24f645a309..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsState.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.roomdetails.impl.rolesandpermissions - -import io.element.android.libraries.architecture.AsyncAction - -data class RolesAndPermissionsState( - val roomSupportsOwnerRole: Boolean, - val adminCount: Int, - val moderatorCount: Int, - val canDemoteSelf: Boolean, - val changeOwnRoleAction: AsyncAction, - val resetPermissionsAction: AsyncAction, - val eventSink: (RolesAndPermissionsEvents) -> Unit, -) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsNode.kt deleted file mode 100644 index cebcc56e7f..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsNode.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions - -import android.os.Parcelable -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin -import dev.zacsweers.metro.Assisted -import dev.zacsweers.metro.AssistedInject -import io.element.android.annotations.ContributesNode -import io.element.android.libraries.architecture.NodeInputs -import io.element.android.libraries.architecture.inputs -import io.element.android.libraries.di.RoomScope -import kotlinx.parcelize.Parcelize - -@ContributesNode(RoomScope::class) -@AssistedInject -class ChangeRoomPermissionsNode( - @Assisted buildContext: BuildContext, - @Assisted plugins: List, - presenterFactory: ChangeRoomPermissionsPresenter.Factory, -) : Node(buildContext, plugins = plugins) { - @Parcelize - data class Inputs( - val section: ChangeRoomPermissionsSection, - ) : NodeInputs, Parcelable - - private val inputs: Inputs = inputs() - private val presenter = presenterFactory.create(inputs.section) - - @Composable - override fun View(modifier: Modifier) { - val state = presenter.present() - ChangeRoomPermissionsView( - modifier = modifier, - state = state, - onBackClick = this::navigateUp, - ) - } -} - -@Parcelize -enum class ChangeRoomPermissionsSection : Parcelable { - RoomDetails, - MessagesAndContent, - MembershipModeration, -} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsState.kt deleted file mode 100644 index 5e7b77560a..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsState.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions - -import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues -import kotlinx.collections.immutable.ImmutableList - -data class ChangeRoomPermissionsState( - val section: ChangeRoomPermissionsSection, - val currentPermissions: RoomPowerLevelsValues?, - val items: ImmutableList, - val hasChanges: Boolean, - val saveAction: AsyncAction, - val confirmExitAction: AsyncAction, - val eventSink: (ChangeRoomPermissionsEvent) -> Unit, -) - -enum class RoomPermissionType { - BAN, - INVITE, - KICK, - SEND_EVENTS, - REDACT_EVENTS, - ROOM_NAME, - ROOM_AVATAR, - ROOM_TOPIC -} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsView.kt deleted file mode 100644 index 6acf4935dc..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/permissions/ChangeRoomPermissionsView.kt +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.roomdetails.impl.rolesandpermissions.permissions - -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.PreviewParameter -import io.element.android.compound.tokens.generated.CompoundIcons -import io.element.android.features.roomdetails.impl.R -import io.element.android.libraries.core.bool.orFalse -import io.element.android.libraries.designsystem.components.async.AsyncActionView -import io.element.android.libraries.designsystem.components.button.BackButton -import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog -import io.element.android.libraries.designsystem.components.list.ListItemContent -import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.designsystem.theme.components.IconSource -import io.element.android.libraries.designsystem.theme.components.ListItem -import io.element.android.libraries.designsystem.theme.components.ListItemStyle -import io.element.android.libraries.designsystem.theme.components.ListSectionHeader -import io.element.android.libraries.designsystem.theme.components.Scaffold -import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.designsystem.theme.components.TextButton -import io.element.android.libraries.designsystem.theme.components.TopAppBar -import io.element.android.libraries.matrix.api.room.RoomMember -import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues -import io.element.android.libraries.ui.strings.CommonStrings - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun ChangeRoomPermissionsView( - state: ChangeRoomPermissionsState, - onBackClick: () -> Unit, - modifier: Modifier = Modifier, -) { - BackHandler { - state.eventSink(ChangeRoomPermissionsEvent.Exit) - } - Scaffold( - modifier = modifier, - topBar = { - val title = when (state.section) { - ChangeRoomPermissionsSection.RoomDetails -> stringResource(R.string.screen_room_change_permissions_room_details) - ChangeRoomPermissionsSection.MessagesAndContent -> stringResource(R.string.screen_room_change_permissions_messages_and_content) - ChangeRoomPermissionsSection.MembershipModeration -> stringResource(R.string.screen_room_change_permissions_member_moderation) - } - TopAppBar( - titleStr = title, - navigationIcon = { - BackButton(onClick = { state.eventSink(ChangeRoomPermissionsEvent.Exit) }) - }, - actions = { - TextButton( - text = stringResource(CommonStrings.action_save), - onClick = { state.eventSink(ChangeRoomPermissionsEvent.Save) }, - enabled = state.hasChanges, - ) - } - ) - } - ) { padding -> - LazyColumn( - modifier = Modifier - .padding(padding) - .fillMaxSize() - ) { - for ((index, permissionItem) in state.items.withIndex()) { - item { - ListSectionHeader(titleForSection(item = permissionItem), hasDivider = index > 0) - SelectRoleItem( - permissionsItem = permissionItem, - role = RoomMember.Role.Admin, - currentPermissions = state.currentPermissions - ) { item, role -> - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) - } - SelectRoleItem( - permissionsItem = permissionItem, - role = RoomMember.Role.Moderator, - currentPermissions = state.currentPermissions - ) { item, role -> - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) - } - SelectRoleItem( - permissionsItem = permissionItem, - role = RoomMember.Role.User, - currentPermissions = state.currentPermissions - ) { item, role -> - state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role)) - } - } - } - } - } - - AsyncActionView( - async = state.saveAction, - onSuccess = { onBackClick() }, - onErrorDismiss = { state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions) } - ) - - AsyncActionView( - async = state.confirmExitAction, - onSuccess = { onBackClick() }, - confirmationDialog = { - ConfirmationDialog( - title = stringResource(R.string.screen_room_change_role_unsaved_changes_title), - content = stringResource(R.string.screen_room_change_role_unsaved_changes_description), - submitText = stringResource(CommonStrings.action_save), - cancelText = stringResource(CommonStrings.action_discard), - onSubmitClick = { state.eventSink(ChangeRoomPermissionsEvent.Save) }, - onDismiss = { state.eventSink(ChangeRoomPermissionsEvent.Exit) } - ) - }, - onErrorDismiss = {}, - ) -} - -@Composable -private fun SelectRoleItem( - permissionsItem: RoomPermissionType, - role: RoomMember.Role, - currentPermissions: RoomPowerLevelsValues?, - onClick: (RoomPermissionType, RoomMember.Role) -> Unit -) { - val title = when (role) { - RoomMember.Role.Admin -> stringResource(R.string.screen_room_change_permissions_administrators) - RoomMember.Role.Moderator -> stringResource(R.string.screen_room_change_permissions_moderators) - RoomMember.Role.User -> stringResource(R.string.screen_room_change_permissions_everyone) - else -> error("Unsupported role selected: $role") - } - ListItem( - headlineContent = { Text(text = title) }, - trailingContent = if (currentPermissions?.isSelected(permissionsItem, role).orFalse()) { - ListItemContent.Icon(IconSource.Vector(CompoundIcons.Check())) - } else { - null - }, - style = ListItemStyle.Primary, - onClick = { onClick(permissionsItem, role) }, - ) -} - -private fun RoomPowerLevelsValues.isSelected(item: RoomPermissionType, role: RoomMember.Role): Boolean { - return when (item) { - RoomPermissionType.BAN -> RoomMember.Role.forPowerLevel(ban) == role - RoomPermissionType.INVITE -> RoomMember.Role.forPowerLevel(invite) == role - RoomPermissionType.KICK -> RoomMember.Role.forPowerLevel(kick) == role - RoomPermissionType.SEND_EVENTS -> RoomMember.Role.forPowerLevel(sendEvents) == role - RoomPermissionType.REDACT_EVENTS -> RoomMember.Role.forPowerLevel(redactEvents) == role - RoomPermissionType.ROOM_NAME -> RoomMember.Role.forPowerLevel(roomName) == role - RoomPermissionType.ROOM_AVATAR -> RoomMember.Role.forPowerLevel(roomAvatar) == role - RoomPermissionType.ROOM_TOPIC -> RoomMember.Role.forPowerLevel(roomTopic) == role - } -} - -@Composable -private fun titleForSection(item: RoomPermissionType): String = when (item) { - RoomPermissionType.INVITE -> stringResource(R.string.screen_room_change_permissions_invite_people) - RoomPermissionType.KICK -> stringResource(R.string.screen_room_change_permissions_remove_people) - RoomPermissionType.BAN -> stringResource(R.string.screen_room_change_permissions_ban_people) - RoomPermissionType.SEND_EVENTS -> stringResource(R.string.screen_room_change_permissions_send_messages) - RoomPermissionType.REDACT_EVENTS -> stringResource(R.string.screen_room_change_permissions_delete_messages) - RoomPermissionType.ROOM_NAME -> stringResource(R.string.screen_room_change_permissions_room_name) - RoomPermissionType.ROOM_AVATAR -> stringResource(R.string.screen_room_change_permissions_room_avatar) - RoomPermissionType.ROOM_TOPIC -> stringResource(R.string.screen_room_change_permissions_room_topic) -} - -@PreviewsDayNight -@Composable -internal fun ChangeRoomPermissionsViewPreview(@PreviewParameter(ChangeRoomPermissionsStateProvider::class) state: ChangeRoomPermissionsState) { - ElementPreview { - ChangeRoomPermissionsView( - state = state, - onBackClick = {}, - ) - } -} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyEvents.kt deleted file mode 100644 index 5e09405998..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyEvents.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.roomdetails.impl.securityandprivacy - -sealed interface SecurityAndPrivacyEvents { - data object EditRoomAddress : SecurityAndPrivacyEvents - data object Save : SecurityAndPrivacyEvents - data class ChangeRoomAccess(val roomAccess: SecurityAndPrivacyRoomAccess) : SecurityAndPrivacyEvents - data object ToggleEncryptionState : SecurityAndPrivacyEvents - data object CancelEnableEncryption : SecurityAndPrivacyEvents - data object ConfirmEnableEncryption : SecurityAndPrivacyEvents - data class ChangeHistoryVisibility(val historyVisibility: SecurityAndPrivacyHistoryVisibility) : SecurityAndPrivacyEvents - data object ToggleRoomVisibility : SecurityAndPrivacyEvents - data object DismissSaveError : SecurityAndPrivacyEvents -} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyStateProvider.kt deleted file mode 100644 index 00001ff69d..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyStateProvider.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.roomdetails.impl.securityandprivacy - -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.roomdetails.impl.securityandprivacy.permissions.SecurityAndPrivacyPermissions -import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.architecture.AsyncData - -open class SecurityAndPrivacyStateProvider : PreviewParameterProvider { - override val values: Sequence - get() = sequenceOf( - aSecurityAndPrivacyState(), - aSecurityAndPrivacyState( - editedSettings = aSecurityAndPrivacySettings( - roomAccess = SecurityAndPrivacyRoomAccess.AskToJoin - ) - ), - aSecurityAndPrivacyState( - editedSettings = aSecurityAndPrivacySettings( - roomAccess = SecurityAndPrivacyRoomAccess.Anyone, - isEncrypted = false, - ) - ), - aSecurityAndPrivacyState( - savedSettings = aSecurityAndPrivacySettings( - roomAccess = SecurityAndPrivacyRoomAccess.SpaceMember - ) - ), - aSecurityAndPrivacyState( - editedSettings = aSecurityAndPrivacySettings( - roomAccess = SecurityAndPrivacyRoomAccess.Anyone, - address = "#therapy:myserver.xyz" - ) - ), - aSecurityAndPrivacyState( - editedSettings = aSecurityAndPrivacySettings( - isVisibleInRoomDirectory = AsyncData.Loading() - ) - ), - aSecurityAndPrivacyState( - editedSettings = aSecurityAndPrivacySettings( - isVisibleInRoomDirectory = AsyncData.Success(true) - ) - ), - aSecurityAndPrivacyState( - showEncryptionConfirmation = true - ), - aSecurityAndPrivacyState( - saveAction = AsyncAction.Loading - ), - ) -} - -fun aSecurityAndPrivacySettings( - roomAccess: SecurityAndPrivacyRoomAccess = SecurityAndPrivacyRoomAccess.InviteOnly, - isEncrypted: Boolean = true, - address: String? = null, - historyVisibility: SecurityAndPrivacyHistoryVisibility = SecurityAndPrivacyHistoryVisibility.SinceSelection, - isVisibleInRoomDirectory: AsyncData = AsyncData.Uninitialized, -) = SecurityAndPrivacySettings( - roomAccess = roomAccess, - isEncrypted = isEncrypted, - address = address, - historyVisibility = historyVisibility, - isVisibleInRoomDirectory = isVisibleInRoomDirectory -) - -fun aSecurityAndPrivacyState( - savedSettings: SecurityAndPrivacySettings = aSecurityAndPrivacySettings(), - editedSettings: SecurityAndPrivacySettings = savedSettings, - homeserverName: String = "myserver.xyz", - showEncryptionConfirmation: Boolean = false, - saveAction: AsyncAction = AsyncAction.Uninitialized, - permissions: SecurityAndPrivacyPermissions = SecurityAndPrivacyPermissions( - canChangeRoomAccess = true, - canChangeHistoryVisibility = true, - canChangeEncryption = true, - canChangeRoomVisibility = true - ), - eventSink: (SecurityAndPrivacyEvents) -> Unit = {} -) = SecurityAndPrivacyState( - editedSettings = editedSettings, - savedSettings = savedSettings, - homeserverName = homeserverName, - showEnableEncryptionConfirmation = showEncryptionConfirmation, - saveAction = saveAction, - permissions = permissions, - eventSink = eventSink -) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/permissions/SecurityAndPrivacyPermissions.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/permissions/SecurityAndPrivacyPermissions.kt deleted file mode 100644 index cc052b19b1..0000000000 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/permissions/SecurityAndPrivacyPermissions.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.features.roomdetails.impl.securityandprivacy.permissions - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.produceState -import io.element.android.features.roomdetails.impl.securityandprivacy.permissions.SecurityAndPrivacyPermissions.Companion.DEFAULT -import io.element.android.libraries.matrix.api.room.BaseRoom -import io.element.android.libraries.matrix.api.room.StateEventType -import io.element.android.libraries.matrix.api.room.powerlevels.canSendState - -data class SecurityAndPrivacyPermissions( - val canChangeRoomAccess: Boolean, - val canChangeHistoryVisibility: Boolean, - val canChangeEncryption: Boolean, - val canChangeRoomVisibility: Boolean, -) { - val hasAny = canChangeRoomAccess || - canChangeHistoryVisibility || - canChangeEncryption || - canChangeRoomVisibility - - companion object { - val DEFAULT = SecurityAndPrivacyPermissions( - canChangeRoomAccess = false, - canChangeHistoryVisibility = false, - canChangeEncryption = false, - canChangeRoomVisibility = false, - ) - } -} - -@Composable -fun BaseRoom.securityAndPrivacyPermissionsAsState(updateKey: Long): State { - return produceState(DEFAULT, key1 = updateKey) { - value = SecurityAndPrivacyPermissions( - canChangeRoomAccess = canSendState(type = StateEventType.ROOM_JOIN_RULES).getOrElse { false }, - canChangeHistoryVisibility = canSendState(type = StateEventType.ROOM_HISTORY_VISIBILITY).getOrElse { false }, - canChangeEncryption = canSendState(type = StateEventType.ROOM_ENCRYPTION).getOrElse { false }, - canChangeRoomVisibility = canSendState(type = StateEventType.ROOM_CANONICAL_ALIAS).getOrElse { false }, - ) - } -} diff --git a/features/roomdetails/impl/src/main/res/values-be/translations.xml b/features/roomdetails/impl/src/main/res/values-be/translations.xml index 8ab30212b7..a986cd9789 100644 --- a/features/roomdetails/impl/src/main/res/values-be/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-be/translations.xml @@ -6,14 +6,12 @@ "Толькі адміністратары" "Заблакіраваць людзей" "Выдаліць паведамленні" - "Усе" "Запрашайце людзей і прымайце запыты на далучэнне" - "Мадэрацыя ўдзельнікаў" "Паведамленні і змест" "Адміністратары і мадэратары" "Выдаляйце людзей і адхіляйце запыты на далучэнне" "Змяніць аватар пакоя" - "Дэталі пакоя" + "Рэдагаваць пакой" "Змяніць назву пакоя" "Змяніць тэму пакоя" "Адправіць паведамленні" @@ -69,12 +67,10 @@ "Толькі выдаліць удзельніка" "Разблакіраваць" "Яны змогуць зноў далучыцца да гэтага пакоя, калі іх запросяць." - "Разблакіраваць удзельніка" "Заблакіраваныя" "Удзельнікі" - "У чаканні" - "Адміністратар" - "Мадэратар" + "Толькі адміністратары" + "Адміністратары і мадэратары" "Удзельнікі пакоя" "Разблакіроўка %1$s" "Дазволіць уласную наладу" @@ -98,7 +94,6 @@ "Мадэрацыя ўдзельнікаў" "Паведамленні і змест" "Мадэратары" - "Дазволы" "Скінуць дазволы" "Пасля скіду дазволаў вы страціце бягучыя налады." "Скінуць дазволы?" @@ -107,5 +102,4 @@ "Ролі і дазволы" "Папрасіце далучыцца" "Хто заўгодна" - "Хто заўгодна" diff --git a/features/roomdetails/impl/src/main/res/values-bg/translations.xml b/features/roomdetails/impl/src/main/res/values-bg/translations.xml index cfe719f190..340f14a8f1 100644 --- a/features/roomdetails/impl/src/main/res/values-bg/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-bg/translations.xml @@ -5,13 +5,11 @@ "Анкети" "Само администратори" "Премахване на съобщения" - "Всеки" "Поканване на хора и приемане на заявки за присъединяване" - "Модериране на членове" "Съобщения и съдържание" "Администратори и модератори" "Премахване на хора и отхвърляне на заявки за присъединяване" - "Подробности за стаята" + "Редактиране на стаята" "Промяна на името на стаята" "Промяна на темата на стаята" "Изпращане на съобщения" @@ -56,8 +54,8 @@ "%1$d души" "Членове" - "Администратор" - "Модератор" + "Само администратори" + "Администратори и модератори" "Членове на стаята" "Разрешаване на персонализирана настройка" "Включването на това ще замени вашата настройка по подразбиране" @@ -77,7 +75,6 @@ "Модериране на членове" "Съобщения и съдържание" "Модератори" - "Разрешения" "Нулиране на разрешенията" "Роли" "Подробности за стаята" @@ -93,14 +90,11 @@ "Хората могат да се присъединят само ако са поканени" "Само с покана" "Достъп до стаята" - "Пространствата в момента не се поддържат" "Членове на пространството" - "Ще ви е необходим адрес на стаята, за да я направите видима в директорията на стаите." + "Пространствата в момента не се поддържат" "Видима в директорията на обществените стаи" - "Всеки" "Кой може да чете историята" "Само за членове откакто са поканени" "Само за членове от избирането на тази опция" - "Видимост на стаята" "Защита и поверителност" diff --git a/features/roomdetails/impl/src/main/res/values-cs/translations.xml b/features/roomdetails/impl/src/main/res/values-cs/translations.xml index e5186f1d4f..fc19bf400e 100644 --- a/features/roomdetails/impl/src/main/res/values-cs/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-cs/translations.xml @@ -1,21 +1,21 @@ - "Aby byla místnost v adresáři viditelná, je třeba zadat její adresu." - "Adresa místnosti" + "Budete potřebovat adresu místnosti, aby byla viditelná v adresáři místností." + "Upravit adresu" "Při aktualizaci nastavení oznámení došlo k chybě." "Váš domovský server tuto možnost v zašifrovaných místnostech nepodporuje, v některých místnostech nemusíte být upozorněni." "Hlasování" - "Pouze správci" + "Správce" "Vykázat lidi" "Odstranit zprávy" - "Všichni" - "Pozvěte lidi a přijímejte žádosti o připojení" - "Moderování členů" + "Člen" + "Pozvat přátele" + "Spravovat členy" "Zprávy a obsah" - "Správci a moderátoři" - "Odeberte lidi a odmítněte žádosti o připojení" + "Moderátor" + "Odebrat osoby" "Změnit avatar místnosti" - "Podrobnosti místnosti" + "Upravit podrobnosti" "Změnit název místnosti" "Změnit téma místnosti" "Odeslat zprávy" @@ -42,7 +42,7 @@ "Šifrováno" "Není šifrováno" "Veřejná místnost" - "Upravit místnost" + "Upravit podrobnosti" "Došlo k neznámé chybě a informace nebylo možné změnit." "Nelze aktualizovat místnost" "Zprávy jsou zabezpečeny zámky. Pouze vy a příjemci máte jedinečné klíče k jejich odemčení." @@ -62,7 +62,7 @@ "Připnuté zprávy" "Profil" "Žádosti o vstup" - "Role a oprávnění" + "Role a oprávnění" "Název místnosti" "Zabezpečení a soukromí" "Zabezpečení" @@ -71,6 +71,13 @@ "Téma" "Aktualizace místnosti…" "V této místnosti nejsou žádní vykázaní uživatelé." + + "%1$d vykázán(a)" + "%1$d vykázáni" + "%1$d vykázáných" + + "Zkontrolujte pravopis nebo zkuste nové vyhledávání" + "Žádné výsledky pro “%1$s”" "%1$d osoba" "%1$d osoby" @@ -80,10 +87,15 @@ "Pouze odebrat člena" "Zrušit vykázání" "Pokud budou pozváni, budou se moci do této místnosti znovu připojit." - "Zrušit vykázání uživatele" + "Zrušit vykázání z místnosti" "Vykázaní" "Členové" - "Nevyřízeno" + + "%1$d pozván(a)" + "%1$d pozváni" + "%1$d pozvaných" + + "Čekající" "Správce" "Moderátor" "Vlastník" @@ -118,9 +130,9 @@ "Obnovit oprávnění?" "Role" "Podrobnosti místnosti" - "Role a oprávnění" - "Přidat adresu místnosti" - "Kdokoli může požádat o vstup do místnosti, ale správce nebo moderátor bude muset žádost přijmout." + "Role a oprávnění" + "Přidat adresu" + "Všichni musí požádat o přístup." "Požádat o připojení" "Ano, povolit šifrování" "Po aktivaci nelze šifrování místnosti deaktivovat. Historie zpráv bude viditelná pouze pro členy místnosti od doby, kdy byli pozváni nebo od té doby, co do místnosti vstoupili. @@ -130,17 +142,21 @@ Nedoporučujeme povolovat šifrování pro místnosti, které může kdokoli naj "Jakmile je povoleno, šifrování nelze zakázat." "Šifrování" "Povolit koncové šifrování" - "Každý může najít a vstoupit" + "Vstoupit může kdokoli." "Kdokoliv" - "Lidé mohou vstoupit, pouze pokud jsou pozváni" + "Vyberte, kteří členové prostorů se k této místnosti mohou připojit bez pozvánky. %1$s" + "Vstoupit mohou pouze pozvaní lidé." "Pouze pro zvané" - "Přístup do místnosti" - "Prostory nejsou aktuálně podporovány" + "Přístup" + "Vstoupit může kdokoli z autorizovaných prostorů." + "Vstoupit může kdokoli v %1$s." "Členové prostoru" + "Prostory nejsou aktuálně podporovány" "Budete potřebovat adresu místnosti, aby byla viditelná v adresáři místností." - "Adresa místnosti" + "Adresa" "Umožněte nalezení této místnosti prohledáním adresáře veřejných místností na %1$s" - "Viditelné v adresáři veřejných místností" + "Umožnit nalezení vyhledáváním ve veřejném adresáři." + "Viditelné ve veřejném adresáři" "Kdokoliv" "Kdo může číst historii" "Pouze členové od té doby, co byli pozváni" @@ -148,8 +164,7 @@ Nedoporučujeme povolovat šifrování pro místnosti, které může kdokoli naj "Adresy místností představují způsoby, jak najít místnosti a získat k nim přístup. Díky tomu můžete svoji místnost snadno sdílet s ostatními. Můžete se rozhodnout publikovat svou místnost ve veřejném adresáři místnosti vašeho domovského serveru." "Publikování místnosti" - "Adresy místností představují způsoby, jak najít místnosti a získat k nim přístup. Díky tomu můžete svoji místnost snadno sdílet s ostatními. -Adresa je také vyžadována pro zobrazení místnosti v adresáři veřejných místností %1$s." - "Viditelnost místnosti" + "Adresy slouží k vyhledávání a přístupu do místností a prostorů. Díky tomu je také můžete snadno sdílet s ostatními." + "Viditelnost" "Zabezpečení a soukromí" diff --git a/features/roomdetails/impl/src/main/res/values-cy/translations.xml b/features/roomdetails/impl/src/main/res/values-cy/translations.xml index 40c404d69b..97d39509fa 100644 --- a/features/roomdetails/impl/src/main/res/values-cy/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-cy/translations.xml @@ -15,7 +15,7 @@ "Gweinyddwyr a chymedrolwyr" "Dileu pobl a gwrthod ceisiadau i ymuno" "Newid afatar ystafell" - "Manylion yr ystafell" + "Ystafell Golygu" "Newid enw\'r ystafell" "Newid pwnc yr ystafell" "Anfon negeseuon" @@ -83,12 +83,11 @@ "Dileu aelod yn unig" "Adfer" "Fyddan nhw yn gallu ymuno â\'r ystafell hon eto os cân nhw wahoddiad." - "Gwahardd defnyddiwr" + "Dad-wahardd o\'r ystafell" "Wedi\'i wahardd" "Aelodau" - "Dan ystyriaeth" - "Gweinyddwr" - "Cymedrolwr" + "Gweinyddwyr yn unig" + "Gweinyddwyr a chymedrolwyr" "Perchennog" "Aelodau\'r ystafell" "Dad-wahardd %1$s" @@ -115,7 +114,6 @@ "Negeseuon a chynnwys" "Cymedrolwyr" "Perchnogion" - "Caniatâd" "Ailosod caniatâd" "Ar ôl i chi ailosod caniatâd, byddwch yn colli\'r gosodiadau cyfredol." "Ailosod caniatâd?" @@ -138,9 +136,9 @@ Nid ydym yn argymell galluogi amgryptio ar gyfer ystafelloedd y gall unrhyw un d "Dim ond os cawn nhw wahoddiad gall pobl ymuno" "Gwahoddiad yn unig" "Mynediad ystafell" - "Nid yw gofodau\'n cael eu cefnogi ar hyn o bryd" "Aelodau gofod" - "Bydd angen cyfeiriad ystafell arnoch er mwyn ei wneud yn weladwy yn y cyfeiriadur ystafelloedd." + "Nid yw gofodau\'n cael eu cefnogi ar hyn o bryd" + "Bydd angen cyfeiriad ystafell arnoch i\'w wneud yn weladwy yn y cyfeiriadur." "Cyfeiriad yr ystafell" "Caniatáu i\'r ystafell hon gael ei chanfod trwy chwilio cyfeiriadur ystafelloedd cyhoeddus %1$s" "Gweladwy yn y cyfeiriadur ystafelloedd cyhoeddus" @@ -151,8 +149,6 @@ Nid ydym yn argymell galluogi amgryptio ar gyfer ystafelloedd y gall unrhyw un d "Mae cyfeiriadau ystafelloedd yn ffyrdd o ddod o hyd i ystafelloedd a chael mynediad iddyn nhw. Mae hyn hefyd yn sicrhau y gallwch chi rannu\'ch ystafell yn hawdd ag eraill. Gallwch ddewis cyhoeddi eich ystafell yng ngweinydd cartref eich cyfeiriadur ystafelloedd cyhoeddus." "Cyhoeddi ystafell" - "Mae cyfeiriadau ystafelloedd yn ffyrdd o ddod o hyd i ystafelloedd a chael mynediad iddyn nhw. Mae hyn hefyd yn sicrhau y gallwch chi rannu\'ch ystafell yn hawdd ag eraill. -Mae angen y cyfeiriad hefyd i wneud yr ystafell yn weladwy yng nghyfeiriadur ystafell gyhoeddus %1$s." "Gwelededd yr ystafell" "Diogelwch a phreifatrwydd" diff --git a/features/roomdetails/impl/src/main/res/values-da/translations.xml b/features/roomdetails/impl/src/main/res/values-da/translations.xml index 77e34d87dc..9da1c66904 100644 --- a/features/roomdetails/impl/src/main/res/values-da/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-da/translations.xml @@ -1,21 +1,21 @@ - "Du skal bruge en rum-adresse for at gøre den synlig i kataloget." - "Rummets adresse" + "Du skal bruge en adresse for at gøre det synligt i det offentlige register." + "Redigér adresse" "Der opstod en fejl under opdatering af notifikationsindstillingen." "Din hjemmeserver understøtter ikke denne mulighed i krypterede rum, og derfor er det muligt at du ikke får besked i alle rum." "Afstemninger" - "Kun admins" + "Administrator" "Spær brugere" "Fjern beskeder" - "Alle" - "Invitér personer og acceptér anmodninger om at deltage" - "Moderation af medlemmer" + "Medlem" + "Invitér andre" + "Administrer medlemmer" "Beskeder og indhold" - "Admins og moderatorer" - "Fjern personer og afvis anmodninger om at deltage" + "Moderator" + "Fjern personer" "Skift rummets avatar" - "Detaljer om rummet" + "Redigér detaljer" "Skift rummets navn" "Skift emne for rummet" "Send beskeder" @@ -42,7 +42,7 @@ "Krypteret" "Ikke krypteret" "Offentligt rum" - "Rediger rum" + "Redigér detaljer" "Der opstod en ukendt fejl, og oplysningerne kunne ikke ændres." "Rummet kunne ikke opdateres" "Beskeder er sikret med låse. Kun du og modtagerne har de unikke nøgler til at låse dem op." @@ -71,6 +71,12 @@ "Emne" "Opdaterer rum…" "Der er ingen spærrede brugere i dette rum." + + "%1$d Spærret" + "%1$d Spærret" + + "Tjek stavningen eller prøv en ny søgning" + "Ingen resultater for \"%1$s\"" "%1$d person" "%1$d personer" @@ -79,11 +85,15 @@ "Fjern kun medlem" "Fjern spærring af" "De vil være i stand til at deltage i dette rum igen, hvis de inviteres." - "Ophæv blokering af bruger fra rum" + "Fjern brugerens spærring fra rummet" "Spærret" "Medlemmer" - "Afventer" - "Admin" + + "%1$d Inviteret" + "%1$d Inviteret" + + "Afventer" + "Administrator" "Moderator" "Ejeren" "Medlemmer af rummet" @@ -118,8 +128,8 @@ "Roller" "Detaljer om rummet" "Roller og tilladelser" - "Tilføj adresse på rum" - "Alle kan bede om at deltage i lokalet, men en administrator eller moderator skal acceptere anmodningen." + "Tilføj adresse" + "Alle skal anmode om adgang." "Spørg om at deltage" "Ja, aktivér kryptering" "Når det først er aktiveret, kan kryptering for et rum ikke deaktiveres igen. Beskedhistorik vil kun være synlig for rummedlemmer, siden de blev inviteret, eller siden de blev medlem af rummet. @@ -129,26 +139,24 @@ Vi anbefaler ikke at aktivere kryptering for rum, som alle kan finde og deltage "Når kryptering først er aktiveret, kan den ikke deaktiveres igen." "Kryptering" "Aktivér end-to-end-kryptering" - "Alle kan finde og deltage" + "Alle kan være med." "Enhver" - "Andre kan kun deltage, hvis de bliver inviteret" - "Kun med invitation" - "Adgang til rummet" - "Klynger understøttes ikke i øjeblikket" - "Medlemmer af klyngen" - "Du skal bruge en adresse til rummet, for at gøre den synlig i rum-registeret." - "Rummets adresse" + "Kun inviterede personer kan deltage i dette rum." + "Kun inviterede" + "Adgang" + "Medlemmer af gruppen" + "Grupper understøttes ikke i øjeblikket" + "Du skal bruge en adresse for at gøre det synligt i det offentlige register." + "Adresse" "Tillad, at dette rum kan findes ved at søge i %1$s fortegnelse over offentlige rum" - "Synlig i det offentlige register over rum" - "Enhver" + "Gør det muligt at blive fundet ved søgninger i det offentlige register." + "Synlig i det offentlige register" "Hvem kan læse historikken?" "Kun medlemmer, efter de blev inviteret" "Kun medlemmer siden valg af denne mulighed" "Rum-adresser er en måde at finde og få adgang til værelser på. Dette sikrer også, at du nemt kan dele dit rum med andre. Du kan vælge at offentliggøre dit rum i din hjemmeservers offentlige katalog over rum." "Udgivelse af rum" - "Rum-adresser er måder at finde og få adgang til rum på. Dette sikrer også, at du nemt kan dele dit rum med andre. -Adressen er også påkrævet for at gøre rummet synligt i det %1$s offentlige register." - "Rummets synlighed" + "Synlighed" "Sikkerhed og privatliv" diff --git a/features/roomdetails/impl/src/main/res/values-de/translations.xml b/features/roomdetails/impl/src/main/res/values-de/translations.xml index 4a4f8a44b6..89ef69c173 100644 --- a/features/roomdetails/impl/src/main/res/values-de/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-de/translations.xml @@ -8,14 +8,12 @@ "Nur Admins" "Mitglieder sperren" "Nachrichten entfernen" - "Alle" "Personen einladen und Beitrittsanfragen annehmen" - "Moderation der Mitglieder" "Nachrichten senden & löschen" "Admins und Moderatoren" "Personen entfernen und Beitrittsanfragen ablehnen" "Avatar ändern" - "Chat-Details anpassen" + "Chat bearbeiten" "Chat-Namen ändern" "Chat Thema ändern" "Nachrichten senden" @@ -70,7 +68,7 @@ "Informationen" "Thema" "Chat wird aktualisiert…" - "In diesem Chat gibt es keine gesperrten Nutzer." + "Es gibt keine gesperrten Nutzer." "%1$d Person" "%1$d Personen" @@ -79,12 +77,11 @@ "Mitglied nur entfernen" "Sperre aufheben" "Die Nutzer können dem Chat wieder beitreten, wenn sie eingeladen werden." - "Nutzer entsperren" + "Sperre für diesen Chat aufheben" "Gesperrt" "Mitglieder" - "Ausstehend" - "Admin" - "Moderator" + "Nur Admins" + "Admins und Moderatoren" "Eigentümer" "Mitglieder" "%1$s wird entsperrt." @@ -111,7 +108,6 @@ "Nachrichten senden & löschen" "Moderatoren" "Eigentümer" - "Berechtigungen" "Rollen und Berechtigungen zurücksetzen" "Sobald du die Berechtigungen zurücksetzt, verlierst du die aktuellen Einstellungen." "Berechtigungen zurücksetzen?" @@ -134,10 +130,10 @@ Wir empfehlen keine Verschlüsselung für Chats zu aktivieren, die jeder finden "Personen können nur beitreten, wenn sie eingeladen werden." "Nur auf Einladung" "Chat Zugang" - "Spaces werden zur Zeit nicht unterstützt." "Spacemitglieder" + "Spaces werden zur Zeit nicht unterstützt." "Du benötigst eine Chat-Adresse, um den Chat im Verzeichnis sichtbar zu machen." - "Chat-Adresse" + "Chatroomadresse" "Erlaube das Auffinden dieses Chats durch Suche im öffentlichen Verzeichnis von %1$s" "Sichtbar im öffentlichen Verzeichnis" "Jeder" @@ -147,8 +143,6 @@ Wir empfehlen keine Verschlüsselung für Chats zu aktivieren, die jeder finden "Chat-Adressen machen es möglich, Chats zu finden und ihnen beizutreten. Dies erleichtert es, Chats mit anderen zu teilen. Auf Wunsch kannst du deinen Chat im öffentlichen Verzeichnis deines Homeservers veröffentlichen." "Veröffentlichung von Chats" - "Chat-Adressen machen es möglich, Chats zu finden und ihnen beizutreten. Dies erleichtert es, Chats mit anderen zu teilen. -Die Adresse ist auch erforderlich, um den Chat im öffentlichen Verzeichnis von %1$s zu veröffentlichen." - " Sichtbarkeit des Chats" + "Chatroomsichtbarkeit." "Sicherheit & Datenschutz" diff --git a/features/roomdetails/impl/src/main/res/values-el/translations.xml b/features/roomdetails/impl/src/main/res/values-el/translations.xml index 678dad15c9..9f481e8d46 100644 --- a/features/roomdetails/impl/src/main/res/values-el/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-el/translations.xml @@ -8,14 +8,12 @@ "Μόνο διαχειριστές" "Αποκλεισμός ατόμων" "Αφαίρεση μηνυμάτων" - "Όλοι" "Προσκάλεσε άτομα και αποδέξου αιτήματα συμμετοχής" - "Συντονισμός μελών" "Μηνύματα και περιεχόμενο" "Διαχειριστές και συντονιστές" "Αφαίρεση ατόμων και απόρριψη αιτημάτων συμμετοχής" "Αλλαγή εικόνας προφίλ αίθουσας" - "Λεπτομέρειες αίθουσας" + "Επεξεργασία Αίθουσας" "Αλλαγή ονόματος αίθουσας" "Αλλαγή θέματος αίθουσας" "Αποστολή μηνυμάτων" @@ -73,12 +71,11 @@ "Μόνο αφαίρεση μέλους" "Αναίρεση αποκλεισμού" "Θα μπορούν να συμμετάσχουν ξανά σε αυτή την αίθουσα, εάν προσκληθούν." - "Άρση αποκλεισμού χρήστη" + "Άρση αποκλεισμού από την αίθουσα" "Αποκλεισμένοι" "Μέλη" - "Σε αναμονή" - "Διαχειριστής" - "Συντονιστής" + "Μόνο διαχειριστές" + "Διαχειριστές και συντονιστές" "Μέλη της αίθουσας" "Άρση αποκλεισμού %1$s" "Να επιτρέπεται η προσαρμοσμένη ρύθμιση" @@ -102,7 +99,6 @@ "Συντονισμός μελών" "Μηνύματα και περιεχόμενο" "Συντονιστές" - "Άδειες" "Επαναφορά δικαιωμάτων" "Μόλις επαναφέρεις τα δικαιώματα, θα χάσεις τις τρέχουσες ρυθμίσεις." "Επαναφορά δικαιωμάτων;" @@ -125,21 +121,16 @@ "Τα άτομα μπορούν να συμμετάσχουν μόνο εάν έχουν προσκληθεί" "Μόνο πρόσκληση" "Πρόσβαση στην αίθουσα" - "Οι χώροι δεν υποστηρίζονται προς το παρόν" "Μέλη χώρου" - "Θα χρειαστείτε μια διεύθυνση αίθουσας για να την κάνετε ορατή στον κατάλογο αιθουσών." - "Διεύθυνση αίθουσας" + "Οι χώροι δεν υποστηρίζονται προς το παρόν" + "Θα χρειαστείτε μια διεύθυνση αίθουσας για να την κάνετε ορατή στον κατάλογο." "Επιστρέψτε την εύρεση αυτής της αίθουσας με αναζήτηση στον κατάλογο %1$s δημοσίων αιθουσών" "Ορατή στον κατάλογο δημόσιων αιθουσών" - "Οποιοσδήποτε" "Ποιος μπορεί να διαβάσει το ιστορικό" "Μόνο μέλη από τη στιγμή που προσκλήθηκαν" "Μόνο για μέλη μετά από αυτήν την επιλογή" "Οι διευθύνσεις αιθουσών είναι τρόποι εύρεσης και πρόσβασης σε αίθουσες. Αυτό διασφαλίζει επίσης ότι μπορείτε εύκολα να μοιραστείτε την αίθουσα με άλλους. Μπορείτε να επιλέξετε να δημοσιεύσετε την αίθουσά σας στον δημόσιο κατάλογο αιθουσών του αρχικού διακομιστή σας." "Δημοσίευση αίθουσας" - "Οι διευθύνσεις αιθουσών είναι τρόποι εύρεσης και πρόσβασης σε αίθουσες. Αυτό εξασφαλίζει επίσης ότι μπορείτε εύκολα να μοιραστείτε την αίθουσα με άλλους. -Η διεύθυνση απαιτείται επίσης για να γίνει η αίθουσα ορατή στον δημόσιο κατάλογο αιθουσών %1$s." - "Ορατότητα αίθουσας" "Ασφάλεια & απόρρητο" diff --git a/features/roomdetails/impl/src/main/res/values-en-rUS/translations.xml b/features/roomdetails/impl/src/main/res/values-en-rUS/translations.xml new file mode 100644 index 0000000000..9a4b56e287 --- /dev/null +++ b/features/roomdetails/impl/src/main/res/values-en-rUS/translations.xml @@ -0,0 +1,5 @@ + + + "Anyone in authorized spaces can join, but everyone else must request access." + "Anyone in authorized spaces can join." + diff --git a/features/roomdetails/impl/src/main/res/values-eo/translations.xml b/features/roomdetails/impl/src/main/res/values-eo/translations.xml deleted file mode 100644 index 46471ca4f3..0000000000 --- a/features/roomdetails/impl/src/main/res/values-eo/translations.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - "Messages are secured with locks. Only you and the recipients can unlock them." - diff --git a/features/roomdetails/impl/src/main/res/values-es/translations.xml b/features/roomdetails/impl/src/main/res/values-es/translations.xml index 96e31eb5c5..43a42bb274 100644 --- a/features/roomdetails/impl/src/main/res/values-es/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-es/translations.xml @@ -8,14 +8,12 @@ "Solo administradores" "Vetar personas" "Eliminar mensajes" - "Todos" "Invitar personas y aceptar solicitudes de unión" - "Moderación de miembros" "Mensajes y contenido" "Administradores y moderadores" "Eliminar personas y rechazar solicitudes de unión" "Cambiar el avatar de la sala" - "Detalles de la sala" + "Editar sala" "Cambiar el nombre de la sala" "Cambiar el tema de la sala" "Enviar mensajes" @@ -73,12 +71,11 @@ "Solo eliminar miembro" "Quitar veto" "Podrá volver a unirse a esta sala si se le invita." - "Quitar veto al usuario" + "Eliminar veto en la sala" "Vetados" "Miembros" - "Pendiente" - "Admin" - "Moderador" + "Solo administradores" + "Administradores y moderadores" "Miembros de la sala" "Levantando veto a %1$s" "Permitir configuración personalizada" @@ -102,7 +99,6 @@ "Moderación de miembros" "Mensajes y contenido" "Moderadores" - "Permisos" "Restablecer permisos" "Una vez que restablezca los permisos, perderá la configuración actual." "¿Restablecer los permisos?" @@ -125,21 +121,16 @@ No recomendamos habilitar el cifrado para las salas que cualquiera pueda encontr "Las personas solo pueden unirse si están invitadas" "Solo por invitación" "Acceso a la sala" - "No se admiten los espacios por el momento." "Miembros del espacio" - "Necesitarás una dirección de sala para que esta sea visible en el directorio de salas." - "Dirección de la sala" + "No se admiten los espacios por el momento." + "Necesitarás una dirección de sala para que sea visible en el directorio." "Permite encontrar esta sala buscando en el directorio de salas públicas de %1$s" "Visible en el directorio de salas públicas" - "Cualquiera" "Quién puede leer el historial" "Solo participantes desde que fueron invitados" "Solo participantes desde que se selecciona esta opción" "Las direcciones de sala son formas de buscar salas y acceder a ellas. Esto también garantiza que puedas compartir fácilmente tu sala con otras personas. Puedes optar por publicar tu sala en el directorio de salas públicas de tu servidor base." "Publicación de la sala" - "Las direcciones de sala son un medio para buscar y acceder a salas. También te permiten compartir fácilmente tu sala con otras personas. -La dirección también es necesaria para hacer visible la sala en el directorio de salas públicas de %1$s." - "Visibilidad de la sala" "Seguridad y privacidad" diff --git a/features/roomdetails/impl/src/main/res/values-et/translations.xml b/features/roomdetails/impl/src/main/res/values-et/translations.xml index 6de3107def..1c45e12cef 100644 --- a/features/roomdetails/impl/src/main/res/values-et/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-et/translations.xml @@ -1,21 +1,21 @@ - "Selleks, et jututuba oleks nähtav jututubade kataloogis, vajab ta aadressi." - "Jututoa aadress" + "Selleks, et jututuba oleks nähtav jututubade avalikus kataloogis, vajab ta aadressi." + "Muuda aadressi" "Teavituste seadistamisel tekkis viga" "Sinu koduserver ei toeta seda funktsionaalsust krüptitud jututubades ja seega ei pruugi kõik teavitused sinuni jõuda." "Küsitlused" - "Vaid peakasutajad" + "Peakasutajad" "Suhtluskeelu seadmine" "Eemalda sõnumid" - "Kõik" - "Kutsu teisi osalejaid ja vasta ise liitumiskutsetele" - "Jututoas osalejate modereerimine" + "Liikmed" + "Osalejate kutsumine" + "Liikmete haldus" "Sõnumid ja sisu" - "Peakasutajad ja moderaatorid" - "Eemalda osalejaid jututoast ja lükka liitumiskutsed tagasi" + "Moderaatorid" + "Osalejate eemaldamine" "Jututoa tunnuspildi muutmine" - "Jututoa üksikasjad" + "Muuda üksikasju" "Jututoa nime muutmine" "Jututoa teema muutmine" "Sõnumite saatmine" @@ -42,7 +42,7 @@ "Krüptitud jututuba" "Krüptimata jututuba" "Avalik jututuba" - "Muuda jututuba" + "Muuda üksikasju" "Tekkis tundmatu viga ja andmed jäid muutmata." "Jututoa andmete muutmine ei õnnestu" "Sõnumid on turvatud krüptimise abil. Ainult sinul ja sõnumite saajatel on vajalikud võtmed nende lugemiseks." @@ -70,7 +70,13 @@ "Jututoa teave" "Teema" "Uuendame jututuba…" - "Jututoas pole suhtluskeeluga kasutajaid" + "Suhtluskeeluga kasutajaid pole" + + "%1$d suhtluskeeluga kasutaja" + "%1$d suhtluskeeluga kasutajat" + + "Palun kontrolli otsingusõna korrektsust ja proovi siis uuesti" + "Otsingul „%1$s“ pole tulemusi" "%1$d osaleja" "%1$d osalejat" @@ -79,12 +85,16 @@ "Ainult eemalda kasutaja" "Eemalda suhtluskeeld" "Kutse olemasolul saab ta nüüd jututoaga uuesti liituda" - "Eemalda kasutaja suhtluskeeld" + "Eemalda suhtluskeeld jututoas" "Suhtluskeeluga kasutajad" "Liikmed" - "Ootel" - "Peakasutaja" - "Moderaator" + + "%1$d saatis kutse" + "%1$d saatis kutse" + + "Ootel" + "Peakasutajad" + "Moderaatorid" "Omanik" "Jututoas osalejad" "Eemaldame suhtluskeelu kasutajalt %1$s" @@ -118,9 +128,11 @@ "Rollid" "Jututoa üksikasjad" "Rollid ja õigused" - "Lisa jututoa aadress" - "Kõik võivad paluda jututoaga liitumist, kuid peakasutaja või moderaator peavad sellega nõustuma." + "Lisa aadress" + "Liituda saavad kõik volitatud kogukondade liikmed, kuid kõik teised peavad küsima võimalust ligipääsuks." + "Kõik võivad paluda jututoaga liitumist." "Küsi võimalust liitumiseks" + "Liituda saavad kõik „%1$s“ kogukonna liikmed, kuid kõik teised peavad küsima võimalust ligipääsuks." "Jah, lülita krüptimine sisse" "Kui jututoa krüptimine on kord sisse lülitatud, siis seda välja lülitada ei saa. Sõnumite ajalugu on nähtav vaid jututoa liikmetele alates kutse saamise või liitumise hetkest. Keegi teine peale jututoa liikmete ei saa sõnumeid lugeda. See võib takistada suhtlusrobotite ja/või võrgusildade toimimist. @@ -129,26 +141,31 @@ Me ei soovita krüptimise kasutamist selliste avalike jututubade puhul, millega "Kui krüptimine on kasutusel, siis seda enam väljalülitada ei saa." "Krüptimine" "Võta läbiv krüptimine kasutusele" - "Kõik võivad jututuba leida ja sellega liituda" - "Kõik" - "Huvilised võivad liituda vaid kutse olemasolul" - "Vaid kutse alusel" - "Ligipääs jututuppa" - "Kogukondade tugi veel puudub" + "Kõik võivad jututoaga liituda" + "Kõik kasutajad" + "Vali kogukonnad, mille liikmed saavad selle jututoaga liituda ilma kutseta. %1$s" + "Halda kogukondi" + "Liituda saab vaid kutse olemasolul" + "Vaid kutsega" + "Ligipääs" + "Liituda saavad kõik volitatud kogukondade liikmed." + "Liituda võivad kõik „%1$s“ liikmed." "Kogukonna liikmed" - "Selleks, et jututuba oleks nähtav jututubade kataloogis, vajab ta aadressi." - "Jututoa aadress" + "Kogukondade tugi veel puudub" + "Selleks, et jututuba oleks nähtav jututubade avalikus kataloogis, vajab ta aadressi." + "Aadress" "Võimalda leida seda jututuba avalikust kataloogist otsides „%1$s“" - "Nähtav jututubade avalikus kataloogis" - "Kõik" + "Luba leitavus avaliku kataloogi otsingust." + "Nähtav avalikus kataloogis" + "Kõik (ajalugu on avalik)" + "Muudatused ei mõjuta varasemaid sõnumeid, ainult uusi. %1$s" "Kes võivad lugeda jututoa ajalugu" "Liikmed peale kutse saamist" - "Liikmed peale selle valiku sisselülitamist" + "Liikmed (terviklik ajalugu)" "Jututoa aadressid annavad võimaluse neid leida ning saada neile ligi. Samuti võimaldab see jututuba teistele huvilistele jagada. Lisaks võid sa jututoa avaldada oma koduserveri avalikus jututubade kataloogis." "Jututoa avaldamine" - "Jututoa aadressid annavad võimaluse neid leida ning saada neile ligi. Samuti võimaldab see jututuba teistele huvilistele jagada. -Lisaks on aadress vajalik, et jututuba oleks %1$s serveri avalikus jututubade kataloogis nähtav." - "Jututoa nähtavus" + "Aadressid on mugav viis jututubade ja kogukondade leidmiseks ning tagab mugava võimaluse nende jagamiseks." + "Nähtavus" "Turvalisus ja privaatsus" diff --git a/features/roomdetails/impl/src/main/res/values-eu/translations.xml b/features/roomdetails/impl/src/main/res/values-eu/translations.xml index 64ec4ad928..95d6b17daf 100644 --- a/features/roomdetails/impl/src/main/res/values-eu/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-eu/translations.xml @@ -7,14 +7,12 @@ "Administratzaileak soilik" "Jarri debekua jendeari" "Kendu mezuak" - "Guztiak" "Gonbidatu jendea" - "Kideen moderazioa" "Mezuak eta edukiak" "Administratzaileak eta moderatzaileak" "Kendu jendea" "Aldatu gelaren abatarra" - "Gelaren xehetasunak" + "Editatu gela" "Aldatu gelaren izena" "Aldatu gelako mintzagaia" "Bidali mezuak" @@ -70,12 +68,10 @@ "Kendu kidea eta ezarri debekua" "Kendu kidea soilik" "Kendu debekua" - "Kendu debekua erabiltzaileari" "Debekatuta" "Kideak" - "Zain" - "Kudeatzailea" - "Moderatzailea" + "Administratzaileak soilik" + "Administratzaileak eta moderatzaileak" "Jabea" "Gelako kideak" "%1$s(r)i debekua kentzen" @@ -98,7 +94,6 @@ "Mezuak eta edukiak" "Moderatzaileak" "Jabeak" - "Baimenak" "Berrezarri baimenak" "Baimenak berrezarritakoan, uneko ezarpenak galduko dituzu." "Baimenak berrezarri?" @@ -113,12 +108,10 @@ "Gonbidatutako pertsonak bakarrik sartu ahal izango dira" "Gonbidapen bidez" "Gelarako sarbidea" - "Gaur-gaurkoz ez da guneekin bateragarria" "Guneko kideak" - "Gelarako helbideren bat beharko duzu gelen direktorioan ikusgai egon dadila nahi baduzu." + "Gaur-gaurkoz ez da guneekin bateragarria" "Gelaren helbidea" "Gela publikoen direktorioan ikusgai" - "Edonork" "Nork irakur dezake historia" "Kideek bakarrik, gonbidatu zituztenetik" "Kideek bakarrik, aukera hau hautatu zenetik" diff --git a/features/roomdetails/impl/src/main/res/values-fa/translations.xml b/features/roomdetails/impl/src/main/res/values-fa/translations.xml index 2de731b612..c04650417c 100644 --- a/features/roomdetails/impl/src/main/res/values-fa/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-fa/translations.xml @@ -14,20 +14,23 @@ "مدیرن و ناظران" "برداشتن افراد و رد درخواست‌های پیوستن" "تغییر چهرک اتاق" - "جزییات اتاق" + "ویرایش اتاق" "تغییر نام اتاق" "دگرگونی موضوع اتاق" "فرستادن پیام‌ها" "ویرایش مدیران" "قادر نخواهید بود این کنش را بازکردانید. داردید کاربر را به سطح قدرت خودتان ارتقا می‌دهید." "افزودن مدیر؟" + "انتقال مالکیت؟" "تنزل بده" "شما نمی‌توانید این تغییر را بازگردانید زیرا در حال تنزل نقش خود در اتاق هستید، اگر آخرین کاربر ممتاز در اتاق باشید، امکان دستیابی مجدد به دسترسی‌های سطح بالای اتاق غیرممکن است." "تنزل نقش شما در اتاق؟" "%1$s (منتظر)" "(منتظر)" "مدیران به صورت خودکار اجازه‌های نظارتی را دارند" + "ماکان به صورت خودکار اجازه‌های مدیریتی را دارند." "ویرایش ناظران" + "گزینش مالکان" "مدیران" "ناظم‌ها" "اعضا" @@ -45,6 +48,8 @@ "هنگام بارگیری تنظیمات اعلان خطایی رخ داد." "بی صدا کردن این اتاق ناموفق بود، لطفا دوباره امتحان کنید." "بی صدا کردن این اتاق ناموفق بود، لطفا دوباره امتحان کنید." + "کاره را تا زمان پایانش نبندید." + "آماده سازی دعوت‌ها…" "دعوت افراد" "ترک گفت‌وگو" "ترک اتاق" @@ -63,6 +68,7 @@ "اطّلاعات اتاق" "موضوع" "به‌روز کردن اتاق…" + "هیچ کاربر محرومی در این اتاق نیست." "%1$d نفر" "%1$d نفر" @@ -71,12 +77,11 @@ "تنها برداشتن عضو" "رفع انسداد" "در صورت دعوت می‌تواند دوباره به اتاق بپیوندد." - "تحریم نکردن کاربر" + "تحریم نکردن از اتاق" "محروم" "اعضا" - "منتظر" - "مدیر" - "ناظمر" + "فقط مدیران" + "مدیرن و ناظران" "مالک" "اعضای اتاق" "رفع تحریم %1$s" @@ -90,17 +95,19 @@ "هنگام بارگیری تنظیمات اعلان خطایی رخ داد." "بازیابی حالت پیش فرض ناموفق بود، لطفا دوباره امتحان کنید." "تنظیم حالت ناموفق است، لطفا دوباره امتحان کنید." + "کارساز خانگیتان از این گزینه در اتاق‌های رمز شده پشتیبانی نمی‌کند. ممکن است در این اتاق آگاه نشوید." "همهٔ پیام‌ها" "فقط اشاره‌ها و کلیدواژگان" "آگاهی من در این اتاق برای" "مدیران" + "مدیران و مالکان" "تغییر نقشم" "تنزّل به عضو" "تنزّل به ناظم" "نظارت اعضا" "پیام‌ها و محتوا" "ناظم‌ها" - "اجازه‌ها" + "مالکان" "بازنشانی اجازه‌ها" "بازنشانی اجازه‌ها؟" "نقش‌ها" @@ -117,8 +124,8 @@ "افراد فقط در صورت دعوت می‌توانند بپیوندند" "فقط دعوتی" "دسترسی اتاق" - "در حال حاضر فضاها پشتیبانی نمی‌شوند" "اعضای فضا" + "در حال حاضر فضاها پشتیبانی نمی‌شوند" "نشانی اتاق" "هرکسی" "انتشار اتاق" diff --git a/features/roomdetails/impl/src/main/res/values-fi/translations.xml b/features/roomdetails/impl/src/main/res/values-fi/translations.xml index e77b0ab2c6..b696660b62 100644 --- a/features/roomdetails/impl/src/main/res/values-fi/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-fi/translations.xml @@ -1,21 +1,21 @@ - "Tarvitset huoneen osoitteen, jotta se näkyy hakemistossa." - "Huoneen osoite" + "Tarvitset osoitteen, jotta se näkyy julkisessa hakemistossa." + "Muokkaa osoitetta" "Ilmoitusasetusten muokkaamisessa tapahtui virhe." "Kotipalvelimesi ei tue tätä vaihtoehtoa salatuissa huoneissa, joten et ehkä saa ilmoitusta joissakin huoneissa." "Kyselyt" - "Vain ylläpitäjät" + "Ylläpitäjä" "Porttikieltojen antaminen" "Viestien poistaminen" - "Kaikki" + "Jäsen" "Ihmisten kutsuminen ja liittymispyyntöjen hyväksyminen" - "Jäsenten valvonta" + "Jäsenien hallinta" "Viestit ja sisältö" - "Ylläpitäjät ja valvojat" + "Valvoja" "Henkilöiden poistaminen ja liittymispyyntöjen hylkääminen" "Huoneen avatarin vaihtaminen" - "Huoneen tiedot" + "Muokkaa tietoja" "Huoneen nimen vaihtaminen" "Huoneen aiheen vaihtaminen" "Viestien lähettäminen" @@ -42,7 +42,7 @@ "Salattu" "Ei salattu" "Julkinen huone" - "Muokkaa huonetta" + "Muokkaa tietoja" "Tuntematon virhe tapahtui, eikä tietoja voitu muuttaa." "Huoneen muokkaaminen ei onnistunut" "Viestisi suojataan lukoilla. Vain sinulla ja viesiten vastaanottajilla on uniikit avaimet niiden avaamiseen." @@ -52,7 +52,7 @@ "Tämän huoneen mykistyksen poistaminen epäonnistui, yritä uudelleen." "Älä sulje sovellusta ennen kuin se on valmis." "Valmistellaan kutsuja…" - "Kutsu ihmisiä" + "Kutsu henkilöitä" "Poistu keskustelusta" "Poistu huoneesta" "Media ja tiedostot" @@ -70,7 +70,7 @@ "Huoneen tiedot" "Aihe" "Muokataan huonetta…" - "Tässä huoneessa ei ole porttikieltoja" + "Porttikiellettyjä käyttäjiä ei ole." "%1$d henkilö" "%1$d henkilöä" @@ -79,10 +79,9 @@ "Poista vain jäsen huoneesta" "Poista porttikielto" "He voivat liittyä tähän huoneeseen uudelleen, jos heidät kutsutaan." - "Poista käyttäjän porttikielto" + "Poista porttikielto huoneesta" "Porttikiellot" "Jäsenet" - "Kutsuttu" "Ylläpitäjä" "Valvoja" "Omistaja" @@ -118,8 +117,8 @@ "Roolit" "Huoneen tiedot" "Roolit ja oikeudet" - "Lisää huoneen osoite" - "Kuka tahansa voi pyytää saada liittyä huoneeseen, mutta ylläpitäjän tai valvojan on hyväksyttävä pyyntö." + "Lisää osoite" + "Kaikkien on pyydettävä pääsyä." "Pyydä liittymistä" "Kyllä, ota salaus käyttöön" "Kun salaus on kerran otettu käyttöön, sitä ei voi poistaa käytöstä. Viestihistoria näkyy vain huoneen jäsenille kutsusta tai liittymisestä lähtien. @@ -129,17 +128,18 @@ Emme suosittele salauksen ottamista käyttöön huoneissa, jotka kuka tahansa vo "Kun salaus on kerran otettu käyttöön, sitä ei voi poistaa käytöstä." "Salaus" "Ota päästä päähän -salaus käyttöön" - "Kuka tahansa voi löytää ja liittyä." + "Kuka tahansa voi liittyä." "Kuka tahansa" - "Vain kutsutut henkilöt voivat liittyä" + "Vain kutsutut henkilöt voivat liittyä." "Vain kutsutut" - "Huoneeseen pääsy" - "Tiloja ei tällä hetkellä tueta" + "Pääsy" "Tilan jäsenet" - "Tarvitset huoneen osoitteen, jotta se näkyy huonehakemistossa." - "Huoneen osoite" + "Tiloja ei tällä hetkellä tueta" + "Tarvitset osoitteen, jotta se näkyy julkisessa hakemistossa." + "Osoite" "Salli tämän huoneen löytäminen hakemalla %1$s -palvelimen julkisesta huonehakemistosta." - "Näkyy julkisessa huonehakemistossa" + "Anna muiden löytää tämä julkisen hakemiston kautta." + "Näkyy julkisessa hakemistossa" "Kuka tahansa" "Kuka voi lukea viestihistoriaa" "Jäsenet vasta kutsusta lähtien" @@ -147,8 +147,6 @@ Emme suosittele salauksen ottamista käyttöön huoneissa, jotka kuka tahansa vo "Huoneosoitteet ovat tapoja löytää ja käyttää huoneita. Näin voit myös helposti jakaa huoneesi muiden kanssa. Voit halutessasi julkaista huoneesi kotipalvelimesi julkisessa huonehakemistossa." "Huoneen julkaiseminen" - "Huoneosoitteet ovat tapoja löytää ja käyttää huoneita. Näin voit myös helposti jakaa huoneesi muiden kanssa. -Osoitetta tarvitaan myös, jotta huone näkyy %1$s -palvelimen julkisessa huonehakemistossa." - "Huoneen näkyvyys" + "Näkyvyys" "Turvallisuus ja yksityisyys" diff --git a/features/roomdetails/impl/src/main/res/values-fr/translations.xml b/features/roomdetails/impl/src/main/res/values-fr/translations.xml index c30bb2c8c9..75c8592d0e 100644 --- a/features/roomdetails/impl/src/main/res/values-fr/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-fr/translations.xml @@ -1,21 +1,21 @@ - "Vous aurez besoin d’une adresse de salon pour le rendre visible dans le répertoire." - "Adresse du salon" + "Vous aurez besoin d’une adresse pour le rendre visible dans l’annuaire public." + "Modifier l’adresse" "Une erreur s’est produite lors de la mise à jour du paramètre de notification." "Votre serveur d’accueil ne supporte pas cette option pour les salons chiffrés, vous pourriez ne pas être notifié(e) dans certains salons." "Sondages" - "Administrateurs seulement" + "Administrateurs" "Bannir des participants" "Supprimer des messages" - "Tout le monde" - "Inviter des personnes et accepter les demandes pour rejoindre" - "Administration des membres" + "Membre" + "Inviter des personnes" + "Gérer les membres" "Messages et contenus" - "Administrateurs et modérateurs" - "Retirer des personnes et refuser les demandes pour rejoindre" + "Modérateurs" + "Retirer des personnes" "Changer l’avatar du salon" - "Détails du salon" + "Modifier les détails" "Changer le nom du salon" "Changer le sujet du salon" "Envoyer des messages" @@ -42,7 +42,7 @@ "Chiffré" "Non chiffré" "Salon public" - "Modifier le salon" + "Modifier les détails" "Une erreur inconnue s’est produite et les informations n’ont pas pu être modifiées." "Impossible de mettre à jour le salon" "Les messages sont sécurisés par des clés de chiffrement. Seuls vous et les destinataires possédez les clés uniques pour les déverrouiller." @@ -62,7 +62,7 @@ "Messages épinglés" "Profil" "Demandes en attente" - "Rôles et autorisations" + "Rôles & autorisations" "Nom du salon" "Sécurité & confidentialité" "Sécurité" @@ -70,21 +70,31 @@ "Informations du salon" "Sujet" "Mise à jour du salon…" - "Il n’y a pas d’utilisateur banni dans ce salon." + "Il n’y a pas d’utilisateur banni." + + "%1$d Banni(e)" + "%1$d Banni(e)s" + + "Vérifiez la saisie ou effectuez une nouvelle recherche" + "Aucun résultat pour «%1$s»" - "%1$d personne" - "%1$d personnes" + "%1$d Personne" + "%1$d Personnes" "Bannir du salon" "Retirer le membre uniquement" "Débannir" "Il pourra rejoindre le salon à nouveau si il est invité." - "Débannir l’utilisateur" + "Débannir du salon" "Bannis" "Membres" - "En attente" - "Administrateur" - "Modérateur" + + "%1$d Invité(e)" + "%1$d Invité(e)s" + + "En attente" + "Administrateurs" + "Modérateurs" "Propriétaire" "Membres du salon" "Débannissement de %1$s" @@ -117,9 +127,9 @@ "Réinitialisation des autorisations ?" "Rôles" "Détails du salon" - "Rôles et autorisations" - "Ajouter l’adresse du salon" - "N’importe qui peut demander à rejoindre le salon, mais un administrateur ou un modérateur devra accepter la demande." + "Rôles & autorisations" + "Ajouter une adresse" + "Tout le monde doit demander un accès." "Demander à rejoindre" "Oui, activer le chiffrement" "Une fois activé, le chiffrement d’un salon ne peut pas être désactivé. L’historique des messages ne sera visible que pour les membres depuis qu’ils ont été invités ou depuis qu’ils ont rejoint le salon. @@ -129,17 +139,22 @@ Nous ne recommandons pas d’activer le chiffrement pour les salons que tout le "Une fois activé, le chiffrement ne peut pas être désactivé." "Chiffrement" "Activer le chiffrement de bout en bout" - "Tout le monde peut le trouver et le rejoindre" + "Tout le monde peut rejoindre." "Tout le monde" - "Le salon ne peut être joint que par les personnes invitées" + "Choisissez les espaces dont les membres peuvent rejoindre ce salon sans invitation. %1$s" + "Gérer les espaces" + "Seules les personnes invitées peuvent rejoindre." "Sur invitation uniquement" - "Accès au salon" - "Les Espaces ne sont pas encore supportés" + "Accès" + "Toute personne se trouvant dans un espace autorisé peut joindre le salon." + "Toute personne de l’espace %1$s peut joindre le salon." "Membres de l’espace" - "Vous aurez besoin de l’adresse du salon pour le rendre visible dans le répertoire des salons." - "Adresse du salon" + "Les Espaces ne sont pas encore supportés" + "Vous aurez besoin d’une adresse pour le rendre visible dans l’annuaire public." + "Adresse" "Autoriser le salon à apparaître dans les résultats de recherche dans le répertoire %1$s des salons publics" - "Visible dans le répertoire des salons publics" + "Permet d’être trouvé en recherchant dans l’annuaire public." + "Visible dans l’annuaire public" "Tout le monde" "Qui peux lire l’historique" "Les membres uniquement depuis qu’ils ont été invités" @@ -147,8 +162,7 @@ Nous ne recommandons pas d’activer le chiffrement pour les salons que tout le "Les adresses de salon sont un moyen de trouver et d’accéder aux salons. Cela vous permet également de partager facilement votre salon avec d’autres personnes. Vous pouvez choisir de publier votre salon dans l’annuaire des salons publics de votre serveur d’accueil." "Publication du salon" - "Les adresses de salon sont un moyen de trouver et d’accéder aux salons. Cela vous permet également de partager facilement votre salon avec d’autres personnes. -L’adresse est également requise pour que le salon soit visible dans le répertoire %1$s des salons publics." - "Visibilité du salon" + "Les adresses permettent de trouver et d’accéder aux salons et aux espaces. Elles facilitent également leur partage avec d’autres personnes." + "Visibilité" "Sécurité & confidentialité" diff --git a/features/roomdetails/impl/src/main/res/values-hr/translations.xml b/features/roomdetails/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..0edf4dadbb --- /dev/null +++ b/features/roomdetails/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,174 @@ + + + "Trebat će vam adresa kako bi bila vidljiva u javnom direktoriju." + "Uredi adresu" + "Došlo je do pogreške prilikom ažuriranja postavke obavijesti." + "Vaš matični poslužitelj ne podržava ovu mogućnost u šifriranim sobama; možda nećete dobiti obavijesti u nekim sobama." + "Ankete" + "Administrator" + "Zabrana pristupa osobama" + "Uklanjanje poruka" + "Član" + "Pozivanje osoba" + "Upravljanje članovima" + "Poruke i sadržaj" + "Moderator" + "Uklanjanje osoba" + "Promjena avatara" + "Uredi pojedinosti" + "Promjena imena" + "Promjena teme" + "Slanje poruka" + "Uredi administratore" + "Nećete moći poništiti ovu radnju. Postavit ćete da korisnik ima isti položaj kao i vi." + "Dodati administratora?" + "Nećete moći poništiti ovu radnju. Prenosite vlasništvo na odabrane korisnike. Nakon što odete, to će biti trajno." + "Želite li prenijeti vlasništvo?" + "Degradiraj" + "Nećete moći poništiti ovu promjenu jer sami sebe degradirate. Ako ste posljednji privilegirani korisnik u sobi, nećete moći ponovno dobiti privilegije." + "Želite li se degradirati?" + "%1$s (na čekanju)" + "(na čekanju)" + "Administratori automatski imaju moderatorske ovlasti" + "Vlasnici automatski imaju administratorske ovlasti." + "Uredi moderatore" + "Odaberi vlasnike" + "Administratori" + "Moderatori" + "Članovi" + "Niste spremili sve promjene." + "Želite li spremiti promjene?" + "Dodaj temu" + "Šifrirano" + "Nije šifrirano" + "Javna soba" + "Uredi pojedinosti" + "Došlo je do nepoznate pogreške i podatci se nisu mogli promijeniti." + "Nije moguće ažurirati sobu" + "Poruke su zaključane. Samo vi i primatelji imate jedinstvene ključeve za njihovo otključavanje." + "Omogućeno je šifriranje poruka" + "Došlo je do pogreške prilikom učitavanja postavki obavijesti." + "Isključivanje zvuka u ovoj sobi nije uspjelo, pokušajte ponovno." + "Uključivanje zvuka u ovoj sobi nije uspjelo, pokušajte ponovno." + "Ne zatvarajte aplikaciju dok se ne završi." + "Priprema pozivnica…" + "Pozovi osobe" + "Napusti razgovor" + "Napusti sobu" + "Mediji i datoteke" + "Prilagođeno" + "Zadano" + "Obavijesti" + "Prikvačene poruke" + "Profil" + "Zahtjevi za pridruživanje" + "Uloge i dopuštenja" + "Naziv sobe" + "Sigurnost i privatnost" + "Sigurnost" + "Podijeli sobu" + "Informacije o sobi" + "Tema" + "Ažuriranje pojedinosti…" + "Nema zabranjenih korisnika." + + "%1$d zabranjen" + "%1$d zabranjena" + "%1$d zabranjenih" + + "Provjerite pravopis ili pokušajte s novim pretraživanjem" + "Nema rezultata za “%1$s”" + + "%1$d osoba" + "%1$d osobe" + "%1$d ljudi" + + "Zabrani korisnika" + "Samo ukloni člana" + "Poništi zabranu" + "Moći će se ponovno pridružiti ovoj sobi ako budu pozvani." + "Poništi zabranu pristupa korisniku" + "Zabranjeni" + "Članovi" + + "%1$d pozvan" + "%1$d pozvana" + "%1$d pozvanih" + + "Na čekanju" + "Administrator" + "Moderator" + "Vlasnik" + "Članovi sobe" + "Uklanja se zabrana korisniku %1$s" + "Dopusti prilagođenu postavku" + "Uključivanjem ovoga poništit ćete zadanu postavku" + "Obavijesti me u ovom razgovoru za" + "Možete to promijeniti u %1$s ." + "globalne postavke" + "Zadana postavka" + "Ukloni prilagođenu postavku" + "Došlo je do pogreške prilikom učitavanja postavki obavijesti." + "Vraćanje zadanog načina rada nije uspjelo, pokušajte ponovno." + "Postavljanje načina rada nije uspjelo, pokušajte ponovno." + "Vaš matični poslužitelj ne podržava ovu mogućnost u šifriranim sobama; nećete dobiti obavijest u ovoj sobi." + "Sve poruke" + "Samo spominjanja i ključne riječi" + "U ovoj sobi obavijesti me za" + "Administratori" + "Administratori i vlasnici" + "Promijeni moju ulogu" + "Degradiraj u člana" + "Degradiraj u moderatora" + "Moderiranje članova" + "Poruke i sadržaj" + "Moderatori" + "Vlasnici" + "Dopuštenja" + "Poništi dopuštenja" + "Nakon što poništite dopuštenja, izgubit ćete trenutačne postavke." + "Želite li poništiti dopuštenja?" + "Uloge" + "Pojedinosti o sobi" + "Uloge i dopuštenja" + "Dodaj adresu" + "Svatko tko se nalazi u ovlaštenim prostorima može se pridružiti, ali svi ostali moraju zatražiti pristup." + "Svi moraju zatražiti pristup." + "Zatraži pridruživanje" + "Svatko u %1$s može se pridružiti, ali svi ostali moraju zatražiti pristup." + "Da, omogući šifriranje" + "Nakon što se šifriranje za sobu omogući, više se neće moći onemogućiti. Povijest poruka bit će vidljiva samo članovima sobe otkad su pozvani ili otkad su joj se pridružili. +Nitko osim članova sobe neće moći čitati poruke. Zbog toga botovi i mostovi možda neće ispravno funkcionirati. +Ne preporučujemo omogućavanje šifriranja za sobe koje svatko može pronaći i pridružiti im se." + "Želite li omogućiti šifriranje?" + "Nakon što se šifriranje omogući, više se neće moći onemogućiti." + "Šifriranje" + "Omogući sveobuhvatno šifriranje" + "Svatko se može pridružiti." + "Svatko" + "Odaberite iz kojih se prostora članovi mogu pridružiti ovoj sobi bez pozivnice. %1$s" + "Upravljaj prostorima" + "Samo pozvane osobe mogu se pridružiti." + "Samo s pozivnicom" + "Pristup" + "Svatko tko se nalazi u ovlaštenim prostorima može se pridružiti." + "Svatko u %1$s može se pridružiti." + "Članovi prostora" + "Prostori trenutačno nisu podržani" + "Trebat će vam adresa kako bi bila vidljiva u javnom direktoriju." + "Adresa" + "Omogući pronalazak ove sobe pretraživanjem %1$s javnog direktorija soba" + "Omogući pronalazak pretraživanjem javnog direktorija." + "Vidljivo u javnom direktoriju" + "Svatko (povijest je javna)" + "Promjene neće utjecati na prethodne poruke, samo na nove. %1$s" + "Tko zna čitati povijest" + "Samo za članove nakon što su pozvani" + "Članovi (cjelokupna povijest)" + "Adrese soba služe za pronalaženje i pristup sobama. Time se također osigurava jednostavno dijeljenje sobe s drugim korisnicima. +Možete odabrati objavljivanje svoje sobe u javnom direktoriju soba na matičnom poslužitelju." + "Objavljivanje sobe" + "Adrese služe za pronalaženje soba i prostora te pristup njima. Tako ih ujedno možete jednostavno dijeliti s drugima." + "Vidljivost" + "Sigurnost i privatnost" + diff --git a/features/roomdetails/impl/src/main/res/values-hu/translations.xml b/features/roomdetails/impl/src/main/res/values-hu/translations.xml index 941389ca42..440ae23d09 100644 --- a/features/roomdetails/impl/src/main/res/values-hu/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-hu/translations.xml @@ -1,21 +1,21 @@ "Szüksége lesz egy szobacímre, hogy láthatóvá tegye a szobakatalógusban." - "A szoba címe" + "Cím szerkesztése" "Hiba történt az értesítési beállítás frissítésekor." "A Matrix-kiszolgálója nem támogatja ezt a beállítást a titkosított szobákban, előfordulhat, hogy egyes szobákban nem kap értesítést." "Szavazások" - "Csak adminisztrátorok" + "Adminisztrátor" "Emberek kitiltása" "Üzenetek eltávolítása" - "Mindenki" - "Személyek meghívása és csatlakozási kérések elfogadása" - "Tagok moderálása" + "Tag" + "Emberek meghívása" + "Tagok kezelése" "Üzenetek és tartalom" - "Adminisztrátorok és moderátorok" - "Emberek eltávolítása és a csatlakozási kérések elutasítása" + "Moderátor" + "Emberek eltávolítása" "Szoba profilképének módosítása" - "Szoba részletei" + "Részletek szerkesztése" "Szoba nevének módosítása" "Szoba témájának módosítása" "Üzenetek küldése" @@ -42,7 +42,7 @@ "Titkosított" "Nem titkosított" "Nyilvános szoba" - "Szoba szerkesztése" + "Részletek szerkesztése" "Ismeretlen hiba történt, és az információkat nem lehetett megváltoztatni." "Nem sikerült frissíteni a szobát" "Az üzeneteket kulcsok védik. Csak Ön és a címzett rendelkezik a feloldásukhoz szükséges egyedi kulcsokkal." @@ -71,6 +71,8 @@ "Téma" "Szoba frissítése…" "Ebben a szobában nincsenek kitiltott felhasználók." + "Ellenőrizze a helyesírást, vagy próbáljon meg egy új keresést" + "Nincs találat a következőre: „%1$s\"" "%1$d személy" "%1$d személy" @@ -79,10 +81,10 @@ "Csak a tag eltávolítása" "Tiltás feloldása" "Ehhez a szobához is csatlakozhat, ha meghívják." - "Felhasználó tiltásának feloldása" + "Visszaengedés a szobába" "Kitiltva" "Tagok" - "Függőben" + "Függőben" "Adminisztrátor" "Moderátor" "Tulajdonos" @@ -118,8 +120,8 @@ "Szerepkörök" "Szoba részletei" "Szerepkörök és jogosultságok" - "Szobacím hozzáadása" - "Bárki kérheti, hogy csatlakozzon a szobához, de egy adminisztrátornak vagy moderátornak el kell fogadnia a kérést." + "Cím hozzáadása" + "Mindenkinek hozzáférést kell kérnie." "Csatlakozás kérése" "Igen, engedélyezze a titkosítást" "Az engedélyezés után a szoba titkosítása nem tiltható le. Az üzenetek előzményei csak a szobatagok számára láthatók, amikor meghívást kaptak, vagy mióta csatlakoztak a szobához. @@ -129,16 +131,17 @@ Nem javasoljuk a titkosítás engedélyezését az olyan szobákban, amelyeket b "Engedélyezés után a titkosítás nem tiltható le." "Titkosítás" "Végpontok közötti titkosítás engedélyezése" - "Bárki megtalálhatja és csatlakozhat" + "Bárki csatlakozhat." "Bárki" - "Az emberek csak akkor csatlakozhatnak, ha meghívást kapnak" - "Csak meghívással" - "Szobahozzáférés" - "A terek jelenleg nem támogatottak" + "Csak a meghívott emberek léphetnek be." + "Csak meghívásos" + "Hozzáférés" "A tér tagjai" - "Szüksége lesz egy szobacímre, hogy láthatóvá váljon a szobakatalógusban." - "A szoba címe" + "A terek jelenleg nem támogatottak" + "Szüksége lesz egy szobacímre, hogy láthatóvá tegye a szobakatalógusban." + "Cím" "A szoba megtalálhatóvá tétele a(z) %1$s nyilvános szobakatalógusában való kereséssel." + "Lehetővé teszi, hogy a nyilvános szobakatalógusban megtalálható legyen." "Látható a nyilvános szobakatalógusban" "Bárki" "Ki olvashatja az előzményeket" @@ -147,8 +150,6 @@ Nem javasoljuk a titkosítás engedélyezését az olyan szobákban, amelyeket b "A szobacímek a szobák megtalálásának és elérésnek módjai. Ez azt is biztosítja, hogy könnyen megoszthatja a szobáját másokkal. Kiválaszthatja, hogy szobáját közzéteszi-e a Matrix-kiszolgáló nyilvános szobakatalógusában." "Szoba közzététele" - "A szobacímek a szobák megtalálásának és elérésnek módjai. Ez azt is biztosítja, hogy könnyen megoszthatja a szobáját másokkal. -A cím szükséges ahhoz is, hogy a nyilvános szoba láthatóvá váljon a(z) %1$s kiszolgáló nyilvános szobakatalógusában." - "Szoba láthatósága" + "Láthatóság" "Biztonság és adatvédelem" diff --git a/features/roomdetails/impl/src/main/res/values-in/translations.xml b/features/roomdetails/impl/src/main/res/values-in/translations.xml index c78485bc4d..34703d6c01 100644 --- a/features/roomdetails/impl/src/main/res/values-in/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-in/translations.xml @@ -8,14 +8,12 @@ "Hanya admin" "Cekal orang-orang" "Hilangkan pesan" - "Semua orang" "Undang orang-orang dan terima permintaan untuk bergabung" - "Moderasi anggota" "Pesan dan konten" "Admin dan moderator" "Keluarkan orang-orang dan tolak permintaan untuk bergabung" "Ubah avatar ruangan" - "Detail ruangan" + "Sunting Ruangan" "Ubah nama ruangan" "Ubah topik ruangan" "Kirim pesan" @@ -72,12 +70,11 @@ "Hanya keluarkan anggota" "Batalkan pencekalan" "Pengguna dapat bergabung ke ruangan ini lagi jika diundang." - "Batalkan pencekalan pengguna" + "Batalkan cekalan dari ruangan" "Tercekal" "Anggota" - "Tertunda" - "Admin" - "Moderator" + "Hanya admin" + "Admin dan moderator" "Anggota ruangan" "Membatalkan cekalan %1$s" "Izinkan pengaturan khusus" @@ -101,7 +98,6 @@ "Moderasi anggota" "Pesan dan konten" "Moderator" - "Perizinan" "Atur ulang perizinan" "Setelah Anda mengatur ulang perizinan, Anda akan kehilangan pengaturan Anda saat ini." "Atur ulang perizinan?" @@ -124,20 +120,15 @@ Kami tidak menyarankan untuk mengaktifkan enkripsi untuk ruangan yang dapat dite "Orang hanya dapat bergabung jika mereka diundang" "Hanya undangan" "Akses ruangan" - "Space saat ini tidak didukung" "Anggota space" - "Anda memerlukan alamat ruangan agar dapat membuatnya terlihat di direktori ruangan." - "Alamat ruangan" + "Space saat ini tidak didukung" + "Anda akan memerlukan alamat ruangan untuk membuatnya terlihat dalam direktori." "Izinkan ruangan ini ditemukan dengan mencari direktori ruangan %1$s publik" "Terlihat di direktori ruangan publik" - "Siapa pun" "Siapa yang bisa membaca riwayat" "Hanya anggota sejak mereka diundang" "Hanya anggota sejak memilih opsi ini" "Alamat ruangan adalah cara untuk menemukan dan mengakses ruangan. Ini juga memastikan Anda dapat dengan mudah berbagi ruangan dengan orang lain. Anda dapat memilih untuk menerbitkan ruangan Anda di direktori ruangan publik homeserver Anda." "Penerbitan ruangan" - "Alamat ruangan adalah cara untuk menemukan dan mengakses ruangan. Hal ini juga memastikan Anda dapat dengan mudah berbagi ruangan dengan orang lain. -Alamat ini juga diperlukan untuk membuat ruangan terlihat di %1$s direktori ruangan publik." - "Keterlihatan ruangan" "Keamanan & privasi" diff --git a/features/roomdetails/impl/src/main/res/values-it/translations.xml b/features/roomdetails/impl/src/main/res/values-it/translations.xml index 3fae2bbc04..3d83dba3cb 100644 --- a/features/roomdetails/impl/src/main/res/values-it/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-it/translations.xml @@ -1,21 +1,21 @@ - "Avrai bisogno di un indirizzo della stanza per renderla visibile nell\'elenco." - "Indirizzo della stanza" + "Per renderlo visibile nell\'elenco pubblico, avrai bisogno di un indirizzo." + "Modifica indirizzo" "Si è verificato un errore durante l\'aggiornamento delle impostazioni di notifica." "Il tuo homeserver non supporta questa opzione nelle stanze crifrate, quindi potresti non ricevere notifiche in alcune stanze." "Sondaggi" - "Solo amministratori" + "Amministratore" "Escludi membri" "Rimuovi messaggi" - "Tutti" - "Invita persone e accetta richieste di partecipazione" - "Moderazione dei membri" + "Membro" + "Invita persone" + "Gestisci membri" "Messaggi e contenuti" - "Amministratori e moderatori" - "Rimuovi le persone e rifiuta le richieste di partecipazione" + "Moderatore" + "Rimuovi membri" "Cambia avatar della stanza" - "Dettagli della stanza" + "Modifica dettagli" "Cambia il nome della stanza" "Cambiare l\'argomento della stanza" "Inviare messaggi" @@ -42,7 +42,7 @@ "Cifrata" "Non cifrata" "Stanza pubblica" - "Modifica stanza" + "Modifica dettagli" "Si è verificato un errore sconosciuto e non è stato possibile modificare le informazioni." "Impossibile aggiornare la stanza" "I messaggi sono protetti da lucchetti. Solo tu e i destinatari avete le chiavi univoche per sbloccarli." @@ -50,6 +50,8 @@ "Si è verificato un errore durante il caricamento delle impostazioni di notifica." "Impostazione del silenzioso fallita per questa stanza, riprova." "Disattivazione del silenzioso di questa stanza fallita, riprova." + "Non chiudere l\'app fino al completamento." + "Preparazione degli inviti…" "Invita persone" "Abbandona la conversazione" "Esci dalla stanza" @@ -68,19 +70,29 @@ "Informazioni sulla stanza" "Argomento" "Aggiornamento della stanza…" - "Non ci sono utenti esclusi in questa stanza." + "Non ci sono utenti bannati." + + "%1$d Bannato" + "%1$d Bannati" + + "Controlla l\'ortografia o prova una nuova ricerca" + "Nessun risultato per “%1$s ”" - "%1$d persona" - "%1$d persone" + "%1$d Persona" + "%1$d Persone" "Rimuovi ed escludi" "Rimuovi soltanto" "Riammetti" "Potrà entrare nuovamente in questa stanza se invitato." - "Riammetti utente" + "Riammetti nella stanza" "Esclusi" "Membri" - "In attesa" + + "%1$d Invitato" + "%1$d Invitati" + + "In attesa" "Amministratore" "Moderatore" "Proprietario" @@ -116,8 +128,8 @@ "Ruoli" "Dettagli della stanza" "Ruoli e autorizzazioni" - "Aggiungi l\'indirizzo della stanza" - "Chiunque può chiedere di entrare nella stanza, ma un amministratore o un moderatore dovrà accettare la richiesta." + "Aggiungi indirizzo" + "Chiunque deve richiedere l\'accesso." "Chiedi di entrare" "Sì, attiva la crittografia" "Una volta attivata, la crittografia di una stanza non può essere disattivata, la cronologia dei messaggi sarà visibile solo ai membri della stanza da quando sono stati invitati o da quando sono entrati nella stanza. @@ -127,17 +139,18 @@ Non consigliamo di attivare la crittografia per le stanze che chiunque può trov "Una volta attivata, la crittografia non può essere disattivata." "Crittografia" "Attiva la crittografia end-to-end" - "Chiunque può trovare ed entrare" + "Chiunque può partecipare." "Chiunque" - "Le persone possono partecipare solo se invitate" + "Solo le persone invitate possono entrare." "Solo su invito" - "Accesso alla stanza" - "Gli spazi non sono attualmente supportati" + "Accesso" "Membri dello spazio" - "Per rendere visibile la stanza nell\'elenco delle stanze, è necessario l\'indirizzo della stanza." - "Indirizzo della stanza" + "Gli spazi non sono attualmente supportati" + "Per renderlo visibile nell\'elenco pubblico, avrai bisogno di un indirizzo." + "Indirizzo" "Consenti la ricerca di questa stanza effettuando una ricerca nell\'elenco delle stanze pubbliche di %1$s" - "Visibile nell\'elenco delle stanze pubbliche" + "Consenti di essere trovato effettuando una ricerca nell\'elenco pubblico." + "Visibile nell\'elenco pubblico" "Chiunque" "Chi può leggere la cronologia messaggi" "Solo membri da quando sono stati invitati" @@ -145,7 +158,7 @@ Non consigliamo di attivare la crittografia per le stanze che chiunque può trov "Gli indirizzi delle stanze sono modi per trovare e accedervi. In questo modo puoi anche condividere facilmente la tua stanze con altri. Puoi scegliere di pubblicare la tua stanza nell\'elenco delle stanza pubbliche dell\'homeserver." "Pubblicazione della stanza" - "Gli indirizzi delle stanze sono modi per trovare e accedere a queste ultime. Questo assicura anche che tu possa condividere facilmente la tua stanza con altri. L\'indirizzo è anche necessario per rendere la stanza visibile in %1$s nell\'elenco delle stanze pubbliche." - "Visibilità della stanza" + "Gli indirizzi sono un modo per trovare e accedere a stanze e spazi. Questo ti consente anche di condividerli facilmente con altri." + "Visibilità" "Sicurezza e privacy" diff --git a/features/roomdetails/impl/src/main/res/values-ka/translations.xml b/features/roomdetails/impl/src/main/res/values-ka/translations.xml index 481082bf63..d9c56aa89c 100644 --- a/features/roomdetails/impl/src/main/res/values-ka/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-ka/translations.xml @@ -6,14 +6,12 @@ "მხოლოდ ადმინისტრატორები" "მომხმარებლების დაბლოკვა" "შეტყობინებების წაშლა" - "ყველა" "მომხმარებლების მოწვევა და გაწევრიანების მოთხოვნების დადასტურება" - "წევრების მოდერირება" "შეტყობინებები და შინაარსი" "ადმინისტრატორები და მოდერატორები" "მომხმარებლების გაგდება და გაწევრიანების მოთხოვნების უარყოფა" "ოთახის სურათის შეცვლა" - "ოთახის დეტალები" + "ოთახის რედაქტირება" "ოთახის სახელის შეცვლა" "ოთახის თემის შეცვლა" "შეტყობინებების გაგზავნა" @@ -63,12 +61,10 @@ "მხოლოდ წევრის წაშლა" "განბლოკვა" "მოწვევის შემთხვევაში განბლოკილი მომხმარებელი ისევ შეძლებს ოთახს შეუერთდეს." - "მომხმარებლის განბლოკვა" "დაბლოკილები" "წევრები" - "მომლოდინე" - "ადმინისტრატორი" - "მოდერატორი" + "მხოლოდ ადმინისტრატორები" + "ადმინისტრატორები და მოდერატორები" "ოთახის წევრები" "%1$s-ს განბლოკვა" "მორგებული პარამეტრის დაშვება" @@ -92,7 +88,6 @@ "წევრების მოდერირება" "შეტყობინებები და შინაარსი" "მოდერატორები" - "ნებართვები" "ნებართვების გადაყენება" "ნებართვების გადაყენების შემთხვევაში მიმდინარე პარამეტრებს დაკარგავთ." "გადავაყენოთ ცვლილებები?" diff --git a/features/roomdetails/impl/src/main/res/values-ko/translations.xml b/features/roomdetails/impl/src/main/res/values-ko/translations.xml index fe864a4be6..196656408c 100644 --- a/features/roomdetails/impl/src/main/res/values-ko/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-ko/translations.xml @@ -8,14 +8,12 @@ "관리자 전용" "사용자 차단" "메시지 삭제" - "모두" "사람들을 초대하고 가입 요청을 수락합니다" - "회원 조정" "메시지 및 콘텐츠" "관리자 및 중재자" "사람들을 제거하고 가입 요청을 거부합니다" "방 아바타 변경" - "방 세부 정보" + "방 편집" "방 이름 변경" "방 화제 변경" "메시지 보내기" @@ -76,12 +74,11 @@ "회원만 삭제할 수 있습니다." "금지 해제" "초대받으면 이 방에 다시 들어올 수 있습니다." - "사용자 차단 해제" + "방에서 차단 해제" "차단됨" "회원들" - "보류 중" - "관리자" - "중재자" + "관리자 전용" + "관리자 및 중재자" "소유자" "방 회원들" "차단 해제 %1$s" @@ -108,7 +105,6 @@ "메시지 및 콘텐츠" "중재자" "소유자" - "권한" "권한 재설정" "권한을 재설정하면 현재 설정이 모두 삭제됩니다." "권한을 재설정하시겠습니까?" @@ -131,21 +127,16 @@ "초대받은 사용자만 가입할 수 있습니다." "초대 전용" "방 액세스" - "스페이스는 현재 지원되지 않습니다" "스페이스 멤버들" - "방 디렉토리에 표시하려면 방 주소가 필요합니다." - "방 주소" + "스페이스는 현재 지원되지 않습니다" + "디렉토리에 표시하려면 방 주소가 필요합니다." "%1$s 공개 방 디렉토리에서 이 방을 검색할 수 있도록 허용합니다" "공개 룸 디렉토리에 표시됨" - "누구나" "누가 기록을 읽을 수 있는가" "초대받은 회원만 이용 가능합니다" "이 옵션을 선택한 회원만 이용 가능합니다." "방 주소는 방을 찾고 액세스하는 방법입니다. 이를 통해 다른 사람들과 방을 쉽게 공유할 수 있습니다. 홈서버의 공개 방 디렉토리에 방을 공개할지 여부를 선택할 수 있습니다." "방 게시" - "방 주소는 방을 찾고 액세스하는 방법입니다. 또한 이 주소를 사용하면 다른 사람들과 방을 쉽게 공유할 수 있습니다. -%1$s 의 공개 방 디렉토리에서 방을 표시하려면 이 주소도 필요합니다." - "방 표시 여부" "보안 및 개인정보 보호" diff --git a/features/roomdetails/impl/src/main/res/values-lt/translations.xml b/features/roomdetails/impl/src/main/res/values-lt/translations.xml index 1b3b44fdf2..727875d696 100644 --- a/features/roomdetails/impl/src/main/res/values-lt/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-lt/translations.xml @@ -1,5 +1,6 @@ + "Redaguoti kambarį" "Pridėti temą" "Redaguoti kambarį" "Įvyko nežinoma klaida ir informacijos pakeisti nepavyko." @@ -19,6 +20,5 @@ "%1$d asmenys" "%1$d asmenų" - "Laukiama" "Kambario nariai" diff --git a/features/roomdetails/impl/src/main/res/values-nb/translations.xml b/features/roomdetails/impl/src/main/res/values-nb/translations.xml index 16042fdf57..45499ee3c9 100644 --- a/features/roomdetails/impl/src/main/res/values-nb/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-nb/translations.xml @@ -1,21 +1,21 @@ - "Du trenger en adresse til rommet for å gjøre det synlig i katalogen." - "Romadresse" + "Du trenger en adresse for å gjøre den synlig i den offentlige katalogen." + "Rediger adresse" "Det oppstod en feil under oppdatering av varslingsinnstillingen." "Hjemmeserveren din støtter ikke dette alternativet i krypterte rom, og det kan hende at du ikke blir varslet i enkelte rom." "Avstemninger" - "Kun for administratorer" + "Admin" "Forby folk" "Fjern meldinger" - "Alle" - "Inviter folk og godta forespørsler om å bli med" - "Moderering av medlemmer" + "Medlem" + "Inviter folk" + "Administrer medlemmer" "Meldinger og innhold" - "Administratorer og moderatorer" - "Fjern folk og avslå forespørsler om å bli med" + "Moderator" + "Fjern folk" "Endre romavatar" - "Romdetaljer" + "Rediger detaljer" "Endre romnavn" "Endre temaet til rommet" "Send meldinger" @@ -42,7 +42,7 @@ "Kryptert" "Ikke kryptert" "Offentlig rom" - "Rediger rom" + "Rediger detaljer" "Det oppstod en ukjent feil, og informasjonen kunne ikke endres." "Kan ikke oppdatere rommet" "Meldingene er krypterte. Det er bare du og mottakerne som har de unike nøklene til å låse dem opp." @@ -70,7 +70,7 @@ "Informasjon om rommet" "Emne" "Oppdaterer rommet …" - "Det er ingen utestengte brukere i dette rommet." + "Det er ingen utestengte brukere." "%1$d person" "%1$d personer" @@ -79,11 +79,10 @@ "Bare fjern medlem" "Opphev utestengelse" "De vil kunne bli med i dette rommet igjen hvis de blir invitert." - "Opphev utestengelse av bruker" + "Fjern utestengelsen fra rommet" "Utestengt" "Medlemmer" - "Venter" - "Administrator" + "Admin" "Moderator" "Eier" "Medlemmer av rommet" @@ -111,15 +110,14 @@ "Meldinger og innhold" "Moderatorer" "Eiere" - "Tillatelser" "Tilbakestill tillatelser" "Når du har tilbakestilt tillatelsene, mister du gjeldende innstillinger." "Vil du tilbakestille tillatelsene?" "Roller" "Romdetaljer" "Roller og tillatelser" - "Legg til romadresse" - "Alle kan be om å bli med i rommet, men en administrator eller moderator må godta forespørselen." + "Legg til adresse" + "Alle må be om tilgang." "Be om å bli med" "Ja, aktiver kryptering" "Når kryptering for et rom er aktivert, kan den ikke deaktiveres. Meldingshistorikken vil bare være synlig for rommedlemmer siden de ble invitert eller siden de ble med i rommet. @@ -129,26 +127,24 @@ Vi anbefaler ikke å aktivere kryptering for rom som hvem som helst kan finne og "Når kryptering er aktivert, kan det ikke deaktiveres." "Kryptering" "Aktiver ende-til-ende-kryptering" - "Alle kan finne og bli med" + "Alle kan bli med." "Alle" - "Folk kan bare bli med hvis de er invitert" + "Bare inviterte personer kan bli med." "Kun for inviterte" - "Tilgang til rom" - "Områder støttes ikke for øyeblikket" + "Tilgang" "Medlemmer av område" - "Du trenger en adresse til rommet for å gjøre rommet synlig i romkatalogen." - "Romadresse" + "Områder støttes ikke for øyeblikket" + "Du trenger en adresse for å gjøre den synlig i den offentlige katalogen." + "Adresse" "Tillat at dette rommet blir funnet ved å søke %1$s offentlig romkatalog" - "Synlig i offentlig romkatalog" - "Alle" + "Synlig i offentlig katalog" + "Alle (historikken er offentlig)" "Hvem kan lese historikk" - "Medlemmer bare siden de ble invitert" - "Kun medlemmer siden du valgte dette alternativet" + "Medlemmer siden de ble invitert" + "Medlemmer (full historikk)" "Romadresser er måter å finne og få tilgang til rom på. Dette sikrer også at du enkelt kan dele rommet ditt med andre. Du kan velge å publisere rommet ditt i hjemeserverens offentlige romkatalog." "Publisering av rom" - "Romadresser er en måte å finne og få tilgang til rommene på. Dette sikrer også at du enkelt kan dele rommet ditt med andre. -Adressen er også nødvendig for å gjøre rommet synlig i den offentlige romkatalogen på %1$s." - "Romsynlighet" + "Synlighet" "Sikkerhet og personvern" diff --git a/features/roomdetails/impl/src/main/res/values-nl/translations.xml b/features/roomdetails/impl/src/main/res/values-nl/translations.xml index baec0b0bb9..f780437cd5 100644 --- a/features/roomdetails/impl/src/main/res/values-nl/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-nl/translations.xml @@ -6,14 +6,12 @@ "Alleen beheerders" "Personen verbannen" "Berichten verwijderen" - "Iedereen" "Nodig personen uit en accepteer verzoeken om deel te nemen" - "Moderatie van leden" "Berichten en inhoud" "Beheerders en moderators" "Verwijder personen en weiger verzoeken om deel te nemen" "Kamerafbeelding wijzigen" - "Kamergegevens" + "Kamer bewerken" "Kamernaam wijzigen" "Kameronderwerp wijzigen" "Berichten verzenden" @@ -68,12 +66,10 @@ "Alleen lid verwijderen" "Ontbannen" "Ze kunnen opnieuw tot de kamer toetreden als ze worden uitgenodigd." - "Ontban gebruiker" "Verbannen" "Leden" - "In behandeling" - "Beheerder" - "Moderator" + "Alleen beheerders" + "Beheerders en moderators" "Kamerleden" "%1$s ontbannen" "Aanpassen toestaan" @@ -97,7 +93,6 @@ "Moderatie van leden" "Berichten en inhoud" "Moderators" - "Rechten" "Rechten opnieuw instellen" "Als je de rechten opnieuw instelt, raak je de huidige instellingen kwijt." "Rechten opnieuw instellen?" @@ -106,5 +101,4 @@ "Rollen en rechten" "Vraag om toe te treden" "Iedereen" - "Iedereen" diff --git a/features/roomdetails/impl/src/main/res/values-pl/translations.xml b/features/roomdetails/impl/src/main/res/values-pl/translations.xml index b9847c6cfd..620bd0d674 100644 --- a/features/roomdetails/impl/src/main/res/values-pl/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-pl/translations.xml @@ -7,15 +7,13 @@ "Ankiety" "Tylko administratorzy" "Banowanie osób" - "Usuwanie wiadomości" - "Wszyscy" + "Usuń wiadomości" "Zapraszanie osób i akceptowanie próśb o dołączenie" - "Moderacja członków" "Wiadomości i zawartość" "Administratorzy i moderatorzy" "Usuwanie osób i odrzucanie próśb o dołączenie" "Zmień awatar pokoju" - "Szczegóły pokoju" + "Edytuj pokój" "Zmień nazwę pokoju" "Zmień temat pokoju" "Wysyłanie wiadomości" @@ -50,6 +48,8 @@ "Wystąpił błąd podczas ładowania ustawień powiadomień." "Wyciszenie tego pokoju nie powiodło się, spróbuj ponownie." "Nie udało się wyłączyć wyciszenia tego pokoju. Spróbuj ponownie." + "Nie zamykaj aplikacji przed zakończeniem." + "Przygotowywanie zaproszeń…" "Zaproś znajomych" "Opuść rozmowę" "Opuść pokój" @@ -78,12 +78,11 @@ "Tylko usuń członka" "Odbanuj" "Będą mogli ponownie dołączyć do tego pokoju, jeśli zostaną zaproszeni." - "Odbanuj użytkownika" + "Odbanuj z pokoju" "Zbanowanych" "Członków" - "Oczekujące" - "Administrator" - "Moderator" + "Tylko administratorzy" + "Administratorzy i moderatorzy" "Właściciel" "Członkowie pokoju" "Odbanowanie %1$s" @@ -110,7 +109,6 @@ "Wiadomości i zawartość" "Moderatorzy" "Właściciele" - "Uprawnienia" "Resetuj uprawnienia" "Po zresetowaniu uprawnień utracisz bieżące ustawienia." "Zresetować uprawnienia?" @@ -133,21 +131,19 @@ Odradzamy włączanie szyfrowania dla pokoi, które każdy może znaleźć i do "Tylko osoby z zaproszeniem mogą dołączyć" "Tylko zaproszenie" "Dostęp do pokoju" - "Przestrzenie nie są obecnie wspierane" "Członkowie przestrzeni" - "Aby pokój był widoczny w katalogu pokoi, potrzebny jest adres pokoju." + "Przestrzenie nie są obecnie wspierane" + "Aby pokój był widoczny w katalogu, potrzebny jest adres pokoju." "Adres pokoju" "Zezwól na znalezienie tego pokoju wyszukując %1$s w katalogu pokoi publicznych" "Widoczny w katalogu pokoi publicznych" - "Wszyscy" + "Ktokolwiek" "Kto może czytać historię" "Od momentu kiedy członkowie zostali zaproszeni" "Członkowie od momentu włączenia tej opcji" "Adresy pokoju umożliwiają łatwe znalezienie i dołączenie do pokojów. Również możesz się zdecydować na upublicznienie Twojego serwera w katalogu pokoi publicznych." "Publikowanie pokoju" - "Adresy pokoju umożliwiają łatwe znalezienie i dołączenie do pokojów. -Dodatkowo adres pokoju jest wymagany, aby pomieszczenie było widoczne w katalogu pokoi publicznych %1$s." - "Widoczność pomieszczenia" + "Widoczność pokoju" "Bezpieczeństwo i prywatność" diff --git a/features/roomdetails/impl/src/main/res/values-pt-rBR/translations.xml b/features/roomdetails/impl/src/main/res/values-pt-rBR/translations.xml index 9df66d9205..0b65ee0f63 100644 --- a/features/roomdetails/impl/src/main/res/values-pt-rBR/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-pt-rBR/translations.xml @@ -1,21 +1,21 @@ - "Você precisará de um endereço de sala para torná-la visível no diretório." - "Endereço da sala" + "Você precisará de um endereço para torná-la visível no diretório." + "Editar endereço" "Ocorreu um erro ao atualizar a configuração de notificação." "Seu servidor-casa não suporta esta opção em salas criptografadas. Você pode não ser notificado em algumas salas." "Enquetes" - "Somente administradores" + "Administradores" "Banir pessoas" "Remover mensagens" - "Todos" - "Convide pessoas e aceite solicitações de entrada" - "Moderação de membros" + "Membro" + "Convidar pessoas" + "Gerenciar membros" "Mensagens e conteúdo" - "Administradores e moderadores" - "Remover pessoas e recusar solicitações de entrada" + "Moderador" + "Remover pessoas" "Alterar avatar da sala" - "Detalhes da sala" + "Editar detalhes" "Alterar nome da sala" "Alterar tópico da sala" "Enviar mensagens" @@ -42,7 +42,7 @@ "Criptografado" "Não criptografado" "Sala pública" - "Editar sala" + "Editar detalhes" "Ocorreu um erro desconhecido e as informações não puderam ser alteradas." "Não foi possível atualizar a sala" "As mensagens são protegidas com cadeados. Somente você e os destinatários têm as chaves exclusivas para desbloqueá-los." @@ -50,6 +50,8 @@ "Ocorreu um erro ao carregar as configurações de notificação." "Falha ao silenciar esta sala, tente novamente." "Falha ao desilenciar esta sala. Tente novamente." + "Não feche o aplicativo até terminar." + "Preparando convites…" "Convidar pessoas" "Sair da conversa" "Sair da sala" @@ -68,7 +70,13 @@ "Informação da sala" "Tópico" "Atualizando a sala…" - "Não há usuários banidos nesta sala." + "Não há usuários banidos." + + "%1$d banido" + "%1$d banidos" + + "Confira a ortografia ou tente uma nova busca" + "Nenhum resultado para “%1$s”" "%1$d pessoa" "%1$d pessoas" @@ -77,11 +85,15 @@ "Somente remover o membro" "Desbanir" "Esta pessoa poderá entrar nesta sala novamente se for convidada." - "Desbanir usuário" + "Desbanir da sala" "Banidos" "Membros" - "Pendente" - "Administrador" + + "%1$d convidado" + "%1$d convidados" + + "Pendente" + "Administradores" "Moderador" "Proprietário" "Membros da sala" @@ -116,9 +128,11 @@ "Cargos" "Detalhes da sala" "Cargos e permissões" - "Adicionar endereço da sala" - "Qualquer pessoa pode pedir para entrar na sala, mas um administrador ou moderador terá que aceitar o pedido." + "Adicionar endereço" + "Qualquer um nos espaços autorizados podem entrar, mas todos os outros devem pedir acesso." + "Qualquer um pode pedir acesso, mas um administrador terá que aceitar o pedido." "Pedir para entrar" + "Qualquer um em %1$s pode entrar, mas todos os outros devem pedir acesso." "Sim, ativar a criptografia" "Uma vez ativada, a criptografia de uma sala não pode ser desativada. O histórico de mensagens só será visível para os membros da sala desde que foram convidados ou desde que entraram na sala. Ninguém além dos membros da sala poderá ler as mensagens. Isso pode impedir que os bots e as pontes funcionem corretamente. @@ -127,26 +141,29 @@ Não recomendamos que você ative a criptografia para salas que qualquer pessoa "Uma vez ativada, a criptografia não poderá ser desativada." "Criptografia" "Ativar a criptografia de ponta a ponta" - "Qualquer um pode encontrar e entrar" + "Qualquer um pode entrar" "Qualquer pessoa" - "As pessoas só podem participar se forem convidadas" - "Somente para convidados" - "Acesso à sala" - "No momento, não há suporte aos espaços" + "Escolha os espaços dos quais os membros podem entrar nesta sala sem um convite. %1$s" + "Gerenciar espaços" + "Apenas pessoas convidadas podem entrar." + "Privado" + "Acesso" + "Qualquer um em espaços autorizados podem entrar." + "Qualquer um em %1$s pode entrar." "Membros do espaço" - "Você precisará de um endereço de sala para torná-la visível no diretório de salas." - "Endereço da sala" + "No momento, não há suporte aos espaços" + "Você precisará de um endereço para torná-la visível no diretório." + "Endereço publicado" "Permitir que esta sala seja encontrada pesquisando diretório de salas públicas de %1$s" - "Visível no diretório de salas públicas" - "Qualquer pessoa" + "Permite que seja encontrada ao buscar no diretório público." + "Visível no diretório público" "Quem pode ler o histórico" - "Somente membros, desde que foram convidados" - "Somente para membros após selecionar esta opção" + "Membros desde o convite" + "Membros (histórico completo)" "Os endereços das salas são formas de encontrar e acessar as salas. Isso também garante que você possa compartilhar facilmente sua sala com outras pessoas. Você pode optar por publicar sua sala no diretório público de salas do seu servidor-casa." "Publicação da sala" - "Os endereços das salas são formas de encontrar e acessar as salas. Isso também garante que você possa compartilhar facilmente sua sala com outras pessoas. -O endereço também é necessário para que você possa ver a sala no diretório público de salas do %1$s." - "Visibilidade da sala" + "Os endereços das salas são formas de encontrar e acessar as salas. Isso também garante que você possa compartilhá-las facilmente com outras pessoas." + "Visibilidade" "Segurança e privacidade" diff --git a/features/roomdetails/impl/src/main/res/values-pt/translations.xml b/features/roomdetails/impl/src/main/res/values-pt/translations.xml index 6338f63b0c..b682c0597d 100644 --- a/features/roomdetails/impl/src/main/res/values-pt/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-pt/translations.xml @@ -8,14 +8,12 @@ "Apenas administradores" "Banir pessoas" "Remover mensagens" - "Toda a gente" "Convidar pessoas e aceitar pedidos de entrada" - "Moderação de participantes" "Mensagens e conteúdo" "Administradores e moderadores" "Remover pessoas e rejeitar pedidos de entrada" "Alterar o ícone da sala" - "Detalhes da sala" + "Editar sala" "Altera o nome da sala" "Alterar a descrição da sala" "Enviar mensagens" @@ -79,12 +77,11 @@ "Remover apenas" "Anular banimento" "Poderão juntar-se novamente a esta sala se forem convidados." - "Anular banimento do utilizador" + "Desbanir da sala" "Banidos" "Participantes" - "Pendente" - "Administrador" - "Moderador" + "Apenas administradores" + "Administradores e moderadores" "Dono / Dona" "Participantes" "A anular banimento de %1$s" @@ -111,7 +108,6 @@ "Mensagens e conteúdo" "Moderadores" "Donos" - "Permissões" "Repor permissões" "Ao repores as permissões, perderás as configurações atuais." "Repor as permissões?" @@ -134,8 +130,8 @@ Não recomendamos ativar a cifragem em salas que qualquer pessoa possa encontrar "Só é possível entrar tendo um convite" "Apenas por convite" "Acesso à sala" - "Os espaços ainda não estão implementados" "Membros do espaço" + "Os espaços ainda não estão implementados" "É necessário um endereço para tornar a sala visível no diretório." "Endereço da sala" "Permite que esta sala seja encontrada através do diretório público do %1$s." @@ -147,8 +143,6 @@ Não recomendamos ativar a cifragem em salas que qualquer pessoa possa encontrar "Estes endereços permitem encontrar e aceder a sala, bem como a sua fácil partilha com outros. Podes escolher publicar a sala no diretório público do teu servidor." "Publicar sala" - "Estes endereços permitem encontrar e aceder a sala, bem como a sua fácil partilha com outros. -Além disso, é necessário ter endereço para publicar a sala no diretório público do %1$s." "Visibilidade da sala" "Segurança e privacidade" diff --git a/features/roomdetails/impl/src/main/res/values-ro/translations.xml b/features/roomdetails/impl/src/main/res/values-ro/translations.xml index 7379a45550..854d3643ce 100644 --- a/features/roomdetails/impl/src/main/res/values-ro/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-ro/translations.xml @@ -1,21 +1,21 @@ - "Veți avea nevoie de o adresă de cameră pentru a o face vizibilă în director." - "Adresa camerei" + "Veți avea nevoie de o adresă pentru a o face vizibilă în directorul public." + "Editați adresa" "A apărut o eroare în timpul actualizării setărilor pentru notificari." "Serverul dumneavoastră nu acceptă această opțiune în camerele criptate, este posibil să nu primiți notificări în unele camere." "Sondaje" - "Doar administratori" + "Administrator" "Interziceți persoane" "Ștergeți mesajele" - "Toți" - "Invitați persoane și acceptați cereri de alaturare" - "Moderarea membrilor" + "Membru" + "Invitați persoane" + "Gestionați membrii" "Mesaje și conținut" - "Administratori și moderatori" - "Îndepărtați persoane și refuzați cereri de alăturare" + "Moderator" + "Îndepărtați persoane" "Schimbați avatarul camerei" - "Detaliile camerei" + "Editați detaliile" "Schimbă numele camerei" "Schimbați subiectul camerei" "Trimiteți mesaje" @@ -42,7 +42,7 @@ "Criptat" "Necriptat" "Cameră publică" - "Editați camera" + "Editați detaliile" "A apărut o eroare la actualizarea detaliilor camerei" "Nu s-a putut actualiza camera" "Mesajele sunt securizate cu încuietori. Doar dumneavoastră și destinatarii aveți cheile unice pentru a le debloca." @@ -70,19 +70,32 @@ "Informatii camera" "Subiect" "Se actualizează camera…" - "Nu există utilizatori interziși în această cameră." + "Nu există utilizatori interziși." + + "%1$d Interzis" + "%1$d Interziși" + "%1$d Interziși" + + "Verificați ortografia sau încercați o căutare nouă" + "Niciun rezultat pentru “%1$s”" - "o persoană" + "%1$d persoană" + "%1$d persoane" "%1$d persoane" "Îndepărtați și interziceți membrul" "Doar înlăturare" "Anulare excludere" "Se vor putea alătura din nou acestei săli dacă sunt invitați." - "Anulați interzicerea utilizatorului" + "Revocati excluderea din camera" "Excluși" "Membri" - "În așteptare" + + "%1$d Invitat" + "%1$d Invitați" + "%1$d Invitați" + + "În așteptare" "Administrator" "Moderator" "Proprietar" @@ -118,9 +131,11 @@ "Roluri" "Detaliile camerei" "Roluri și permisiuni" - "Adăugați adresa camerei" - "Oricine poate cere să se alăture camerei, dar un administrator sau moderator va trebui să accepte cererea." + "Adăugați o adresă" + "Oricine se află în spațiile autorizate se poate alătura, dar toți ceilalți trebuie să solicite accesul." + "Toată lumea trebuie să solicite acces." "Cereți să vă alăturați" + "Oricine în %1$s se poate alătura, dar toți ceilalți trebuie să solicite acces." "Da, activați criptarea" "Odată activată, criptarea pentru o cameră nu poate fi dezactivată. Mesajele anterioare vor fi vizibile numai pentru membrii camerei de la momentul la care au fost invitați sau de la momentul la care s-au alăturat camerei. Nimeni în afară de membrii camerei nu va putea citi messaje. Acest lucru poate împiedica funcționarea corectă a boților și a punților. @@ -129,26 +144,29 @@ Nu recomandăm activarea criptării pentru camerele pe care oricine le poate gă "Odată activată, criptarea nu poate fi dezactivată." "Criptare" "Activați criptarea end-to-end" - "Oricine poate găsi și alătura camerei" + "Oricine se poate alătura." "Oricine" - "Persoanele se pot alătura numai dacă invitate" + "Alegeți membrii căror spații se pot alătura acestei camere fără invitație. %1$s" + "Gestionați spațiile" + "Doar persoanele invitate se pot alătura." "Doar pe bază de invitație" - "Acces la cameră" - "Spațiile nu sunt momentan suportate." + "Acces" + "Oricine se află într-un spațiu autorizat poate participa." + "Oricine din %1$s se poate alătura." "Membrii spațiului" - "Veți avea nevoie de o adresă de cameră pentru a o face vizibilă în directorul de camere." - "Adresa camerei" + "Spațiile nu sunt momentan suportate." + "Veți avea nevoie de o adresă pentru a o face vizibilă în directorul public." + "Adresă" "Permiteți găsirea acestei camere prin căutarea în directorul de camere publice al %1$s" + "Permiteți găsirea prin căutarea în directorul public." "Vizibilă în directorul de camere publice" - "Oricine" "Cine poate citi mesajele anterioare" "Doar pentru membri, de la momentul în care au fost invitați" "Doar pentru membri, după selectarea acestei opțiuni" "Adresele camerelor sunt modalități de a găsi și accesa camere. Acest lucru vă asigură, de asemenea, că puteți partaja cu ușurință camera dumneavoastră cu alte persoane. Puteți alege să publicați camera în directorul public al camerelor serverului dumneavoastră." "Publicare cameră" - "Adresele camerelor sunt modalități de a găsi și accesa camerele. Acest lucru vă asigură, de asemenea, că puteți partaja cu ușurință camera dvs. cu alte persoane. -Adresa este necesară și pentru ca camera să fie vizibilă în directorul public de camere al %1$s." - "Vizibilitatea camerei" + "Adresele sunt o modalitate de a găsi și accesa camere și spații. Acest lucru asigură, de asemenea, că le puteți partaja cu ușurință cu alții." + "Vizibilitate" "Securitate & confidențialitate" diff --git a/features/roomdetails/impl/src/main/res/values-ru/translations.xml b/features/roomdetails/impl/src/main/res/values-ru/translations.xml index 82efd71adf..47de4c388c 100644 --- a/features/roomdetails/impl/src/main/res/values-ru/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-ru/translations.xml @@ -1,21 +1,21 @@ "Вам понадобится адрес комнаты, чтобы сделать ее видимой в каталоге." - "Адрес комнаты" + "Редактировать адрес комнаты" "Произошла ошибка при обновлении настройки уведомления." "Ваш домашний сервер не поддерживает эту опцию в зашифрованных комнатах, в некоторых комнатах вы можете не получать уведомления." "Опросы" "Только администраторы" "Блокировать людей могут" "Удалить сообщения" - "Все" - "Приглашать людей и принимать запросы на присоединение могут" - "Модерация участников" + "Участник" + "Пригласить людей" + "Список участников" "Сообщения и содержание" - "Администраторы и модераторы" - "Удалять людей и отклонять запросы на присоединение могут" + "Модератор" + "Удалять участников" "Менять изображение комнаты могут" - "Информация о комнате" + "Редактировать комнату" "Менять название комнаты могут" "Менять тему комнаты могут" "Отправлять сообщения могут" @@ -71,6 +71,8 @@ "Тема" "Обновление комнаты…" "В этой комнате нет заблокированных пользователей." + "Проверьте правописание или попробуйте новый поиск" + "Отсутствует результат по запросу “%1$s”" "%1$d пользователь" "%1$d пользователя" @@ -80,11 +82,11 @@ "Только удалить участника" "Разблокировать" "Они снова смогут присоединиться в эту комнату если их пригласят." - "Разбанить пользователя?" + "Разблокировать в комнате" "Заблокированные" "Участники" - "В ожидании" - "Администратор" + "В ожидании" + "Только администраторы" "Модератор" "Владелец" "Участники комнаты" @@ -119,8 +121,8 @@ "Роли" "Информация о комнате" "Роли и разрешения" - "Добавить адрес комнаты" - "Любой желающий может подать заявку на присоединение к комнате, но администратор или модератор должен будет принять запрос." + "Добавить адрес" + "Каждый должен запросить доступ." "Попросить присоединиться" "Да, включить шифрование" "Шифрование комнаты нельзя будет отключить, история сообщений будет видна только участникам комнаты с момента их приглашения или с момента присоединения к комнате. @@ -132,15 +134,16 @@ "Включить сквозное шифрование" "Любой желающий может найти и присоединиться" "Любой" - "Люди могут присоединиться только по приглашению" + "Присоединиться могут только приглашенные люди." "Только по приглашению" - "Доступ в комнату" - "Пространства в настоящее время не поддерживаются." + "Доступ" "Участники пространства" - "Вам понадобится адрес комнаты, чтобы сделать ее видимой в каталоге комнат." - "Адрес комнаты" - "Разрешить поиск этой комнаты %1$s в каталоге общественных комнат" - "Доступна в каталоге общественных комнат" + "Пространства в настоящее время не поддерживаются." + "Вам понадобится адрес комнаты, чтобы сделать ее видимой в каталоге." + "Адрес" + "Опубликовать %1$s в каталоге публичных комнат" + "Разрешить поиск в публичном каталоге." + "Доступна в списке публичных комнат" "Любой" "Кто может читать историю" "Участники только с тех пор, как они были приглашены" @@ -148,8 +151,6 @@ "Адреса комнат — это способ найти комнату и получить к ней доступ. Это также гарантирует, что вы сможете легко поделиться своей комнатой с другими. Вы можете опубликовать свою комнату в каталоге общедоступных комнат на домашнем сервере." "Публикация комнат" - "Адреса комнат — это способ найти комнату и получить к ним доступ. Это также гарантирует, что вы сможете легко поделиться своей комнатой с другими. -Адрес также необходим для отображения комнаты в каталоге %1$s общедоступных комнат." - "Видимость комнаты" + "Видимость" "Безопасность и конфиденциальность" diff --git a/features/roomdetails/impl/src/main/res/values-sk/translations.xml b/features/roomdetails/impl/src/main/res/values-sk/translations.xml index 530455cbf2..f61e3341bf 100644 --- a/features/roomdetails/impl/src/main/res/values-sk/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-sk/translations.xml @@ -1,21 +1,21 @@ - "Budete potrebovať adresu miestnosti, aby bola viditeľná v adresári." - "Adresa miestnosti" + "Budete potrebovať adresu, aby sa zobrazovala vo verejnom adresári." + "Upraviť adresu" "Pri aktualizácii nastavenia oznámenia došlo k chybe." "Váš domovský server nepodporuje túto možnosť v šifrovaných miestnostiach, v niektorých miestnostiach nemusíte dostať upozornenie." "Ankety" - "Iba správcovia" + "Správca" "Zakázať ľudí" "Odstrániť správy" - "Všetci" - "Pozvite ľudí a prijmite žiadosti o pripojenie" - "Moderovanie členov" + "Člen" + "Pozvať ľudí" + "Spravovať členov" "Správy a obsah" - "Správcovia a moderátori" - "Odstrániť ľudí a odmietnuť žiadosti o pripojenie" + "Moderátor" + "Odstrániť ľudí" "Zmeniť obrázok miestnosti" - "Podrobnosti o miestnosti" + "Upraviť podrobnosti" "Zmeniť názov miestnosti" "Zmeniť tému miestnosti" "Odoslať správy" @@ -42,7 +42,7 @@ "Zašifrované" "Nešifrované" "Verejná miestnosť" - "Upraviť miestnosť" + "Upraviť podrobnosti" "Vyskytla sa neznáma chyba a informácie nebolo možné zmeniť." "Nepodarilo sa aktualizovať miestnosť" "Správy sú zabezpečené zámkami. Jedine vy a príjemcovia máte jedinečné kľúče na ich odomknutie." @@ -50,6 +50,8 @@ "Pri načítaní nastavení oznámení došlo k chybe." "Nepodarilo sa stlmiť túto miestnosť, skúste to prosím znova." "Nepodarilo sa zrušiť stlmenie tejto miestnosti, skúste to prosím znova." + "Nezatvárajte aplikáciu, kým sa neukončí pozývanie." + "Príprava pozvánok…" "Pozvať ľudí" "Opustiť konverzáciu" "Opustiť miestnosť" @@ -68,7 +70,14 @@ "Informácie o miestnosti" "Téma" "Aktualizácia miestnosti…" - "V tejto miestnosti nie sú žiadni zakázaní používatelia." + "Neexistujú žiadni zablokovaní používatelia." + + "%1$d zakázaný" + "%1$d zakázaní" + "%1$d zakázaných" + + "Skontrolujte preklepy alebo skúste nové vyhľadávanie" + "Žiadne výsledky pre „%1$s“" "%1$d osoba" "%1$d osoby" @@ -78,11 +87,16 @@ "Iba odstrániť člena" "Zrušiť zákaz" "V prípade pozvania sa budú môcť znova pripojiť k tejto miestnosti." - "Zrušiť zákaz používateľa" + "Zrušiť zákaz prístupu do miestnosti" "Zakázaní" "Členovia" - "Čaká sa" - "Administrátor" + + "%1$d pozvaný" + "%1$d pozvaní" + "%1$d pozvaných" + + "Čaká na schválenie" + "Správca" "Moderátor" "Vlastník" "Členovia miestnosti" @@ -110,15 +124,15 @@ "Správy a obsah" "Moderátori" "Vlastníci" - "Oprávnenia" + "Povolenia" "Obnoviť povolenia" "Po obnovení oprávnení prídete o aktuálne nastavenia." "Obnoviť oprávnenia?" "Roly" "Podrobnosti o miestnosti" "Roly a povolenia" - "Pridať adresu miestnosti" - "Ktokoľvek môže požiadať o pripojenie do miestnosti, ale správca alebo moderátor bude musieť žiadosť prijať." + "Pridať adresu" + "Všetci musia požiadať o prístup." "Požiadať o pripojenie" "Áno, povoliť šifrovanie" "Po aktivácii nie je možné zakázať šifrovanie pre miestnosť. História správ bude viditeľná len pre členov miestnosti, odkedy boli pozvaní alebo keď vstúpili do miestnosti. @@ -128,17 +142,22 @@ To môže brániť správnemu fungovaniu robotov a premostení. Neodporúčame p "Po zapnutí už šifrovanie nie je možné vypnúť." "Šifrovanie" "Povoliť end-to-end šifrovanie" - "Ktokoľvek môže nájsť a pripojiť sa" + "Pripojiť sa môže ktokoľvek." "Ktokoľvek" - "Ľudia sa môžu pripojiť len vtedy, ak sú pozvaní" + "Vyberte, ktorých členovia priestorov sa môžu pripojiť k tejto miestnosti bez pozvánky. %1$s" + "Spravovať priestory" + "Pripojiť sa môžu iba pozvaní ľudia." "Iba na pozvánku" - "Prístup do miestnosti" - "Priestory momentálne nie sú podporované" + "Prístup" + "Ktokoľvek v povolených priestoroch sa môže pripojiť." + "Ktokoľvek v %1$s sa môže pripojiť." "Členovia priestoru" - "Budete potrebovať adresu miestnosti, aby bola viditeľná v adresári miestností." - "Adresa miestnosti" + "Priestory momentálne nie sú podporované" + "Budete potrebovať adresu, aby sa zobrazovala vo verejnom adresári." + "Adresa" "Umožniť vyhľadanie tejto miestnosti v adresári verejných miestností %1$s" - "Viditeľné v adresári verejných miestností" + "Umožniť nájdenie vyhľadávaním vo verejnom adresári." + "Viditeľné vo verejnom adresári" "Ktokoľvek" "Kto môže čítať históriu" "Len pre členov, odkedy boli pozvaní" @@ -146,8 +165,7 @@ To môže brániť správnemu fungovaniu robotov a premostení. Neodporúčame p "Adresy miestností predstavujú spôsoby, ako nájsť a získať prístup k miestnostiam. To tiež zaisťuje, že môžete jednoducho zdieľať svoju miestnosť s ostatnými. Môžete sa rozhodnúť zverejniť svoju miestnosť v adresári verejných miestností vášho domovského servera." "Zverejnenie miestnosti" - "Adresy miestností predstavujú spôsoby, ako nájsť a získať prístup k miestnostiam. Toto tiež zaisťuje, že môžete jednoducho zdieľať svoju miestnosť s ostatnými. -Adresa je tiež potrebná, aby bola miestnosť viditeľná v adresári verejných miestností %1$s." - "Viditeľnosť miestnosti" + "Adresy sú spôsob, ako nájsť a získať prístup do miestností a priestorov. To tiež zabezpečuje, že ich môžete jednoducho zdieľať s ostatnými." + "Viditeľnosť" "Bezpečnosť a súkromie" diff --git a/features/roomdetails/impl/src/main/res/values-sv/translations.xml b/features/roomdetails/impl/src/main/res/values-sv/translations.xml index 1cc11933ab..1cc0d81715 100644 --- a/features/roomdetails/impl/src/main/res/values-sv/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-sv/translations.xml @@ -8,14 +8,12 @@ "Endast administratörer" "Banna personer" "Ta bort meddelanden" - "Alla" "Bjuda in personer och acceptera förfrågningar om att gå med" - "Medlemsmoderering" "Meddelanden och innehåll" "Administratörer och moderatorer" "Ta bort personer och avslå förfrågningar om att gå med" "Byt rumsavatar" - "Rumsdetaljer" + "Redigera rummet" "Byt rumsnamn" "Byt rumsämne" "Skicka meddelanden" @@ -77,12 +75,11 @@ "Ta bara bort medlem" "Avbanna" "Denne kommer kunna gå med i rummet igen om denne bjuds in" - "Avbanna användare" + "Avbanna från rummet" "Bannade" "Medlemmar" - "Väntar" - "Admin" - "Moderator" + "Endast administratörer" + "Administratörer och moderatorer" "Ägare" "Rumsmedlemmar" "Avbannar %1$s" @@ -109,7 +106,6 @@ "Meddelanden och innehåll" "Moderatorer" "Ägare" - "Behörigheter" "Återställ behörigheter" "När du har återställt behörigheterna kommer du att förlora de aktuella inställningarna." "Återställ behörigheter?" @@ -132,21 +128,16 @@ Vi rekommenderar inte att aktivera kryptering för rum som vem som helst kan hit "Användare kan bara gå med om de är inbjudna" "Endast inbjudan" "Tillgång till rum" - "Utrymmen stöds för närvarande inte" "Utrymmesmedlemmar" - "Du behöver en rumsadress för att göra den synlig i rumskatalogen." - "Rumsadress" + "Utrymmen stöds för närvarande inte" + "Du behöver en rumsadress för att göra den synlig i katalogen." "Tillåt att detta rum hittas genom att söka i den offentliga rumskatalogen på %1$s" "Synlig i katalogen för offentliga rum" - "Vem som helst" "Vem kan läsa historik" "Endast medlemmar sedan de bjöds in" "Endast medlemmar sedan det här alternativet har valts" "Rumsadresser är sätt att hitta och komma åt rum. Detta säkerställer också att du enkelt kan dela ditt rum med andra. Du kan välja att publicera ditt rum i din hemservers offentliga rumskatalog." "Rumspublicering" - "Rumsadresser är sätt att hitta och komma åt rum. Detta säkerställer också att du enkelt kan dela ditt rum med andra. -Adressen krävs också för att göra rummet synligt i%1$s katalog för offentliga rum." - "Rumssynlighet" "Säkerhet och sekretess" diff --git a/features/roomdetails/impl/src/main/res/values-tr/translations.xml b/features/roomdetails/impl/src/main/res/values-tr/translations.xml index 92caaaf5eb..01cac3a6cb 100644 --- a/features/roomdetails/impl/src/main/res/values-tr/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-tr/translations.xml @@ -8,14 +8,12 @@ "Yalnızca yöneticiler" "İnsanları yasakla" "Mesajları kaldır" - "Herkes" "Kişileri davet etme ve katılma isteklerini kabul etme" - "Üye moderasyonu" "Mesajlar ve içerik" "Yöneticiler ve moderatörler" "Kişileri kaldırma ve katılma isteklerini reddetme" "Oda resmini değiştir" - "Oda bilgileri" + "Odayı Düzenle" "Oda adını değiştir" "Oda konusunu değiştir" "Mesaj gönder" @@ -73,12 +71,10 @@ "Yalnızca üyeyi kaldır" "Yasağı Kaldır" "Davet edildikleri takdirde bu odaya tekrar katılabileceklerdir." - "Kullanıcının yasağını kaldır" "Yasaklandı" "Üyeler" - "Beklemede" - "Yönetici" - "Moderatör" + "Yalnızca yöneticiler" + "Yöneticiler ve moderatörler" "Oda üyeleri" "Yasak kaldırılıyor %1$s" "Özel ayarlara izin ver" @@ -102,7 +98,6 @@ "Üye moderasyonu" "Mesajlar ve içerik" "Moderatörler" - "İzinler" "İzinleri sıfırla" "İzinleri sıfırladığınızda, mevcut ayarları kaybedersiniz." "İzinleri sıfırla?" @@ -125,9 +120,9 @@ Herkesin bulabileceği ve katılabileceği odalar için şifrelemenin etkinleşt "İnsanlar yalnızca davet edildiklerinde katılabilirler" "Yalnızca davet" "Oda Erişimi" - "Alanlar şu anda desteklenmiyor" "Alan üyeleri" - "Oda dizininde görünür kılmak için bir oda adresine ihtiyacınız olacaktır." + "Alanlar şu anda desteklenmiyor" + "Dizinde görünür hale getirmek için bir oda adresine ihtiyacınız olacak." "Oda adresi" "Bu odanın %1$s genel oda dizininde arama yapılarak bulunmasına izin verin" "Genel oda dizininde görünür" @@ -138,8 +133,6 @@ Herkesin bulabileceği ve katılabileceği odalar için şifrelemenin etkinleşt "Oda adresleri, odaları bulmanın ve odalara erişmenin yoludur. Bu aynı zamanda odanızı başkalarıyla kolayca paylaşabilmenizi sağlar. Odanızı ana sunucunuzun genel oda dizininde yayınlamayı seçebilirsiniz." "Oda yayınlama" - "Oda adresleri, odaları bulmanın ve odalara erişmenin yollarıdır. Bu aynı zamanda odanızı başkalarıyla kolayca paylaşabilmenizi sağlar. -Adres, odayı %1$s genel oda dizininde görünür kılmak için de gereklidir." "Oda görünürlüğü" "Güvenlik ve gizlilik" diff --git a/features/roomdetails/impl/src/main/res/values-uk/translations.xml b/features/roomdetails/impl/src/main/res/values-uk/translations.xml index 7ef99d8430..fddcca05d0 100644 --- a/features/roomdetails/impl/src/main/res/values-uk/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-uk/translations.xml @@ -8,14 +8,12 @@ "Тільки для адміністраторів" "Заблоковувати людей" "Вилучати повідомлення" - "Усі" "Запрошувати людей і приймати запити на приєднання" - "Модерація учасників" "Повідомлення та зміст" "Адміністратори та модератори" "Вилучати людей і відхиляти запити на приєднання" "Змінювати аватар кімнати" - "Деталі кімнати" + "Редагувати кімнату" "Змінювати назву кімнати" "Змінювати тему кімнати" "Надсилати повідомлення" @@ -78,12 +76,11 @@ "Лише вилучити учасника" "Розблокувати" "Вони зможуть знову приєднатися до цієї кімнати, якщо їх запросять." - "Розблокувати користувача" + "Розблокувати в кімнаті" "Заблоковані" "Учасники" - "На розгляді" - "Адміністратор" - "Модератор" + "Тільки для адміністраторів" + "Адміністратори та модератори" "Власник" "Учасники кімнати" "Розблокування %1$s" @@ -110,7 +107,6 @@ "Повідомлення та зміст" "Модератори" "Власники" - "Дозволи" "Скинути дозволи" "Після скидання дозволів ви втратите поточні налаштування." "Скинути дозволи?" @@ -133,21 +129,19 @@ "Люди можуть приєднатися, лише якщо їх запросили" "Лише запрошені" "Доступ до кімнати" - "Простори наразі не підтримуються" "Учасники простору" - "Вам знадобиться адреса кімнати, щоб зробити її видимою в каталозі кімнат." + "Простори наразі не підтримуються" + "Вам знадобиться адреса кімнати, щоб зробити її видимою в каталозі." "Адреса кімнати" "Дозвольте, щоб цю кімнату можна було знайти за допомогою пошуку в каталозі загальнодоступних кімнат %1$s " "Видима в каталозі загальнодоступних кімнат" - "Кожний" + "Будь-хто" "Хто може читати історію" "Лише учасники з моменту запрошення" "Лише учасники після вибору цього параметра" "Адреси кімнат — це спосіб знайти кімнату та отримати до неї доступ. Це також гарантує, що ви можете легко поділитися своєю кімнатою з іншими. Ви можете опублікувати свою кімнату в каталозі загальнодоступних кімнат вашого домашнього сервера." "Публікація в кімнаті" - "Адреси кімнат — це спосіб знайти та отримати доступ до кімнат. Це також гарантує, що ви зможете легко поділитися своєю кімнатою з іншими. -Адреса також необхідна, щоб зробити кімнату видимою в каталозі загальнодоступних кімнат %1$s." "Видимість кімнати" "Безпека й приватність" diff --git a/features/roomdetails/impl/src/main/res/values-ur/translations.xml b/features/roomdetails/impl/src/main/res/values-ur/translations.xml index 8cd040835f..524afe515d 100644 --- a/features/roomdetails/impl/src/main/res/values-ur/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-ur/translations.xml @@ -6,14 +6,12 @@ "صرف منتظمین" "لوگوں کو محظور کریں" "پیغامات ہٹائیں" - "ہر کوئی" "لوگوں کو مدعو کریں اور شمولیت کی درخواستیں قبول کریں" - "ارکان کا اعتدال" "پیغامات اور مواد" "منتظمین اور ناظمین" "لوگوں کو ہٹا دیں اور شمولیت کی درخواستیں مسترد کریں" "کمرے کا اوتار بدلیں" - "کمرے کی تفصیلات" + "کمرے میں ترمیم کریں" "کمرے کا نام بدلیں" "کمرے کا موضوع بدلیں" "پیغامات بھیجیں" @@ -68,12 +66,10 @@ "رکن کو صرف ہٹائیں" "غیر محظور کریں" "اگر وہ مدعو کیا جائیں تو وہ دوبارہ اس کمرے میں شامل ہوسکیں گے۔" - "صارف کو غیر محظور کریں" "محظور" "اراکین" - "زیر التواء" - "منتظم" - "ناظم" + "صرف منتظمین" + "منتظمین اور ناظمین" "کمرے کے ارکان" "%1$s کو غیر محظور کر رہا ہے" "حسب ضرورت ترتیب کی اجازت دیں" @@ -97,7 +93,6 @@ "ارکان کا اعتدال" "پیغامات اور مواد" "ناظمین" - "اجازتیں" "اجازتیں بحال کریں" "ایک بار جب آپ اجازتیں بحال کردیں گے، آپ موجودہ ترتیبات کھو دیں گے۔" "اجازتیں بحال کریں؟" diff --git a/features/roomdetails/impl/src/main/res/values-uz/translations.xml b/features/roomdetails/impl/src/main/res/values-uz/translations.xml index e9b50e5400..466bf18b8b 100644 --- a/features/roomdetails/impl/src/main/res/values-uz/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-uz/translations.xml @@ -1,32 +1,37 @@ + "Katalogda ko‘rinadigan qilish uchun xona manzili kerak bo‘ladi." + "Xona manzili" "Bildirishnoma sozlamalarini yangilashda xatolik yuz berdi." "Uy serveringiz shifrlangan xonalarda ushbu imkoniyatni qoʻllab-quvvatlamaydi, shuning uchun baʼzi xonalardagi xabarlarni olmasligingiz mumkin." "Soʻrovnomalar" "Faqat adminlar" "Odamlarni taqiqlash" "Xabarlarni olib tashlash" - "Har kim" "Odamlarni taklif qiling va qo‘shilish so‘rovlarini qabul qiling" - "Aʻzo moderatsiyasi" + "A’zolarni boshqarish" "Xabarlar va kontent" "Adminlar va moderatorlar" "Odamlarni olib tashlash va qoʻshilish soʻrovlarini rad etish" "Xona avatarini oʻzgartirish" - "Xona tafsilotlari" + "Tafsilotlarni tahrirlash" "Xona nomini oʻzgartirish" "Xona mavzusini almashtirish" "Xabarlar yuborish" "Administratorlarni tahrirlash" "Bu amalni bekor qila olmaysiz. Siz foydalanuvchini o‘zingiz bilan bir xil quvvat darajasiga ega bo‘lishga undayapsiz." "Admin qo‘shilsinmi?" + "Bu amalni bekor qila olmaysiz. Siz egalikni tanlangan foydalanuvchilarga o‘tkazmoqdasiz. Tark etsangiz, bu doimiy bo‘ladi." + "Egalik huquqini o‘tkazasizmi?" "Pastga tushirish" "Siz oʻzingizni imtiyozlardan mahrum qilayotganingiz sababli, bu o‘zgarishni bekor qila olmaysiz. Agar xonadagi so‘nggi imtiyozli foydalanuvchi bo‘lsangiz, imtiyozlarni qayta tiklash imkonsiz bo‘ladi." "O‘z darajangizni pasaytirmoqchimisiz?" "%1$s (Jarayonda)" "(Kutilmoqda)" "Administratorlar avtomatik ravishda moderator imtiyozlariga ega" + "Egalar avtomatik ravishda administrator huquqlariga ega." "Moderatorlarni tahrirlash" + "Egalarni tanlang" "Adminlar" "Moderatorlar" "Azolar" @@ -36,7 +41,7 @@ "Shifrlangan" "Shifrlanmagan" "Jamoat xonasi" - "Xonani tahrirlash" + "Tafsilotlarni tahrirlash" "Nomaʼlum xatolik yuz berdi va maʼlumotni oʻzgartirib boʻlmadi." "Xonani yangilab bo‘lmadi" "Xabarlar qulflar bilan himoyalangan. Faqat siz va qabul qiluvchilar ularni qulfdan chiqarish uchun noyob kalitlarga ega." @@ -47,13 +52,16 @@ "Odamlarni taklif qiling" "Suhbatni tark etish" "Xonani tark etish" + "Media va fayllar" "Maxsus" "Standart" "Bildirishnomalar" "Qadalgan xabarlar" "Profil" + "Qo‘shilish uchun so‘rovlar" "Rollar va ruxsatlar" "Xona nomi" + "Xavfsizlik va maxfiylik" "Xavfsizlik" "Xonani baham ko\'ring" "Xona haqida maʼlumot" @@ -68,12 +76,12 @@ "Faqat aʻzoni olib tashlash" "Taqiqni bekor qilish" "Agar taklif qilinsa, ular bu xonaga qayta qo‘shilishlari mumkin." - "Foydalanuvchini blokdan chiqarish" + "Xonadan taqiqni olib tashlash" "Taqiqlangan" "Azolar" - "Kutilmoqda" - "Admin" - "Moderator" + "Faqat adminlar" + "Adminlar va moderatorlar" + "Egasi" "Xona a\'zolari" "Taqiqni bekor qilish %1$s" "Moslashtirilgan sozlamalarga ruxsat bering" @@ -91,22 +99,45 @@ "Faqat eslatmalar va kalit so\'zlar" "Bu xonada menga xabar bering" "Adminlar" + "Adminlar va egalari" "Rolimni o‘zgartirish" "Aʼzolikka tushirish" "Moderatorga pasaytirish" "Aʻzo moderatsiyasi" "Xabarlar va kontent" "Moderatorlar" - "Ruxsatlar" + "Egalari" "Ruxsatlarni tiklash" "Ruxsatlarni asliga qaytargach, joriy sozlamalarni yoʻqotasiz." "Ruxsatlar asliga qaytarilsinmi?" "Rollar" "Xona tafsilotlari" "Rollar va ruxsatlar" + "Xona manzilini kiritish" + "Xonaga qo‘shilishni istalgan kishi so‘rashi mumkin, lekin administrator yoki moderator so‘rovni qabul qilishi kerak" "Qo‘shilishni so‘rang" + "Ha, shifrlashni yoqish" + "Yoqilgandan so‘ng, xona uchun shifrlashni o‘chirib bo‘lmaydi. Xabarlar tarixi faqat xona a’zolari taklif qilinganidan yoki xonaga qo‘shilganidan keyingi davrdan boshlab ko‘rinadi. Xona a’zolaridan tashqari hech kim xabarlarni o‘qiy olmaydi. Bu botlar va ko‘priklarning to‘g‘ri ishlashiga to‘sqinlik qilishi mumkin. +Shu sababli, har kim topishi va qo‘shilishi mumkin bo‘lgan xonalar uchun shifrlashni yoqishni tavsiya etmaymiz." + "Shifrlash yoqilsinmi?" + "Yoqilgandan keyin shifrlashni faolsizlantirish imkonsiz." "Shifrlash" + "End-to-end shifrlashni yoqish" + "Istalgan kishi topishi va qo‘shilishi mumkin" "Har kim" - "Har kim" - "Xonaning ko‘rinishi" + "Odamlar faqat taklif qilingan taqdirdagina qo‘shilishi mumkin" + "Faqat taklif qilish" + "Xonaga kirish huquqi" + "Maydon a’zolari" + "Hozirda maydonlar qo‘llab-quvvatlanmaydi" + "Katalogda ko‘rinadigan qilish uchun xona manzili kerak bo‘ladi." + "Bu xonani %1$s umumiy xonalar ro‘yxatidan qidirib topish imkoniyatini berish" + "Umumiy xona ro‘yxatida ko‘rinadi" + "Tarixni kim o‘qiy oladi" + "Taklif qilinganidan buyon faqat a’zolar" + "A’zolar faqat bu parametr tanlanganidan keyin" + "Xona manzillari xonalarni topish va ularga kirish usullaridir. Bu shuningdek xonangizni boshqalar bilan oson ulashish imkonini beradi. +Xonangizni o‘z homeserveringizning ommaviy xonalar ro‘yxatida e’lon qilishni tanlashingiz mumkin." + "xona nashriyoti" + "Xavfsizlik va maxfiylik" diff --git a/features/roomdetails/impl/src/main/res/values-zh-rTW/translations.xml b/features/roomdetails/impl/src/main/res/values-zh-rTW/translations.xml index 888701406d..0332c0ffd5 100644 --- a/features/roomdetails/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-zh-rTW/translations.xml @@ -1,21 +1,21 @@ - "您需要聊天室地址才能在目錄中顯示它。" - "聊天室地址" + "您需要地址才能在公開目錄中顯示。" + "編輯地址" "更新通知設定時發生錯誤。" "您的家伺服器在加密聊天室中不支援此選項,可能無法收到部份聊天室的通知。" "所有投票" - "僅限管理員" + "管理員" "管理黑名單" "移除訊息" - "所有人" - "邀請夥伴並接受加入請求" - "成員管理" + "成員" + "邀請夥伴" + "管理成員" "訊息與內容" - "管理員和版主" - "移除夥伴並拒絕加入請求" + "版主" + "移除夥伴" "變更聊天室大頭照" - "聊天室資訊" + "編輯詳細資訊" "變更聊天室名稱" "變更聊天室主題" "傳送訊息" @@ -42,7 +42,7 @@ "已加密" "未加密" "公開的聊天室" - "編輯聊天室" + "編輯詳細資訊" "發生未知錯誤,無法變更資訊。" "無法更新聊天室" "訊息透過鎖定保護。只有您與收件者才有解鎖它們的唯一金鑰。" @@ -62,7 +62,7 @@ "釘選訊息" "個人檔案" "請求加入" - "身份與權限" + "角色與權限" "聊天室名稱" "安全與隱私" "安全性" @@ -70,18 +70,20 @@ "聊天室資訊" "主題" "正在更新聊天室…" - "此聊天室沒有黑名單。" + "沒有被封鎖的使用者。" + "檢查拼字或嘗試新搜尋" + "找不到「%1$s」" - "%1$d 位夥伴" + "%1$d 個人" "踢出並加入黑名單" "僅移除成員" "解除黑名單" "如果收到邀請,他們能再次加入聊天室。" - "解除黑名單" + "從聊天室解除封鎖" "黑名單" "成員" - "待定" + "擱置中" "管理員" "版主" "擁有者" @@ -116,9 +118,9 @@ "確定要重設權限嗎?" "身份" "聊天室資訊" - "身份與權限" - "新增聊天室地址" - "任何人都可以要求加入聊天室,但管理員必須接受請求。" + "角色與權限" + "新增地址" + "所有人都必須申請存取權。" "要求加入" "是的,啟用加密" "啟用後就無法停用聊天室的加密,只有受邀的聊天室成員或加入聊天室後才能看到訊息歷史紀錄。 @@ -128,17 +130,18 @@ "一旦啟用就無法停用加密。" "加密" "啟用端到端加密" - "任何人都可以找到並加入" + "任何人都可以加入。" "任何人" - "人們僅有受到邀請時才能加入" + "僅受邀者才能加入。" "僅限邀請" - "聊天室存取權" - "目前不支援空間" + "存取權" "空間成員" - "您需要聊天室地址才能在聊天室目錄中顯示。" - "聊天室地址" + "目前不支援空間" + "您需要地址才能在公開目錄中顯示。" + "地址" "允許透過搜尋 %1$s 公開聊天室目錄找到此聊天室" - "在公開聊天室目錄中可見" + "允許其他人透過公開目錄找到。" + "在公開目錄中可見" "任何人" "誰可以讀取歷史紀錄" "僅在成員被邀請後" @@ -146,8 +149,6 @@ "聊天室地址是尋找與存取聊天室的方法。也確保您可以輕鬆與其他人分享聊天室。 您可以選擇在家伺服器公開聊天室目錄中發佈您的聊天室。" "聊天室發佈" - "聊天室地址是尋找與存取聊天室的方法。也確保您可以輕鬆與其他人分享聊天室。 -若要讓聊天室在 %1$s 公開聊天室目錄中可見,地址也是必要的。" - "聊天室能見度" + "能見度" "安全與隱私" diff --git a/features/roomdetails/impl/src/main/res/values-zh/translations.xml b/features/roomdetails/impl/src/main/res/values-zh/translations.xml index b260163ca3..f925bdca2a 100644 --- a/features/roomdetails/impl/src/main/res/values-zh/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-zh/translations.xml @@ -8,14 +8,12 @@ "仅限管理员" "封禁成员" "移除消息" - "所有人" "邀请他人及接受加入请求" - "成员权限" "消息和内容" "管理员和协管员" "移除成员及拒绝加入请求" "更改聊天室头像" - "聊天室详情" + "编辑聊天室" "更改聊天室名称" "更改聊天室主题" "发送消息" @@ -70,7 +68,7 @@ "聊天室信息" "主题" "正在更新聊天室……" - "此聊天室里没有被封禁的用户。" + "没有被封禁的用户。" "%1$d 人" @@ -78,12 +76,11 @@ "仅移除成员" "取消封禁" "如果受到邀请,他们可以重新加入聊天室。" - "解封用户" + "从房间取消解封" "已封禁用户" "成员" - "待处理" - "管理员" - "协管员" + "仅限管理员" + "管理员和协管员" "所有者" "聊天室成员" "解除封禁 %1$s" @@ -110,7 +107,6 @@ "消息和内容" "协管员" "所有者" - "权限" "重置权限" "重置权限后,您将丢失当前设置。" "重置权限?" @@ -133,9 +129,9 @@ "只有受邀者才能加入" "仅限邀请" "房间访问权限" - "目前不支持空间" "空间成员" - "你需要有房间地址才能使其在房间目录中可见。" + "目前不支持空间" + "你需要房间地址才能使其在目录中可见。" "房间地址" "允许通过搜索 %1$s 的公共房间目录来发现此房间" "在公共房间目录中可见" @@ -146,8 +142,6 @@ "房间地址是查找和访问房间的方式。这也确保你可以轻松地向他人分享房间。 你可以选择在你服务器的公共房间目录中发布你的房间。" "房间发布" - "房间地址是查找和访问房间的方式。这也确保你可以轻松地向他人分享房间。 -在 %1$s 的公共房间目录中显示该房间时也需要地址。" "房间可见性" "安全与隐私" diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index 3f4541dacb..cd90edf709 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -1,23 +1,23 @@ - "You’ll need a room address in order to make it visible in the directory." - "Room address" + "You’ll need an address in order to make it visible in the public directory." + "Edit address" "An error occurred while updating the notification setting." "Your homeserver does not support this option in encrypted rooms, you may not get notified in some rooms." "Polls" - "Admins only" + "Admin" "Ban people" "Remove messages" - "Everyone" - "Invite people and accept requests to join" - "Member moderation" + "Member" + "Invite people" + "Manage members" "Messages and content" - "Admins and moderators" - "Remove people and decline requests to join" - "Change room avatar" - "Room details" - "Change room name" - "Change room topic" + "Moderator" + "Remove people" + "Change avatar" + "Edit details" + "Change name" + "Change topic" "Send messages" "Edit Admins" "You will not be able to undo this action. You are promoting the user to have the same power level as you." @@ -42,7 +42,7 @@ "Encrypted" "Not encrypted" "Public room" - "Edit Room" + "Edit details" "There was an unknown error and the information couldn\'t be changed." "Unable to update room" "Messages are secured with locks. Only you and the recipients have the unique keys to unlock them." @@ -62,27 +62,37 @@ "Pinned messages" "Profile" "Requests to join" - "Roles and permissions" + "Roles & permissions" "Room name" "Security & privacy" "Security" "Share room" "Room info" "Topic" - "Updating room…" - "There are no banned users in this room." - - "%1$d person" - "%1$d people" + "Updating details…" + "There are no banned users." + + "%1$d Banned" + "%1$d Banned" - "Ban from room" + "Check the spelling or try a new search" + "No results for “%1$s”" + + "%1$d Person" + "%1$d People" + + "Ban user" "Only remove member" "Unban" "They will be able to join this room again if invited." "Unban user" "Banned" "Members" - "Pending" + + "%1$d Invited" + "%1$d Invited" + + "Pending" "Admin" "Moderator" "Owner" @@ -117,10 +127,12 @@ "Reset permissions?" "Roles" "Room details" - "Roles and permissions" - "Add room address" - "Anyone can ask to join the room but an administrator or moderator will have to accept the request." + "Roles & permissions" + "Add address" + "Anyone in authorised spaces can join, but everyone else must request access." + "Everyone must request access." "Ask to join" + "Anyone in %1$s can join, but everyone else must request access." "Yes, enable encryption" "Once enabled, encryption for a room cannot be disabled, Message history will only be visible for room members since they were invited or since they joined the room. No one besides the room members will be able to read messages. This may prevent bots and bridges to work correctly. @@ -129,26 +141,31 @@ We do not recommend enabling encryption for rooms that anyone can find and join. "Once enabled, encryption cannot be disabled." "Encryption" "Enable end-to-end encryption" - "Anyone can find and join" + "Anyone can join." "Anyone" - "People can only join if they are invited" + "Choose which spaces’ members can join this room without an invitation. %1$s" + "Manage spaces" + "Only invited people can join." "Invite only" - "Room access" - "Spaces are not currently supported" + "Access" + "Anyone in authorised spaces can join." + "Anyone in %1$s can join." "Space members" - "You’ll need a room address in order to make it visible in the room directory." - "Room address" + "Spaces are not currently supported" + "You’ll need an address in order to make it visible in the public directory." + "Address" "Allow for this room to be found by searching %1$s public room directory" - "Visible in public room directory" - "Anyone" + "Allow to be found by searching the public directory." + "Visible in public directory" + "Anyone (history is public)" + "Changes won\'t affect past messages, only new ones. %1$s" "Who can read history" - "Members only since they were invited" - "Members only since selecting this option" + "Members since invited" + "Members (full history)" "Room addresses are ways to find and access rooms. This also ensures you can easily share your room with others. You can choose to publish your room in your homeserver public room directory." "Room publishing" - "Room addresses are ways to find and access rooms. This also ensures you can easily share your room with others. -The address is also required to make the room visible in %1$s public room directory." - "Room visibility" + "Addresses are a way to find and access rooms and spaces. This also ensures you can easily share them with others." + "Visibility" "Security & privacy" diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPointTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPointTest.kt index f2412e616b..5042f942b6 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPointTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,26 +10,26 @@ package io.element.android.features.roomdetails.impl import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node import com.bumble.appyx.testing.junit4.util.MainDispatcherRule import com.google.common.truth.Truth.assertThat -import io.element.android.features.call.api.CallType -import io.element.android.features.call.api.ElementCallEntryPoint -import io.element.android.features.changeroommemberroes.api.ChangeRoomMemberRolesEntryPoint -import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint -import io.element.android.features.messages.api.MessagesEntryPoint -import io.element.android.features.poll.api.history.PollHistoryEntryPoint -import io.element.android.features.reportroom.api.ReportRoomEntryPoint +import io.element.android.features.call.test.FakeElementCallEntryPoint +import io.element.android.features.changeroommemberroles.test.FakeChangeRoomMemberRolesEntryPoint +import io.element.android.features.changeroommemberroles.test.FakeRolesAndPermissionsEntryPoint +import io.element.android.features.knockrequests.test.FakeKnockRequestsListEntryPoint +import io.element.android.features.messages.test.FakeMessagesEntryPoint +import io.element.android.features.poll.test.history.FakePollHistoryEntryPoint +import io.element.android.features.reportroom.test.FakeReportRoomEntryPoint import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint -import io.element.android.features.verifysession.api.OutgoingVerificationEntryPoint +import io.element.android.features.roomdetailsedit.test.FakeRoomDetailsEditEntryPoint +import io.element.android.features.securityandprivacy.test.FakeSecurityAndPrivacyEntryPoint +import io.element.android.features.verifysession.test.FakeOutgoingVerificationEntryPoint import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.room.FakeJoinedRoom -import io.element.android.libraries.mediaviewer.api.MediaGalleryEntryPoint -import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint +import io.element.android.libraries.mediaviewer.test.FakeMediaGalleryEntryPoint +import io.element.android.libraries.mediaviewer.test.FakeMediaViewerEntryPoint import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.node.TestParentNode @@ -50,62 +51,37 @@ class DefaultRoomDetailsEntryPointTest { RoomDetailsFlowNode( buildContext = buildContext, plugins = plugins, - pollHistoryEntryPoint = object : PollHistoryEntryPoint { - override fun createNode(parentNode: Node, buildContext: BuildContext) = lambdaError() - }, - elementCallEntryPoint = object : ElementCallEntryPoint { - override fun startCall(callType: CallType) = lambdaError() - override suspend fun handleIncomingCall( - callType: CallType.RoomCall, - eventId: EventId, - senderId: UserId, - roomName: String?, - senderName: String?, - avatarUrl: String?, - timestamp: Long, - expirationTimestamp: Long, - notificationChannelId: String, - textContent: String? - ) = lambdaError() - }, + pollHistoryEntryPoint = FakePollHistoryEntryPoint(), + elementCallEntryPoint = FakeElementCallEntryPoint(), room = FakeJoinedRoom(), analyticsService = FakeAnalyticsService(), - messagesEntryPoint = object : MessagesEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext) = lambdaError() - }, - knockRequestsListEntryPoint = object : KnockRequestsListEntryPoint { - override fun createNode(parentNode: Node, buildContext: BuildContext) = lambdaError() - }, - mediaViewerEntryPoint = object : MediaViewerEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext) = lambdaError() - }, - mediaGalleryEntryPoint = object : MediaGalleryEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext) = lambdaError() - }, - outgoingVerificationEntryPoint = object : OutgoingVerificationEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext) = lambdaError() - }, - reportRoomEntryPoint = object : ReportRoomEntryPoint { - override fun createNode(parentNode: Node, buildContext: BuildContext, roomId: RoomId) = lambdaError() - }, - changeRoomMemberRolesEntryPoint = object : ChangeRoomMemberRolesEntryPoint { - override fun builder(parentNode: Node, buildContext: BuildContext) = lambdaError() - }, + messagesEntryPoint = FakeMessagesEntryPoint(), + knockRequestsListEntryPoint = FakeKnockRequestsListEntryPoint(), + mediaViewerEntryPoint = FakeMediaViewerEntryPoint(), + mediaGalleryEntryPoint = FakeMediaGalleryEntryPoint(), + outgoingVerificationEntryPoint = FakeOutgoingVerificationEntryPoint(), + reportRoomEntryPoint = FakeReportRoomEntryPoint(), + changeRoomMemberRolesEntryPoint = FakeChangeRoomMemberRolesEntryPoint(), + rolesAndPermissionsEntryPoint = FakeRolesAndPermissionsEntryPoint(), + securityAndPrivacyEntryPoint = FakeSecurityAndPrivacyEntryPoint(), + roomDetailsEditEntryPoint = FakeRoomDetailsEditEntryPoint(), ) } val callback = object : RoomDetailsEntryPoint.Callback { - override fun onOpenGlobalNotificationSettings() = lambdaError() - override fun onOpenRoom(roomId: RoomId, serverNames: List) = lambdaError() - override fun onPermalinkClick(data: PermalinkData, pushToBackstack: Boolean) = lambdaError() - override fun onForwardedToSingleRoom(roomId: RoomId) = lambdaError() + override fun navigateToGlobalNotificationSettings() = lambdaError() + override fun navigateToRoom(roomId: RoomId, serverNames: List) = lambdaError() + override fun handlePermalinkClick(data: PermalinkData, pushToBackstack: Boolean) = lambdaError() + override fun startForwardEventFlow(eventId: EventId, fromPinnedEvents: Boolean) = lambdaError() } val params = RoomDetailsEntryPoint.Params( initialElement = RoomDetailsEntryPoint.InitialTarget.RoomDetails, ) - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .params(params) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + params = params, + callback = callback, + ) assertThat(result).isInstanceOf(RoomDetailsFlowNode::class.java) assertThat(result.plugins).contains(params) assertThat(result.plugins).contains(callback) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/MatrixRoomFixture.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/MatrixRoomFixture.kt index 1de627fd51..2d85744345 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/MatrixRoomFixture.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/MatrixRoomFixture.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,8 +13,8 @@ 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.UserId import io.element.android.libraries.matrix.api.room.RoomMember -import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.join.JoinRule +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.A_ROOM_ALIAS import io.element.android.libraries.matrix.test.A_ROOM_ID @@ -24,6 +25,7 @@ import io.element.android.libraries.matrix.test.notificationsettings.FakeNotific import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.aRoomInfo +import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.tests.testutils.lambda.lambdaError fun aRoom( @@ -34,6 +36,7 @@ fun aRoom( topic: String? = A_ROOM_TOPIC, avatarUrl: String? = AN_AVATAR_URL, canonicalAlias: RoomAlias? = A_ROOM_ALIAS, + roomPermissions: RoomPermissions = FakeRoomPermissions(), isEncrypted: Boolean = true, isPublic: Boolean = true, isDirect: Boolean = false, @@ -41,29 +44,20 @@ fun aRoom( activeMemberCount: Long = 1, joinedMemberCount: Long = 1, invitedMemberCount: Long = 0, - canInviteResult: (UserId) -> Result = { lambdaError() }, - canBanResult: (UserId) -> Result = { lambdaError() }, - canKickResult: (UserId) -> Result = { lambdaError() }, - canSendStateResult: (UserId, StateEventType) -> Result = { _, _ -> lambdaError() }, userDisplayNameResult: (UserId) -> Result = { lambdaError() }, userAvatarUrlResult: () -> Result = { lambdaError() }, - canUserJoinCallResult: (UserId) -> Result = { lambdaError() }, getUpdatedMemberResult: (UserId) -> Result = { lambdaError() }, userRoleResult: () -> Result = { lambdaError() }, setIsFavoriteResult: (Boolean) -> Result = { lambdaError() }, ) = FakeBaseRoom( sessionId = sessionId, roomId = roomId, - canInviteResult = canInviteResult, - canBanResult = canBanResult, - canKickResult = canKickResult, - canSendStateResult = canSendStateResult, userDisplayNameResult = userDisplayNameResult, userAvatarUrlResult = userAvatarUrlResult, - canUserJoinCallResult = canUserJoinCallResult, getUpdatedMemberResult = getUpdatedMemberResult, userRoleResult = userRoleResult, setIsFavoriteResult = setIsFavoriteResult, + roomPermissions = roomPermissions, initialRoomInfo = aRoomInfo( name = displayName, rawName = rawName, @@ -88,6 +82,7 @@ fun aJoinedRoom( topic: String? = A_ROOM_TOPIC, avatarUrl: String? = AN_AVATAR_URL, canonicalAlias: RoomAlias? = A_ROOM_ALIAS, + roomPermissions: RoomPermissions = FakeRoomPermissions(), isEncrypted: Boolean = true, isPublic: Boolean = true, isDirect: Boolean = false, @@ -96,17 +91,12 @@ fun aJoinedRoom( joinedMemberCount: Long = 1, invitedMemberCount: Long = 0, notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(), - canInviteResult: (UserId) -> Result = { lambdaError() }, - canBanResult: (UserId) -> Result = { lambdaError() }, - canKickResult: (UserId) -> Result = { lambdaError() }, - canSendStateResult: (UserId, StateEventType) -> Result = { _, _ -> lambdaError() }, userDisplayNameResult: (UserId) -> Result = { lambdaError() }, userAvatarUrlResult: () -> Result = { lambdaError() }, setNameResult: (String) -> Result = { lambdaError() }, setTopicResult: (String) -> Result = { lambdaError() }, updateAvatarResult: (String, ByteArray) -> Result = { _, _ -> lambdaError() }, removeAvatarResult: () -> Result = { lambdaError() }, - canUserJoinCallResult: (UserId) -> Result = { lambdaError() }, getUpdatedMemberResult: (UserId) -> Result = { lambdaError() }, userRoleResult: () -> Result = { lambdaError() }, kickUserResult: (UserId, String?) -> Result = { _, _ -> lambdaError() }, @@ -131,13 +121,9 @@ fun aJoinedRoom( baseRoom = aRoom( sessionId = sessionId, roomId = roomId, - canInviteResult = canInviteResult, - canBanResult = canBanResult, - canKickResult = canKickResult, - canSendStateResult = canSendStateResult, + roomPermissions = roomPermissions, userDisplayNameResult = userDisplayNameResult, userAvatarUrlResult = userAvatarUrlResult, - canUserJoinCallResult = canUserJoinCallResult, getUpdatedMemberResult = getUpdatedMemberResult, userRoleResult = userRoleResult, setIsFavoriteResult = setIsFavoriteResult, diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt index 695168aa16..5b1fd9474f 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -29,6 +30,7 @@ import io.element.android.libraries.matrix.api.room.RoomMembersState import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.join.JoinRule +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_NAME @@ -40,6 +42,7 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.room.aRoomInfo +import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.libraries.preferences.api.store.AppPreferencesStore import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore import io.element.android.services.analytics.api.AnalyticsService @@ -118,9 +121,7 @@ class RoomDetailsPresenterTest { @Test fun `present - initial state is created from initial room info`() = runTest { val room = aJoinedRoom( - canInviteResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canSendStateResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), ) val presenter = createRoomDetailsPresenter(room) presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) { @@ -147,9 +148,7 @@ class RoomDetailsPresenterTest { pinnedEventIds = listOf(AN_EVENT_ID), ) val room = aJoinedRoom( - canInviteResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canSendStateResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), ).apply { givenRoomInfo(roomInfo) } @@ -169,9 +168,7 @@ class RoomDetailsPresenterTest { fun `present - initial state with no room name`() = runTest { val room = aJoinedRoom( displayName = "", - canInviteResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canSendStateResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), ) val presenter = createRoomDetailsPresenter(room) presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) { @@ -187,9 +184,7 @@ class RoomDetailsPresenterTest { val myRoomMember = aRoomMember(A_SESSION_ID) val otherRoomMember = aRoomMember(A_USER_ID_2) val room = aJoinedRoom( - canInviteResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canSendStateResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), getUpdatedMemberResult = { userId -> when (userId) { A_SESSION_ID -> Result.success(myRoomMember) @@ -224,9 +219,9 @@ class RoomDetailsPresenterTest { @Test fun `present - initial state when user can invite others to room`() = runTest { val room = aJoinedRoom( - canInviteResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canSendStateResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions( + canInvite = true, + ), ) val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) { @@ -242,26 +237,9 @@ class RoomDetailsPresenterTest { @Test fun `present - initial state when user can not invite others to room`() = runTest { val room = aJoinedRoom( - canInviteResult = { Result.success(false) }, - canKickResult = { Result.success(false) }, - canBanResult = { Result.success(false) }, - canUserJoinCallResult = { Result.success(true) }, - canSendStateResult = { _, _ -> Result.success(true) }, - ) - val presenter = createRoomDetailsPresenter(room) - presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) { - assertThat(awaitItem().canInvite).isFalse() - - cancelAndIgnoreRemainingEvents() - } - } - - @Test - fun `present - initial state when canInvite errors`() = runTest { - val room = aJoinedRoom( - canInviteResult = { Result.failure(RuntimeException("Whoops")) }, - canUserJoinCallResult = { Result.success(true) }, - canSendStateResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions( + canInvite = false, + ), ) val presenter = createRoomDetailsPresenter(room) presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) { @@ -274,17 +252,11 @@ class RoomDetailsPresenterTest { @Test fun `present - initial state when user can edit one attribute`() = runTest { val room = aJoinedRoom( - canSendStateResult = { _, stateEventType -> - when (stateEventType) { - StateEventType.ROOM_TOPIC -> Result.success(true) - StateEventType.ROOM_NAME -> Result.success(false) - else -> Result.failure(RuntimeException("Whelp")) - } - }, - canBanResult = { Result.success(false) }, - canKickResult = { Result.success(false) }, - canInviteResult = { Result.success(false) }, - canUserJoinCallResult = { Result.success(true) }, + roomPermissions = roomPermissions( + canChangeName = true, + canChangeTopic = false, + canChangeAvatar = false, + ), ) val presenter = createRoomDetailsPresenter(room) presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) { @@ -302,18 +274,7 @@ class RoomDetailsPresenterTest { val myRoomMember = aRoomMember(A_SESSION_ID) val otherRoomMember = aRoomMember(A_USER_ID_2) val room = aJoinedRoom( - canSendStateResult = { _, stateEventType -> - when (stateEventType) { - StateEventType.ROOM_TOPIC, - StateEventType.ROOM_NAME, - StateEventType.ROOM_AVATAR -> Result.success(true) - else -> Result.failure(RuntimeException("Whelp")) - } - }, - canKickResult = { Result.success(false) }, - canBanResult = { Result.success(false) }, - canInviteResult = { Result.success(false) }, - canUserJoinCallResult = { Result.success(true) }, + roomPermissions = roomPermissions(), getUpdatedMemberResult = { userId -> when (userId) { A_SESSION_ID -> Result.success(myRoomMember) @@ -353,18 +314,9 @@ class RoomDetailsPresenterTest { val room = aJoinedRoom( isDirect = true, topic = null, - canSendStateResult = { _, stateEventType -> - when (stateEventType) { - StateEventType.ROOM_AVATAR, - StateEventType.ROOM_TOPIC, - StateEventType.ROOM_NAME -> Result.success(true) - else -> Result.failure(RuntimeException("Whelp")) - } - }, + roomPermissions = roomPermissions(), userDisplayNameResult = { Result.success(A_USER_NAME) }, userAvatarUrlResult = { Result.success(AN_AVATAR_URL) }, - canInviteResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, getUpdatedMemberResult = { userId -> when (userId) { A_SESSION_ID -> Result.success(myRoomMember) @@ -399,24 +351,11 @@ class RoomDetailsPresenterTest { @Test fun `present - initial state when user can edit all attributes`() = runTest { val room = aJoinedRoom( - canSendStateResult = { _, stateEventType -> - when (stateEventType) { - StateEventType.ROOM_TOPIC, - StateEventType.ROOM_NAME, - StateEventType.ROOM_AVATAR -> Result.success(true) - else -> Result.failure(RuntimeException("Whelp")) - } - }, - canKickResult = { - Result.success(false) - }, - canBanResult = { - Result.success(false) - }, - canInviteResult = { - Result.success(false) - }, - canUserJoinCallResult = { Result.success(true) }, + roomPermissions = roomPermissions( + canChangeAvatar = true, + canChangeName = true, + canChangeTopic = true, + ), ) val presenter = createRoomDetailsPresenter(room) presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) { @@ -432,24 +371,11 @@ class RoomDetailsPresenterTest { @Test fun `present - initial state when user can edit no attributes`() = runTest { val room = aJoinedRoom( - canSendStateResult = { _, stateEventType -> - when (stateEventType) { - StateEventType.ROOM_TOPIC, - StateEventType.ROOM_NAME, - StateEventType.ROOM_AVATAR -> Result.success(false) - else -> Result.failure(RuntimeException("Whelp")) - } - }, - canBanResult = { - Result.success(false) - }, - canKickResult = { - Result.success(false) - }, - canInviteResult = { - Result.success(false) - }, - canUserJoinCallResult = { Result.success(true) }, + roomPermissions = roomPermissions( + canChangeAvatar = false, + canChangeName = false, + canChangeTopic = false, + ), ) val presenter = createRoomDetailsPresenter(room) presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) { @@ -464,24 +390,9 @@ class RoomDetailsPresenterTest { fun `present - topic state is hidden when no topic and user has no permission`() = runTest { val room = aJoinedRoom( topic = null, - canSendStateResult = { _, stateEventType -> - when (stateEventType) { - StateEventType.ROOM_AVATAR, - StateEventType.ROOM_NAME -> Result.success(true) - StateEventType.ROOM_TOPIC -> Result.success(false) - else -> Result.failure(RuntimeException("Whelp")) - } - }, - canKickResult = { - Result.success(false) - }, - canBanResult = { - Result.success(false) - }, - canInviteResult = { - Result.success(false) - }, - canUserJoinCallResult = { Result.success(true) }, + roomPermissions = roomPermissions( + canChangeTopic = false + ), ) val presenter = createRoomDetailsPresenter(room) presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) { @@ -496,24 +407,7 @@ class RoomDetailsPresenterTest { fun `present - topic state is 'can add topic' when no topic and user has permission`() = runTest { val room = aJoinedRoom( topic = null, - canSendStateResult = { _, stateEventType -> - when (stateEventType) { - StateEventType.ROOM_AVATAR, - StateEventType.ROOM_TOPIC, - StateEventType.ROOM_NAME -> Result.success(true) - else -> Result.failure(RuntimeException("Whelp")) - } - }, - canKickResult = { - Result.success(false) - }, - canBanResult = { - Result.success(false) - }, - canInviteResult = { - Result.success(false) - }, - canUserJoinCallResult = { Result.success(true) }, + roomPermissions = roomPermissions(), ).apply { givenRoomInfo(aRoomInfo(topic = null)) } @@ -533,9 +427,7 @@ class RoomDetailsPresenterTest { fun `present - leave room event is passed on to leave room presenter`() = runTest { val leaveRoomEventRecorder = EventsRecorder() val room = aJoinedRoom( - canInviteResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canSendStateResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), ) val presenter = createRoomDetailsPresenter( room = room, @@ -554,9 +446,7 @@ class RoomDetailsPresenterTest { val notificationSettingsService = FakeNotificationSettingsService() val room = aJoinedRoom( notificationSettingsService = notificationSettingsService, - canInviteResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canSendStateResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), ) val presenter = createRoomDetailsPresenter( room = room, @@ -583,9 +473,7 @@ class RoomDetailsPresenterTest { FakeNotificationSettingsService(initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) val room = aJoinedRoom( notificationSettingsService = notificationSettingsService, - canInviteResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canSendStateResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), ) val presenter = createRoomDetailsPresenter( room = room, @@ -611,9 +499,7 @@ class RoomDetailsPresenterTest { ) val room = aJoinedRoom( notificationSettingsService = notificationSettingsService, - canInviteResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canSendStateResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), ) val presenter = createRoomDetailsPresenter( room = room, @@ -636,9 +522,7 @@ class RoomDetailsPresenterTest { val setIsFavoriteResult = lambdaRecorder> { _ -> Result.success(Unit) } val room = aJoinedRoom( setIsFavoriteResult = setIsFavoriteResult, - canInviteResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canSendStateResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), ) val analyticsService = FakeAnalyticsService() val presenter = @@ -664,9 +548,7 @@ class RoomDetailsPresenterTest { @Test fun `present - changes in room info updates the is favorite flag`() = runTest { val room = aJoinedRoom( - canInviteResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canSendStateResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), ) val presenter = createRoomDetailsPresenter(room = room) presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) { @@ -685,9 +567,7 @@ class RoomDetailsPresenterTest { @Test fun `present - show knock requests`() = runTest { val room = aJoinedRoom( - canInviteResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canSendStateResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), joinRule = JoinRule.Knock, ) val featureFlagService = FakeFeatureFlagService( @@ -711,18 +591,12 @@ class RoomDetailsPresenterTest { @Test fun `present - show security and privacy`() = runTest { val room = aJoinedRoom( - canInviteResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canSendStateResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), ) val featureFlagService = FakeFeatureFlagService() val presenter = createRoomDetailsPresenter(room = room, featureFlagService = featureFlagService) presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) { skipItems(1) - with(awaitItem()) { - assertThat(canShowSecurityAndPrivacy).isFalse() - } - featureFlagService.setFeatureEnabled(FeatureFlags.Knock, true) with(awaitItem()) { assertThat(canShowSecurityAndPrivacy).isTrue() } @@ -732,9 +606,7 @@ class RoomDetailsPresenterTest { @Test fun `present - show debug info`() = runTest { val room = aJoinedRoom( - canInviteResult = { Result.success(true) }, - canUserJoinCallResult = { Result.success(true) }, - canSendStateResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), ) val inMemoryAppPreferencesStore = InMemoryAppPreferencesStore( isDeveloperModeEnabled = true, @@ -747,4 +619,41 @@ class RoomDetailsPresenterTest { } } } + + private fun roomPermissions( + canInvite: Boolean = true, + canKick: Boolean = true, + canBan: Boolean = true, + canRedactOther: Boolean = true, + canRedactOwn: Boolean = true, + canChangeRoomAccess: Boolean = true, + canChangeHistoryVisibility: Boolean = true, + canChangeEncryption: Boolean = true, + canChangeRoomVisibility: Boolean = true, + canChangeName: Boolean = true, + canChangeTopic: Boolean = true, + canChangeAvatar: Boolean = true, + canChangePowerLevels: Boolean = true, + ): RoomPermissions { + return FakeRoomPermissions( + canInvite = canInvite, + canKick = canKick, + canBan = canBan, + canRedactOther = canRedactOther, + canRedactOwn = canRedactOwn, + canSendState = { eventType -> + when (eventType) { + StateEventType.RoomJoinRules -> canChangeRoomAccess + StateEventType.RoomHistoryVisibility -> canChangeHistoryVisibility + StateEventType.RoomEncryption -> canChangeEncryption + StateEventType.RoomCanonicalAlias -> canChangeRoomVisibility + StateEventType.RoomAvatar -> canChangeAvatar + StateEventType.RoomName -> canChangeName + StateEventType.RoomTopic -> canChangeTopic + StateEventType.RoomPowerLevels -> canChangePowerLevels + else -> lambdaError() + } + } + ) + } } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateTest.kt index 2a3b41b61b..68889c1551 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt index 3861a20422..b31fb32e8b 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt index 03555fb967..f1cab1524a 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt @@ -1,42 +1,34 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.roomdetails.impl.members -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.roommembermoderation.api.RoomMemberModerationState 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.BaseRoom import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.RoomMembersState +import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.aRoomInfo +import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.tests.testutils.WarmUpRule -import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.test import io.element.android.tests.testutils.testCoroutineDispatchers -import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.time.withTimeout -import kotlinx.coroutines.withTimeout import org.junit.Rule import org.junit.Test -import kotlin.time.Duration.Companion.seconds @ExperimentalCoroutinesApi class RoomMemberListPresenterTest { @@ -44,176 +36,131 @@ class RoomMemberListPresenterTest { val warmUpRule = WarmUpRule() @Test - fun `member loading is done automatically on start, but is async`() = runTest { - val room = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - updateMembersResult = { Result.success(Unit) }, - canInviteResult = { Result.success(true) } - ).apply { - // Needed to avoid discarding the loaded members as a partial and invalid result - givenRoomInfo(aRoomInfo(joinedMembersCount = 2)) - } - ) - val presenter = createPresenter(joinedRoom = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + fun `initial state is loading`() = runTest { + val presenter = createPresenter() + presenter.test { skipItems(1) val initialState = awaitItem() - assertThat(initialState.roomMembers.isLoading()).isTrue() + assertThat(initialState.filteredRoomMembers.isLoading()).isTrue() assertThat(initialState.searchQuery).isEmpty() - assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java) - assertThat(initialState.isSearchActive).isFalse() - room.givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - // Skip item while the new members state is processed - skipItems(1) - val loadedMembersState = awaitItem() - assertThat(loadedMembersState.roomMembers.isLoading()).isFalse() - assertThat(loadedMembersState.roomMembers.dataOrNull()?.invited) - .isEqualTo(listOf(RoomMemberWithIdentityState(aVictor(), null), RoomMemberWithIdentityState(aWalter(), null))) - assertThat(loadedMembersState.roomMembers.dataOrNull()?.joined).isNotEmpty() + assertThat(initialState.selectedSection).isEqualTo(SelectedSection.MEMBERS) } } @Test - fun `member loading is done automatically when RoomInfo's activeMemberCount changes`() = runTest { - val reloadMembersMutex = Mutex() - val updateMembersLambda = lambdaRecorder { - if (reloadMembersMutex.isLocked) { - reloadMembersMutex.unlock() + fun `hide banned section when there is no banned users`() = runTest { + val allRoomMembers = aRoomMemberList() + val noBannedMembers = allRoomMembers + .filterNot { it.membership == RoomMembershipState.BAN } + .toImmutableList() + val room = createFakeJoinedRoom() + .apply { + givenRoomMembersState(RoomMembersState.Ready(allRoomMembers)) } - } - val room = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - updateMembersResult = updateMembersLambda, - canInviteResult = { Result.success(true) } - ).apply { - // Needed to avoid discarding the loaded members as a partial and invalid result - givenRoomInfo(aRoomInfo(joinedMembersCount = 2)) - } - ) - val presenter = createPresenter(joinedRoom = room) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) - val initialState = awaitItem() - assertThat(initialState.roomMembers.isLoading()).isTrue() - room.givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - // Skip item while the new members state is processed - skipItems(1) - val loadedMembersState = awaitItem() - assertThat(loadedMembersState.roomMembers.isLoading()).isFalse() - assertThat(loadedMembersState.roomMembers.dataOrNull()?.joined).isNotEmpty() - - // Assert no events are emitted only with that change - expectNoEvents() - - // This will only progress if the `Room.updateMembers()` function is called, triggered by the RoomInfo change - withTimeout(10.seconds) { - reloadMembersMutex.withLock { - launch { room.givenRoomInfo(aRoomInfo(activeMembersCount = 0L)) } - } - } - - // Update the room members state as `Room.updateMembers()` would have done with the actual implementation - room.givenRoomMembersState(RoomMembersState.Ready(persistentListOf())) - // Wait for another update - skipItems(1) - // The members should be reloaded now - assertThat(awaitItem().roomMembers.dataOrNull()?.joined).isEmpty() - } - } - - @Test - fun `open search`() = runTest { val presenter = createPresenter( - joinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - updateMembersResult = { Result.success(Unit) }, - canInviteResult = { Result.success(true) } - ) - ) + joinedRoom = room, + roomMemberModerationState = aRoomMemberModerationState(canBan = true), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val loadedState = awaitItem() - loadedState.eventSink(RoomMemberListEvents.OnSearchActiveChanged(true)) + assertThat(loadedState.showBannedSection).isTrue() + loadedState.eventSink(RoomMemberListEvents.ChangeSelectedSection(SelectedSection.BANNED)) + val bannedSectionState = awaitItem() + assertThat(bannedSectionState.selectedSection).isEqualTo(SelectedSection.BANNED) + // Now update the room members to have no banned users + room.givenRoomMembersState(RoomMembersState.Ready(noBannedMembers)) skipItems(1) - val searchActiveState = awaitItem() - assertThat(searchActiveState.isSearchActive).isTrue() + val noBannedMembersState = awaitItem() + assertThat(noBannedMembersState.showBannedSection).isFalse() + skipItems(1) + val finalState = awaitItem() + assertThat(finalState.selectedSection).isEqualTo(SelectedSection.MEMBERS) + } + } + + @Test + fun `member loading is done automatically on start, but is async`() = runTest { + val room = createFakeJoinedRoom() + val presenter = createPresenter(joinedRoom = room) + presenter.test { + skipItems(1) + val initialState = awaitItem() + assertThat(initialState.filteredRoomMembers.isLoading()).isTrue() + assertThat(initialState.searchQuery).isEmpty() + room.givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) + // Skip items while the new members state is processed + skipItems(2) + val loadedState = awaitItem() + val loadedRoomMembers = loadedState.filteredRoomMembers.dataOrNull()!! + assertThat(loadedRoomMembers.joined).isNotEmpty() + assertThat(loadedRoomMembers.banned).isNotEmpty() + assertThat(loadedRoomMembers.invited).isNotEmpty() + assertThat(loadedRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse() + assertThat(loadedRoomMembers.isEmpty(SelectedSection.BANNED)).isFalse() } } @Test fun `search for something which is not found`() = runTest { - val presenter = createPresenter( - joinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - updateMembersResult = { Result.success(Unit) }, - canInviteResult = { Result.success(true) } - ) - ) - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val room = createFakeJoinedRoom().apply { + givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) + } + val presenter = createPresenter(joinedRoom = room) + presenter.test { skipItems(1) val loadedState = awaitItem() - loadedState.eventSink(RoomMemberListEvents.OnSearchActiveChanged(true)) - val searchActiveState = awaitItem() - searchActiveState.eventSink(RoomMemberListEvents.UpdateSearchQuery("something")) - skipItems(1) + val loadedRoomMembers = loadedState.filteredRoomMembers.dataOrNull()!! + assertThat(loadedRoomMembers.joined).isNotEmpty() + assertThat(loadedRoomMembers.banned).isNotEmpty() + assertThat(loadedRoomMembers.invited).isNotEmpty() + assertThat(loadedRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse() + assertThat(loadedRoomMembers.isEmpty(SelectedSection.BANNED)).isFalse() + loadedState.eventSink(RoomMemberListEvents.UpdateSearchQuery("something")) val searchQueryUpdatedState = awaitItem() assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("something") val searchSearchResultDelivered = awaitItem() - assertThat(searchSearchResultDelivered.searchResults).isInstanceOf(SearchBarResultState.NoResultsFound::class.java) + val emptyRoomMembers = searchSearchResultDelivered.filteredRoomMembers.dataOrNull()!! + assertThat(emptyRoomMembers.joined).isEmpty() + assertThat(emptyRoomMembers.banned).isEmpty() + assertThat(emptyRoomMembers.invited).isEmpty() + assertThat(emptyRoomMembers.isEmpty(SelectedSection.MEMBERS)).isTrue() + assertThat(emptyRoomMembers.isEmpty(SelectedSection.BANNED)).isTrue() } } @Test fun `search for something which is found`() = runTest { - val presenter = createPresenter( - joinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - updateMembersResult = { Result.success(Unit) }, - canInviteResult = { Result.success(true) } - ) - ) - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val room = createFakeJoinedRoom().apply { + givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) + } + val presenter = createPresenter(joinedRoom = room) + presenter.test { skipItems(1) val loadedState = awaitItem() - loadedState.eventSink(RoomMemberListEvents.OnSearchActiveChanged(true)) - val searchActiveState = awaitItem() - searchActiveState.eventSink(RoomMemberListEvents.UpdateSearchQuery("Alice")) - skipItems(1) + val loadedRoomMembers = loadedState.filteredRoomMembers.dataOrNull()!! + assertThat(loadedRoomMembers.joined).isNotEmpty() + assertThat(loadedRoomMembers.banned).isNotEmpty() + assertThat(loadedRoomMembers.invited).isNotEmpty() + assertThat(loadedRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse() + assertThat(loadedRoomMembers.isEmpty(SelectedSection.BANNED)).isFalse() + loadedState.eventSink(RoomMemberListEvents.UpdateSearchQuery("alice")) val searchQueryUpdatedState = awaitItem() - assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("Alice") + assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("alice") val searchSearchResultDelivered = awaitItem() - assertThat(searchSearchResultDelivered.searchResults).isInstanceOf(SearchBarResultState.Results::class.java) - assertThat((searchSearchResultDelivered.searchResults as SearchBarResultState.Results).results.dataOrNull()!!.joined.first().roomMember.displayName) - .isEqualTo("Alice") + val emptyRoomMembers = searchSearchResultDelivered.filteredRoomMembers.dataOrNull()!! + assertThat(emptyRoomMembers.joined).isNotEmpty() + assertThat(emptyRoomMembers.banned).isEmpty() + assertThat(emptyRoomMembers.invited).isEmpty() + assertThat(emptyRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse() + assertThat(emptyRoomMembers.isEmpty(SelectedSection.BANNED)).isTrue() } } @Test fun `present - asynchronously sets canInvite when user has correct power level`() = runTest { - val presenter = createPresenter( - joinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - canInviteResult = { Result.success(true) }, - updateMembersResult = { Result.success(Unit) } - ) - ) - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val presenter = createPresenter() + presenter.test { skipItems(1) val loadedState = awaitItem() assertThat(loadedState.canInvite).isTrue() @@ -223,89 +170,56 @@ class RoomMemberListPresenterTest { @Test fun `present - asynchronously sets canInvite when user does not have correct power level`() = runTest { val presenter = createPresenter( - joinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - canInviteResult = { Result.success(false) }, - updateMembersResult = { Result.success(Unit) } - ) + joinedRoom = createFakeJoinedRoom( + canInvite = false, ) ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) + presenter.test { val loadedState = awaitItem() assertThat(loadedState.canInvite).isFalse() } } @Test - fun `present - asynchronously sets canInvite when power level check fails`() = runTest { + fun `present - RoomMemberSelected will open the moderation options`() = runTest { val presenter = createPresenter( - joinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - canInviteResult = { Result.failure(RuntimeException("Eek")) }, - updateMembersResult = { Result.success(Unit) } - ) - ) + roomMemberModerationState = aRoomMemberModerationState(canBan = true, canKick = true) ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) - val loadedState = awaitItem() - assertThat(loadedState.canInvite).isFalse() - } - } - - @Test - fun `present - RoomMemberSelected will open the moderation options when target user is not banned`() = runTest { - val roomMemberModerationPresenter = Presenter { - aRoomMemberModerationState(canBan = true, canKick = true) - } - val presenter = createPresenter( - roomMemberModerationPresenter = roomMemberModerationPresenter, - joinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - updateMembersResult = { Result.success(Unit) }, - canInviteResult = { Result.success(true) } - ) - ) - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - skipItems(1) - awaitItem().eventSink(RoomMemberListEvents.RoomMemberSelected(aVictor())) + awaitItem().eventSink(RoomMemberListEvents.RoomMemberSelected(anInvitedVictor())) } } } -@ExperimentalCoroutinesApi -private fun TestScope.createDataSource( - room: BaseRoom = FakeBaseRoom().apply { - givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList())) - }, - coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers() -) = RoomMemberListDataSource(room, coroutineDispatchers) +private fun createFakeJoinedRoom( + updateMembersResult: () -> Unit = { }, + canInvite: Boolean = true, +): FakeJoinedRoom { + return FakeJoinedRoom( + baseRoom = FakeBaseRoom( + updateMembersResult = updateMembersResult, + roomPermissions = FakeRoomPermissions( + canInvite = canInvite, + ), + ).apply { + // Needed to avoid discarding the loaded members as a partial and invalid result + givenRoomInfo(aRoomInfo(joinedMembersCount = 2)) + } + ) +} @ExperimentalCoroutinesApi private fun TestScope.createPresenter( coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true), - joinedRoom: JoinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - updateMembersResult = { Result.success(Unit) } - ) - ), - roomMemberListDataSource: RoomMemberListDataSource = createDataSource(coroutineDispatchers = coroutineDispatchers), + joinedRoom: JoinedRoom = createFakeJoinedRoom(), encryptedService: FakeEncryptionService = FakeEncryptionService(), - roomMemberModerationPresenter: Presenter = Presenter { - aRoomMemberModerationState() - }, + roomMemberModerationState: RoomMemberModerationState = aRoomMemberModerationState(), ) = RoomMemberListPresenter( room = joinedRoom, - roomMemberListDataSource = roomMemberListDataSource, coroutineDispatchers = coroutineDispatchers, - roomMembersModerationPresenter = roomMemberModerationPresenter, + roomMembersModerationPresenter = Presenter { + roomMemberModerationState + }, encryptionService = encryptedService, ) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt index 73859b712f..4dbf21c392 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenterTest.kt index 308ad94379..c14dca9497 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetailsedit/api/build.gradle.kts b/features/roomdetailsedit/api/build.gradle.kts new file mode 100644 index 0000000000..0afaad72a6 --- /dev/null +++ b/features/roomdetailsedit/api/build.gradle.kts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-library") + id("kotlin-parcelize") +} + +android { + namespace = "io.element.android.features.roomdetailsedit.api" +} + +dependencies { + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) +} diff --git a/features/roomdetailsedit/api/src/main/kotlin/io/element/android/features/roomdetailsedit/api/RoomDetailsEditEntryPoint.kt b/features/roomdetailsedit/api/src/main/kotlin/io/element/android/features/roomdetailsedit/api/RoomDetailsEditEntryPoint.kt new file mode 100644 index 0000000000..1dad365646 --- /dev/null +++ b/features/roomdetailsedit/api/src/main/kotlin/io/element/android/features/roomdetailsedit/api/RoomDetailsEditEntryPoint.kt @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.roomdetailsedit.api + +import io.element.android.libraries.architecture.SimpleFeatureEntryPoint + +fun interface RoomDetailsEditEntryPoint : SimpleFeatureEntryPoint diff --git a/features/roomdetailsedit/api/src/main/kotlin/io/element/android/features/roomdetailsedit/api/RoomDetailsEditPermissions.kt b/features/roomdetailsedit/api/src/main/kotlin/io/element/android/features/roomdetailsedit/api/RoomDetailsEditPermissions.kt new file mode 100644 index 0000000000..8ac2ea5022 --- /dev/null +++ b/features/roomdetailsedit/api/src/main/kotlin/io/element/android/features/roomdetailsedit/api/RoomDetailsEditPermissions.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.roomdetailsedit.api + +import io.element.android.libraries.matrix.api.room.StateEventType +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions + +data class RoomDetailsEditPermissions( + val canEditName: Boolean, + val canEditTopic: Boolean, + val canEditAvatar: Boolean, +) { + val hasAny = canEditName || + canEditTopic || + canEditAvatar + + companion object { + val DEFAULT = RoomDetailsEditPermissions( + canEditName = false, + canEditTopic = false, + canEditAvatar = false, + ) + } +} + +fun RoomPermissions.roomDetailsEditPermissions(): RoomDetailsEditPermissions { + return RoomDetailsEditPermissions( + canEditName = canOwnUserSendState(StateEventType.RoomName), + canEditTopic = canOwnUserSendState(StateEventType.RoomTopic), + canEditAvatar = canOwnUserSendState(StateEventType.RoomAvatar), + ) +} diff --git a/features/roomdetailsedit/impl/build.gradle.kts b/features/roomdetailsedit/impl/build.gradle.kts new file mode 100644 index 0000000000..6b1d886abe --- /dev/null +++ b/features/roomdetailsedit/impl/build.gradle.kts @@ -0,0 +1,57 @@ +import extension.setupDependencyInjection +import extension.testCommonDependencies + +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023, 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-compose-library") + id("kotlin-parcelize") +} + +android { + namespace = "io.element.android.features.roomdetailsedit.impl" + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } +} + +setupDependencyInjection() + +dependencies { + implementation(projects.libraries.core) + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.matrixui) + implementation(projects.libraries.designsystem) + implementation(projects.libraries.uiStrings) + implementation(projects.libraries.androidutils) + implementation(projects.libraries.mediapickers.api) + implementation(projects.libraries.mediaupload.api) + implementation(projects.libraries.mediaviewer.api) + implementation(projects.libraries.featureflag.api) + implementation(projects.libraries.permissions.api) + implementation(projects.libraries.preferences.api) + implementation(projects.services.analytics.api) + implementation(projects.libraries.testtags) + api(projects.features.roomdetailsedit.api) + api(projects.services.apperror.api) + implementation(libs.coil.compose) + + testCommonDependencies(libs, true) + testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.mediaupload.test) + testImplementation(projects.libraries.mediapickers.test) + testImplementation(projects.libraries.mediaviewer.test) + testImplementation(projects.libraries.permissions.test) + testImplementation(projects.libraries.preferences.test) + testImplementation(projects.libraries.featureflag.test) + testImplementation(projects.services.analytics.test) +} diff --git a/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/DefaultRoomDetailsEditEntryPoint.kt b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/DefaultRoomDetailsEditEntryPoint.kt new file mode 100644 index 0000000000..d928a0238c --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/DefaultRoomDetailsEditEntryPoint.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.roomdetailsedit.impl + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import io.element.android.features.roomdetailsedit.api.RoomDetailsEditEntryPoint +import io.element.android.libraries.architecture.createNode + +@ContributesBinding(AppScope::class) +class DefaultRoomDetailsEditEntryPoint : RoomDetailsEditEntryPoint { + override fun createNode(parentNode: Node, buildContext: BuildContext): Node { + return parentNode.createNode(buildContext) + } +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditEvents.kt b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditEvent.kt similarity index 51% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditEvents.kt rename to features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditEvent.kt index 1becbe9e5c..40f123401c 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditEvents.kt +++ b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditEvent.kt @@ -1,18 +1,20 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.edit +package io.element.android.features.roomdetailsedit.impl import io.element.android.libraries.matrix.ui.media.AvatarAction -sealed interface RoomDetailsEditEvents { - data class HandleAvatarAction(val action: AvatarAction) : RoomDetailsEditEvents - data class UpdateRoomName(val name: String) : RoomDetailsEditEvents - data class UpdateRoomTopic(val topic: String) : RoomDetailsEditEvents - data object Save : RoomDetailsEditEvents - data object CancelSaveChanges : RoomDetailsEditEvents +sealed interface RoomDetailsEditEvent { + data class HandleAvatarAction(val action: AvatarAction) : RoomDetailsEditEvent + data class UpdateRoomName(val name: String) : RoomDetailsEditEvent + data class UpdateRoomTopic(val topic: String) : RoomDetailsEditEvent + data object OnBackPress : RoomDetailsEditEvent + data object Save : RoomDetailsEditEvent + data object CloseDialog : RoomDetailsEditEvent } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditNode.kt b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditNode.kt similarity index 87% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditNode.kt rename to features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditNode.kt index 8143f1848f..541a36c91a 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditNode.kt +++ b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditNode.kt @@ -1,11 +1,12 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.edit +package io.element.android.features.roomdetailsedit.impl import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -41,8 +42,7 @@ class RoomDetailsEditNode( val state = presenter.present() RoomDetailsEditView( state = state, - onBackClick = ::navigateUp, - onRoomEditSuccess = ::navigateUp, + onDone = ::navigateUp, modifier = modifier, ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenter.kt b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditPresenter.kt similarity index 78% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenter.kt rename to features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditPresenter.kt index 2520d0d29c..24f8f49e81 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenter.kt +++ b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditPresenter.kt @@ -1,12 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.edit +package io.element.android.features.roomdetailsedit.impl +import android.Manifest import android.net.Uri import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -21,6 +23,8 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.core.net.toUri import dev.zacsweers.metro.Inject +import io.element.android.features.roomdetailsedit.api.RoomDetailsEditPermissions +import io.element.android.features.roomdetailsedit.api.roomDetailsEditPermissions import io.element.android.libraries.androidutils.file.TemporaryUriDeleter import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter @@ -28,16 +32,12 @@ import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.matrix.api.room.JoinedRoom -import io.element.android.libraries.matrix.api.room.StateEventType -import io.element.android.libraries.matrix.api.room.powerlevels.canSendState +import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState import io.element.android.libraries.matrix.ui.media.AvatarAction -import io.element.android.libraries.matrix.ui.room.avatarUrl -import io.element.android.libraries.matrix.ui.room.rawName -import io.element.android.libraries.matrix.ui.room.topic import io.element.android.libraries.mediapickers.api.PickerProvider import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider import io.element.android.libraries.mediaupload.api.MediaPreProcessor -import io.element.android.libraries.permissions.api.PermissionsEvents +import io.element.android.libraries.permissions.api.PermissionsEvent import io.element.android.libraries.permissions.api.PermissionsPresenter import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope @@ -53,29 +53,28 @@ class RoomDetailsEditPresenter( permissionsPresenterFactory: PermissionsPresenter.Factory, private val mediaOptimizationConfigProvider: MediaOptimizationConfigProvider, ) : Presenter { - private val cameraPermissionPresenter = permissionsPresenterFactory.create(android.Manifest.permission.CAMERA) + private val cameraPermissionPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA) private var pendingPermissionRequest = false @Composable override fun present(): RoomDetailsEditState { val cameraPermissionState = cameraPermissionPresenter.present() - val roomSyncUpdateFlow = room.syncUpdateFlow.collectAsState() - - val roomAvatarUri = room.avatarUrl()?.toUri() - var roomAvatarUriEdited by rememberSaveable { mutableStateOf(null) } + val roomInfo by room.roomInfoFlow.collectAsState() + val roomAvatarUri = roomInfo.avatarUrl + var roomAvatarUriEdited by rememberSaveable { mutableStateOf(null) } LaunchedEffect(roomAvatarUri) { // Every time the roomAvatar change (from sync), we can set the new avatar. - temporaryUriDeleter.delete(roomAvatarUriEdited) + temporaryUriDeleter.delete(roomAvatarUriEdited?.toUri()) roomAvatarUriEdited = roomAvatarUri } - val roomRawNameTrimmed = room.rawName().orEmpty().trim() + val roomRawNameTrimmed = roomInfo.rawName.orEmpty().trim() var roomRawNameEdited by rememberSaveable { mutableStateOf("") } LaunchedEffect(roomRawNameTrimmed) { // Every time the rawName change (from sync), we can set the new name. roomRawNameEdited = roomRawNameTrimmed } - val roomTopicTrimmed = room.topic().orEmpty().trim() + val roomTopicTrimmed = roomInfo.topic.orEmpty().trim() var roomTopicEdited by rememberSaveable { mutableStateOf("") } LaunchedEffect(roomTopicTrimmed) { // Every time the topic change (from sync), we can set the new topic. @@ -94,29 +93,23 @@ class RoomDetailsEditPresenter( } } - var canChangeName by remember { mutableStateOf(false) } - var canChangeTopic by remember { mutableStateOf(false) } - var canChangeAvatar by remember { mutableStateOf(false) } - - LaunchedEffect(roomSyncUpdateFlow.value) { - canChangeName = room.canSendState(StateEventType.ROOM_NAME).getOrElse { false } - canChangeTopic = room.canSendState(StateEventType.ROOM_TOPIC).getOrElse { false } - canChangeAvatar = room.canSendState(StateEventType.ROOM_AVATAR).getOrElse { false } + val permissions by room.permissionsAsState(RoomDetailsEditPermissions.DEFAULT) { perms -> + perms.roomDetailsEditPermissions() } val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker( onResult = { uri -> if (uri != null) { - temporaryUriDeleter.delete(roomAvatarUriEdited) - roomAvatarUriEdited = uri + temporaryUriDeleter.delete(roomAvatarUriEdited?.toUri()) + roomAvatarUriEdited = uri.toString() } } ) val galleryImagePicker = mediaPickerProvider.registerGalleryImagePicker( onResult = { uri -> if (uri != null) { - temporaryUriDeleter.delete(roomAvatarUriEdited) - roomAvatarUriEdited = uri + temporaryUriDeleter.delete(roomAvatarUriEdited?.toUri()) + roomAvatarUriEdited = uri.toString() } } ) @@ -140,52 +133,59 @@ class RoomDetailsEditPresenter( val saveAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val localCoroutineScope = rememberCoroutineScope() - fun handleEvents(event: RoomDetailsEditEvents) { + fun handleEvent(event: RoomDetailsEditEvent) { when (event) { - is RoomDetailsEditEvents.Save -> localCoroutineScope.saveChanges( + is RoomDetailsEditEvent.Save -> localCoroutineScope.saveChanges( currentNameTrimmed = roomRawNameTrimmed, newNameTrimmed = roomRawNameEdited.trim(), currentTopicTrimmed = roomTopicTrimmed, newTopicTrimmed = roomTopicEdited.trim(), - currentAvatar = roomAvatarUri, - newAvatarUri = roomAvatarUriEdited, + currentAvatar = roomAvatarUri?.toUri(), + newAvatarUri = roomAvatarUriEdited?.toUri(), action = saveAction, ) - is RoomDetailsEditEvents.HandleAvatarAction -> { + is RoomDetailsEditEvent.HandleAvatarAction -> { when (event.action) { AvatarAction.ChoosePhoto -> galleryImagePicker.launch() AvatarAction.TakePhoto -> if (cameraPermissionState.permissionGranted) { cameraPhotoPicker.launch() } else { pendingPermissionRequest = true - cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions) + cameraPermissionState.eventSink(PermissionsEvent.RequestPermissions) } AvatarAction.Remove -> { - temporaryUriDeleter.delete(roomAvatarUriEdited) + temporaryUriDeleter.delete(roomAvatarUriEdited?.toUri()) roomAvatarUriEdited = null } } } - is RoomDetailsEditEvents.UpdateRoomName -> roomRawNameEdited = event.name - is RoomDetailsEditEvents.UpdateRoomTopic -> roomTopicEdited = event.topic - RoomDetailsEditEvents.CancelSaveChanges -> saveAction.value = AsyncAction.Uninitialized + is RoomDetailsEditEvent.UpdateRoomName -> roomRawNameEdited = event.name + is RoomDetailsEditEvent.UpdateRoomTopic -> roomTopicEdited = event.topic + RoomDetailsEditEvent.CloseDialog -> saveAction.value = AsyncAction.Uninitialized + RoomDetailsEditEvent.OnBackPress -> if (saveButtonEnabled.not() || saveAction.value == AsyncAction.ConfirmingCancellation) { + // No changes to save or already confirming exit without saving + saveAction.value = AsyncAction.Success(Unit) + } else { + saveAction.value = AsyncAction.ConfirmingCancellation + } } } return RoomDetailsEditState( roomId = room.roomId, roomRawName = roomRawNameEdited, - canChangeName = canChangeName, + canChangeName = permissions.canEditName, roomTopic = roomTopicEdited, - canChangeTopic = canChangeTopic, + canChangeTopic = permissions.canEditTopic, roomAvatarUrl = roomAvatarUriEdited, - canChangeAvatar = canChangeAvatar, + canChangeAvatar = permissions.canEditAvatar, avatarActions = avatarActions, saveButtonEnabled = saveButtonEnabled, saveAction = saveAction.value, cameraPermissionState = cameraPermissionState, - eventSink = ::handleEvents, + isSpace = roomInfo.isSpace, + eventSink = ::handleEvent, ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditState.kt b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditState.kt similarity index 78% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditState.kt rename to features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditState.kt index f01011376c..35f088275f 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditState.kt +++ b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditState.kt @@ -1,13 +1,13 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.edit +package io.element.android.features.roomdetailsedit.impl -import android.net.Uri import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.ui.media.AvatarAction @@ -21,11 +21,12 @@ data class RoomDetailsEditState( val canChangeName: Boolean, val roomTopic: String, val canChangeTopic: Boolean, - val roomAvatarUrl: Uri?, + val roomAvatarUrl: String?, val canChangeAvatar: Boolean, val avatarActions: ImmutableList, val saveButtonEnabled: Boolean, val saveAction: AsyncAction, val cameraPermissionState: PermissionsState, - val eventSink: (RoomDetailsEditEvents) -> Unit + val isSpace: Boolean, + val eventSink: (RoomDetailsEditEvent) -> Unit ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditStateProvider.kt b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditStateProvider.kt similarity index 83% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditStateProvider.kt rename to features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditStateProvider.kt index 7ae59febc0..a6e2e3798b 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditStateProvider.kt +++ b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditStateProvider.kt @@ -1,15 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.edit +package io.element.android.features.roomdetailsedit.impl -import android.net.Uri import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import androidx.core.net.toUri import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.ui.media.AvatarAction @@ -23,11 +22,13 @@ open class RoomDetailsEditStateProvider : PreviewParameterProvider = emptyList(), saveButtonEnabled: Boolean = true, saveAction: AsyncAction = AsyncAction.Uninitialized, cameraPermissionState: PermissionsState = aPermissionsState(showDialog = false), - eventSink: (RoomDetailsEditEvents) -> Unit = {}, + isSpace: Boolean = false, + eventSink: (RoomDetailsEditEvent) -> Unit = {}, ) = RoomDetailsEditState( roomId = roomId, roomRawName = roomRawName, @@ -56,5 +58,6 @@ fun aRoomDetailsEditState( saveButtonEnabled = saveButtonEnabled, saveAction = saveAction, cameraPermissionState = cameraPermissionState, + isSpace = isSpace, eventSink = eventSink, ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditView.kt b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditView.kt similarity index 73% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditView.kt rename to features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditView.kt index 60082b23f1..aedb9bd16b 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditView.kt +++ b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditView.kt @@ -1,20 +1,21 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @file:OptIn(ExperimentalMaterial3Api::class) -package io.element.android.features.roomdetails.impl.edit +package io.element.android.features.roomdetailsedit.impl +import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions @@ -29,12 +30,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp -import io.element.android.features.roomdetails.impl.R +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarType import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.dialogs.SaveChangesDialog import io.element.android.libraries.designsystem.modifiers.clearFocusOnTap import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -50,8 +52,7 @@ import io.element.android.libraries.ui.strings.CommonStrings @Composable fun RoomDetailsEditView( state: RoomDetailsEditState, - onBackClick: () -> Unit, - onRoomEditSuccess: () -> Unit, + onDone: () -> Unit, modifier: Modifier = Modifier, ) { val focusManager = LocalFocusManager.current @@ -62,19 +63,28 @@ fun RoomDetailsEditView( isAvatarActionsSheetVisible.value = true } + BackHandler { + state.eventSink(RoomDetailsEditEvent.OnBackPress) + } Scaffold( modifier = modifier.clearFocusOnTap(focusManager), topBar = { TopAppBar( titleStr = stringResource(id = R.string.screen_room_details_edit_room_title), - navigationIcon = { BackButton(onClick = onBackClick) }, + navigationIcon = { + BackButton( + onClick = { + state.eventSink(RoomDetailsEditEvent.OnBackPress) + } + ) + }, actions = { TextButton( text = stringResource(CommonStrings.action_save), enabled = state.saveButtonEnabled, onClick = { focusManager.clearFocus() - state.eventSink(RoomDetailsEditEvents.Save) + state.eventSink(RoomDetailsEditEvent.Save) }, ) } @@ -85,7 +95,6 @@ fun RoomDetailsEditView( modifier = Modifier .padding(padding) .padding(horizontal = 16.dp) - .navigationBarsPadding() .imePadding() .verticalScroll(rememberScrollState()) ) { @@ -96,44 +105,51 @@ fun RoomDetailsEditView( displayName = state.roomRawName, avatarUrl = state.roomAvatarUrl, avatarSize = AvatarSize.EditRoomDetails, - avatarType = AvatarType.Room(), + avatarType = if (state.isSpace) { + AvatarType.Space() + } else { + AvatarType.Room() + }, + enabled = state.canChangeAvatar, onAvatarClick = ::onAvatarClick, modifier = Modifier.fillMaxWidth(), ) - Spacer(modifier = Modifier.height(60.dp)) + Spacer(modifier = Modifier.height(32.dp)) TextField( - label = stringResource(id = R.string.screen_room_details_room_name_label), + label = stringResource(id = CommonStrings.common_name), value = state.roomRawName, placeholder = stringResource(CommonStrings.common_room_name_placeholder), singleLine = true, readOnly = !state.canChangeName, - onValueChange = { state.eventSink(RoomDetailsEditEvents.UpdateRoomName(it)) }, + onValueChange = { state.eventSink(RoomDetailsEditEvent.UpdateRoomName(it)) }, ) - Spacer(modifier = Modifier.height(28.dp)) + Spacer(modifier = Modifier.height(32.dp)) TextField( label = stringResource(CommonStrings.common_topic), value = state.roomTopic, - placeholder = stringResource(CommonStrings.common_topic_placeholder), + placeholder = if (state.isSpace) { + stringResource(CommonStrings.common_space_topic_placeholder) + } else { + stringResource(CommonStrings.common_topic_placeholder) + }, maxLines = 10, readOnly = !state.canChangeTopic, - onValueChange = { state.eventSink(RoomDetailsEditEvents.UpdateRoomTopic(it)) }, + onValueChange = { state.eventSink(RoomDetailsEditEvent.UpdateRoomTopic(it)) }, keyboardOptions = KeyboardOptions( capitalization = KeyboardCapitalization.Sentences, ), ) } } - AvatarActionBottomSheet( actions = state.avatarActions, isVisible = isAvatarActionsSheetVisible.value, onDismiss = { isAvatarActionsSheetVisible.value = false }, - onSelectAction = { state.eventSink(RoomDetailsEditEvents.HandleAvatarAction(it)) } + onSelectAction = { state.eventSink(RoomDetailsEditEvent.HandleAvatarAction(it)) } ) - AsyncActionView( async = state.saveAction, progressDialog = { @@ -141,9 +157,18 @@ fun RoomDetailsEditView( progressText = stringResource(R.string.screen_room_details_updating_room), ) }, - onSuccess = { onRoomEditSuccess() }, + confirmationDialog = { + if (state.saveAction == AsyncAction.ConfirmingCancellation) { + SaveChangesDialog( + onSaveClick = { state.eventSink(RoomDetailsEditEvent.Save) }, + onDiscardClick = { state.eventSink(RoomDetailsEditEvent.OnBackPress) }, + onDismiss = { state.eventSink(RoomDetailsEditEvent.CloseDialog) } + ) + } + }, + onSuccess = { onDone() }, errorMessage = { stringResource(R.string.screen_room_details_edition_error) }, - onErrorDismiss = { state.eventSink(RoomDetailsEditEvents.CancelSaveChanges) } + onErrorDismiss = { state.eventSink(RoomDetailsEditEvent.CloseDialog) } ) PermissionsView( @@ -156,7 +181,6 @@ fun RoomDetailsEditView( internal fun RoomDetailsEditViewPreview(@PreviewParameter(RoomDetailsEditStateProvider::class) state: RoomDetailsEditState) = ElementPreview { RoomDetailsEditView( state = state, - onBackClick = {}, - onRoomEditSuccess = {}, + onDone = {}, ) } diff --git a/features/roomdetailsedit/impl/src/main/res/values-be/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-be/translations.xml new file mode 100644 index 0000000000..26537576b4 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-be/translations.xml @@ -0,0 +1,7 @@ + + + "Рэдагаваць пакой" + "Адбылася невядомая памылка, і інфармацыю нельга было змяніць." + "Немагчыма абнавіць пакой" + "Ідзе абнаўленне пакоя…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-bg/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-bg/translations.xml new file mode 100644 index 0000000000..787110dc79 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-bg/translations.xml @@ -0,0 +1,7 @@ + + + "Редактиране на стаята" + "Възникна неизвестна грешка и информацията не можа да бъде променена." + "Не може да се обнови стаята" + "Обновяване на стаята…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-cs/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..b2ea107300 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-cs/translations.xml @@ -0,0 +1,7 @@ + + + "Upravit podrobnosti" + "Došlo k neznámé chybě a informace nebylo možné změnit." + "Nelze aktualizovat místnost" + "Aktualizace místnosti…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-cy/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-cy/translations.xml new file mode 100644 index 0000000000..e13ddf6d91 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-cy/translations.xml @@ -0,0 +1,7 @@ + + + "Ystafell Golygu" + "Roedd gwall anhysbys ac nid oedd modd newid y manylion." + "Methu diweddaru\'r ystafell" + "Wrthi\'n diweddaru ystafell…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-da/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-da/translations.xml new file mode 100644 index 0000000000..d751b1da96 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-da/translations.xml @@ -0,0 +1,7 @@ + + + "Redigér detaljer" + "Der opstod en ukendt fejl, og oplysningerne kunne ikke ændres." + "Rummet kunne ikke opdateres" + "Opdaterer rum…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-de/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..1bc31f9f68 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,7 @@ + + + "Chat bearbeiten" + "Es ist ein unbekannter Fehler aufgetreten und die Informationen konnten nicht geändert werden." + "Chat kann nicht aktualisiert werden" + "Chat wird aktualisiert…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-el/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-el/translations.xml new file mode 100644 index 0000000000..c783ab1d86 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-el/translations.xml @@ -0,0 +1,7 @@ + + + "Επεξεργασία Αίθουσας" + "Υπήρξε ένα άγνωστο σφάλμα και οι πληροφορίες δεν μπορούσαν να αλλάξουν." + "Αδυναμία ενημέρωσης αίθουσας" + "Ενημέρωση αίθουσας…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-es/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..45e1d81df9 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,7 @@ + + + "Editar sala" + "Se ha producido un error desconocido y no se ha podido cambiar la información." + "No se puede actualizar la sala" + "Actualizando la sala…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-et/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-et/translations.xml new file mode 100644 index 0000000000..4c158d348a --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-et/translations.xml @@ -0,0 +1,7 @@ + + + "Muuda üksikasju" + "Tekkis tundmatu viga ja andmed jäid muutmata." + "Jututoa andmete muutmine ei õnnestu" + "Uuendame jututuba…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-eu/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-eu/translations.xml new file mode 100644 index 0000000000..000b5856bb --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-eu/translations.xml @@ -0,0 +1,7 @@ + + + "Editatu gela" + "Errore ezezaguna gertatu da eta ezin izan da informazioa aldatu." + "Ezin da gela eguneratu" + "Gela eguneratzen…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-fa/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-fa/translations.xml new file mode 100644 index 0000000000..bbbd63d171 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-fa/translations.xml @@ -0,0 +1,7 @@ + + + "ویرایش اتاق" + "خطایی ناشناخته رخ داد و اطّلاعات قابل تغییر نبودند." + "ناتوان در به‌روز رسانی اتاق" + "به‌روز کردن اتاق…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-fi/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-fi/translations.xml new file mode 100644 index 0000000000..eabdcf2ab1 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-fi/translations.xml @@ -0,0 +1,7 @@ + + + "Muokkaa tietoja" + "Tuntematon virhe tapahtui, eikä tietoja voitu muuttaa." + "Huoneen muokkaaminen ei onnistunut" + "Muokataan huonetta…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-fr/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-fr/translations.xml new file mode 100644 index 0000000000..bd0c4b6f02 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-fr/translations.xml @@ -0,0 +1,7 @@ + + + "Modifier les détails" + "Une erreur inconnue s’est produite et les informations n’ont pas pu être modifiées." + "Impossible de mettre à jour le salon" + "Mise à jour du salon…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-hr/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..7cd06d6bd8 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,7 @@ + + + "Uredi pojedinosti" + "Došlo je do nepoznate pogreške i podatci se nisu mogli promijeniti." + "Nije moguće ažurirati sobu" + "Ažuriranje pojedinosti…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-hu/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-hu/translations.xml new file mode 100644 index 0000000000..9685a9afbb --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-hu/translations.xml @@ -0,0 +1,7 @@ + + + "Részletek szerkesztése" + "Ismeretlen hiba történt, és az információkat nem lehetett megváltoztatni." + "Nem sikerült frissíteni a szobát" + "Szoba frissítése…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-in/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-in/translations.xml new file mode 100644 index 0000000000..14b3d68c74 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-in/translations.xml @@ -0,0 +1,7 @@ + + + "Sunting Ruangan" + "Terjadi kesalahan yang tidak diketahui dan informasinya tidak dapat diubah." + "Tidak dapat memperbarui ruangan" + "Memperbarui ruangan…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-it/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..7d28c1a3e2 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,7 @@ + + + "Modifica dettagli" + "Si è verificato un errore sconosciuto e non è stato possibile modificare le informazioni." + "Impossibile aggiornare la stanza" + "Aggiornamento della stanza…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-ka/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-ka/translations.xml new file mode 100644 index 0000000000..53ea28028b --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-ka/translations.xml @@ -0,0 +1,7 @@ + + + "ოთახის რედაქტირება" + "უცნობი შეცდომა მოხდა. ინფორმაციის შეცვლა ვერ მოხერხდა." + "ოთახის განახლება შეუძლებელია" + "ოთახის განახლება…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-ko/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-ko/translations.xml new file mode 100644 index 0000000000..ae51384737 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-ko/translations.xml @@ -0,0 +1,7 @@ + + + "방 편집" + "알 수 없는 오류가 발생하여 정보를 변경할 수 없습니다." + "방을 업데이트할 수 없습니다." + "방 업데이트 중…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-lt/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-lt/translations.xml new file mode 100644 index 0000000000..7e28eeb16c --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-lt/translations.xml @@ -0,0 +1,7 @@ + + + "Redaguoti kambarį" + "Įvyko nežinoma klaida ir informacijos pakeisti nepavyko." + "Nepavyko atnaujinti kambario" + "Atnaujinamas kambarys…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-nb/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-nb/translations.xml new file mode 100644 index 0000000000..e660c40e7b --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-nb/translations.xml @@ -0,0 +1,7 @@ + + + "Rediger detaljer" + "Det oppstod en ukjent feil, og informasjonen kunne ikke endres." + "Kan ikke oppdatere rommet" + "Oppdaterer rommet …" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-nl/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-nl/translations.xml new file mode 100644 index 0000000000..af6fae3a90 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-nl/translations.xml @@ -0,0 +1,7 @@ + + + "Kamer bewerken" + "Er is een onbekende fout opgetreden en de informatie kon niet worden gewijzigd." + "Kan kamer niet bijwerken" + "Kamer bijwerken…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-pl/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-pl/translations.xml new file mode 100644 index 0000000000..c676ff46ed --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-pl/translations.xml @@ -0,0 +1,7 @@ + + + "Edytuj pokój" + "Wystąpił nieznany błąd i nie można było zmienić informacji." + "Nie można zaktualizować pokoju" + "Aktualizuję pokój…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-pt-rBR/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-pt-rBR/translations.xml new file mode 100644 index 0000000000..e8e5dfc28d --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-pt-rBR/translations.xml @@ -0,0 +1,7 @@ + + + "Editar detalhes" + "Ocorreu um erro desconhecido e as informações não puderam ser alteradas." + "Não foi possível atualizar a sala" + "Atualizando a sala…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-pt/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-pt/translations.xml new file mode 100644 index 0000000000..25069318a5 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-pt/translations.xml @@ -0,0 +1,7 @@ + + + "Editar sala" + "Ocorreu um erro desconhecido e não foi possível alterar a informação." + "Não foi possível atualizar a sala" + "A atualizar sala…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-ro/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..401eeee575 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-ro/translations.xml @@ -0,0 +1,7 @@ + + + "Editați detaliile" + "A apărut o eroare la actualizarea detaliilor camerei" + "Nu s-a putut actualiza camera" + "Se actualizează camera…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-ru/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..91ce37e6c0 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-ru/translations.xml @@ -0,0 +1,7 @@ + + + "Редактировать комнату" + "Произошла неизвестная ошибка и информацию не удалось изменить." + "Не удалось обновить комнату" + "Обновление комнаты…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-sk/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..52e484f1a3 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-sk/translations.xml @@ -0,0 +1,7 @@ + + + "Upraviť podrobnosti" + "Vyskytla sa neznáma chyba a informácie nebolo možné zmeniť." + "Nepodarilo sa aktualizovať miestnosť" + "Aktualizácia miestnosti…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-sv/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-sv/translations.xml new file mode 100644 index 0000000000..176aed6b00 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-sv/translations.xml @@ -0,0 +1,7 @@ + + + "Redigera rummet" + "Ett okänt fel uppstod och informationen kunde inte ändras." + "Kunde inte uppdatera rummet" + "Uppdaterar rummet …" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-tr/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-tr/translations.xml new file mode 100644 index 0000000000..f55c55cfaf --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-tr/translations.xml @@ -0,0 +1,7 @@ + + + "Odayı Düzenle" + "Bilinmeyen bir hata oluştu ve bilgiler değiştirilemedi." + "Oda güncellenemiyor" + "Oda güncelleniyor…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-uk/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-uk/translations.xml new file mode 100644 index 0000000000..21a47cbd59 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-uk/translations.xml @@ -0,0 +1,7 @@ + + + "Редагувати кімнату" + "Сталася невідома помилка, й інформацію не вдалося змінити." + "Не вдалося оновити кімнату" + "Оновлення кімнати…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-ur/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-ur/translations.xml new file mode 100644 index 0000000000..e0fd0e01b5 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-ur/translations.xml @@ -0,0 +1,7 @@ + + + "کمرے میں ترمیم کریں" + "ایک نامعلوم خلل تھا اور معلومات تبدیل نہیں ہوسکی۔" + "کمرے کی تجدید کرنے سے قاصر" + "کمرے کی تجدید کر رہا ہے…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-uz/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-uz/translations.xml new file mode 100644 index 0000000000..cd11813116 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-uz/translations.xml @@ -0,0 +1,7 @@ + + + "Tafsilotlarni tahrirlash" + "Nomaʼlum xatolik yuz berdi va maʼlumotni oʻzgartirib boʻlmadi." + "Xonani yangilab bo‘lmadi" + "Xona yangilanmoqda…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-zh-rTW/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-zh-rTW/translations.xml new file mode 100644 index 0000000000..44955e96dd --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-zh-rTW/translations.xml @@ -0,0 +1,7 @@ + + + "編輯詳細資訊" + "發生未知錯誤,無法變更資訊。" + "無法更新聊天室" + "正在更新聊天室…" + diff --git a/features/roomdetailsedit/impl/src/main/res/values-zh/translations.xml b/features/roomdetailsedit/impl/src/main/res/values-zh/translations.xml new file mode 100644 index 0000000000..6b174fcb2c --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values-zh/translations.xml @@ -0,0 +1,7 @@ + + + "编辑聊天室" + "出现未知错误,无法更改信息。" + "无法更新聊天室" + "正在更新聊天室……" + diff --git a/features/roomdetailsedit/impl/src/main/res/values/localazy.xml b/features/roomdetailsedit/impl/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..c20e6f2b47 --- /dev/null +++ b/features/roomdetailsedit/impl/src/main/res/values/localazy.xml @@ -0,0 +1,7 @@ + + + "Edit details" + "There was an unknown error and the information couldn\'t be changed." + "Unable to update room" + "Updating details…" + diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenterTest.kt b/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditPresenterTest.kt similarity index 67% rename from features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenterTest.kt rename to features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditPresenterTest.kt index 996cbcfece..656cc92265 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenterTest.kt +++ b/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditPresenterTest.kt @@ -1,16 +1,15 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.edit +package io.element.android.features.roomdetailsedit.impl import android.net.Uri import app.cash.turbine.ReceiveTurbine import com.google.common.truth.Truth.assertThat -import io.element.android.features.roomdetails.impl.aJoinedRoom import io.element.android.libraries.androidutils.file.TemporaryUriDeleter import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.core.mimetype.MimeTypes @@ -19,6 +18,11 @@ import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.A_ROOM_NAME import io.element.android.libraries.matrix.test.A_ROOM_RAW_NAME +import io.element.android.libraries.matrix.test.A_ROOM_TOPIC +import io.element.android.libraries.matrix.test.room.FakeBaseRoom +import io.element.android.libraries.matrix.test.room.FakeJoinedRoom +import io.element.android.libraries.matrix.test.room.aRoomInfo +import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.libraries.matrix.ui.media.AvatarAction import io.element.android.libraries.mediapickers.test.FakePickerProvider import io.element.android.libraries.mediaupload.api.MediaUploadInfo @@ -31,6 +35,7 @@ import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.fake.FakeTemporaryUriDeleter import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.matching import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.test import io.mockk.every @@ -66,7 +71,9 @@ class RoomDetailsEditPresenterTest { mockkStatic(Uri::class) every { Uri.parse(AN_AVATAR_URL) } returns roomAvatarUri + every { roomAvatarUri.toString() } returns AN_AVATAR_URL every { Uri.parse(ANOTHER_AVATAR_URL) } returns anotherAvatarUri + every { anotherAvatarUri.toString() } returns ANOTHER_AVATAR_URL } @After @@ -96,7 +103,6 @@ class RoomDetailsEditPresenterTest { avatarUrl = AN_AVATAR_URL, displayName = A_ROOM_NAME, rawName = A_ROOM_RAW_NAME, - canSendStateResult = { _, _ -> Result.success(true) } ) val deleteCallback = lambdaRecorder {} val presenter = createRoomDetailsEditPresenter( @@ -107,7 +113,7 @@ class RoomDetailsEditPresenterTest { val initialState = awaitFirstItem() assertThat(initialState.roomId).isEqualTo(room.roomId) assertThat(initialState.roomRawName).isEqualTo(A_ROOM_RAW_NAME) - assertThat(initialState.roomAvatarUrl).isEqualTo(roomAvatarUri) + assertThat(initialState.roomAvatarUrl).isEqualTo(AN_AVATAR_URL) assertThat(initialState.roomTopic).isEqualTo(room.info().topic.orEmpty()) assertThat(initialState.avatarActions).containsExactly( AvatarAction.ChoosePhoto, @@ -122,15 +128,14 @@ class RoomDetailsEditPresenterTest { @Test fun `present - sets canChangeName if user has permission`() = runTest { val room = aJoinedRoom( - avatarUrl = AN_AVATAR_URL, - canSendStateResult = { _, stateEventType -> + canSendState = { stateEventType -> when (stateEventType) { - StateEventType.ROOM_NAME -> Result.success(true) - StateEventType.ROOM_AVATAR -> Result.success(false) - StateEventType.ROOM_TOPIC -> Result.failure(RuntimeException("Oops")) + StateEventType.RoomName -> true + StateEventType.RoomAvatar -> false + StateEventType.RoomTopic -> false else -> lambdaError() } - }, + } ) val deleteCallback = lambdaRecorder {} val presenter = createRoomDetailsEditPresenter( @@ -156,11 +161,11 @@ class RoomDetailsEditPresenterTest { fun `present - sets canChangeAvatar if user has permission`() = runTest { val room = aJoinedRoom( avatarUrl = AN_AVATAR_URL, - canSendStateResult = { _, stateEventType -> + canSendState = { stateEventType -> when (stateEventType) { - StateEventType.ROOM_NAME -> Result.success(false) - StateEventType.ROOM_AVATAR -> Result.success(true) - StateEventType.ROOM_TOPIC -> Result.failure(RuntimeException("Oops")) + StateEventType.RoomName -> false + StateEventType.RoomAvatar -> true + StateEventType.RoomTopic -> false else -> lambdaError() } } @@ -188,11 +193,11 @@ class RoomDetailsEditPresenterTest { fun `present - sets canChangeTopic if user has permission`() = runTest { val room = aJoinedRoom( avatarUrl = AN_AVATAR_URL, - canSendStateResult = { _, stateEventType -> + canSendState = { stateEventType -> when (stateEventType) { - StateEventType.ROOM_NAME -> Result.success(false) - StateEventType.ROOM_AVATAR -> Result.failure(RuntimeException("Oops")) - StateEventType.ROOM_TOPIC -> Result.success(true) + StateEventType.RoomName -> false + StateEventType.RoomAvatar -> false + StateEventType.RoomTopic -> true else -> lambdaError() } } @@ -222,7 +227,6 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - canSendStateResult = { _, _ -> Result.success(true) } ) val deleteCallback = lambdaRecorder {} val presenter = createRoomDetailsEditPresenter( @@ -233,26 +237,26 @@ class RoomDetailsEditPresenterTest { val initialState = awaitFirstItem() assertThat(initialState.roomTopic).isEqualTo("My topic") assertThat(initialState.roomRawName).isEqualTo("Name") - assertThat(initialState.roomAvatarUrl).isEqualTo(roomAvatarUri) - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("Name II")) + assertThat(initialState.roomAvatarUrl).isEqualTo(AN_AVATAR_URL) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("Name II")) awaitItem().apply { assertThat(roomTopic).isEqualTo("My topic") assertThat(roomRawName).isEqualTo("Name II") - assertThat(roomAvatarUrl).isEqualTo(roomAvatarUri) + assertThat(roomAvatarUrl).isEqualTo(AN_AVATAR_URL) } - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("Name III")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("Name III")) awaitItem().apply { assertThat(roomTopic).isEqualTo("My topic") assertThat(roomRawName).isEqualTo("Name III") - assertThat(roomAvatarUrl).isEqualTo(roomAvatarUri) + assertThat(roomAvatarUrl).isEqualTo(AN_AVATAR_URL) } - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("Another topic")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomTopic("Another topic")) awaitItem().apply { assertThat(roomTopic).isEqualTo("Another topic") assertThat(roomRawName).isEqualTo("Name III") - assertThat(roomAvatarUrl).isEqualTo(roomAvatarUri) + assertThat(roomAvatarUrl).isEqualTo(AN_AVATAR_URL) } - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove)) + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.Remove)) awaitItem().apply { assertThat(roomTopic).isEqualTo("Another topic") assertThat(roomRawName).isEqualTo("Name III") @@ -267,7 +271,6 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - canSendStateResult = { _, _ -> Result.success(true) } ) fakePickerProvider.givenResult(anotherAvatarUri) val deleteCallback = lambdaRecorder {} @@ -277,10 +280,10 @@ class RoomDetailsEditPresenterTest { ) presenter.test { val initialState = awaitFirstItem() - assertThat(initialState.roomAvatarUrl).isEqualTo(roomAvatarUri) - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) + assertThat(initialState.roomAvatarUrl).isEqualTo(AN_AVATAR_URL) + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) awaitItem().apply { - assertThat(roomAvatarUrl).isEqualTo(anotherAvatarUri) + assertThat(roomAvatarUrl).isEqualTo(anotherAvatarUri.toString()) } } } @@ -291,7 +294,6 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - canSendStateResult = { _, _ -> Result.success(true) } ) fakePickerProvider.givenResult(anotherAvatarUri) val fakePermissionsPresenter = FakePermissionsPresenter() @@ -303,21 +305,21 @@ class RoomDetailsEditPresenterTest { ) presenter.test { val initialState = awaitFirstItem() - assertThat(initialState.roomAvatarUrl).isEqualTo(roomAvatarUri) + assertThat(initialState.roomAvatarUrl).isEqualTo(AN_AVATAR_URL) assertThat(initialState.cameraPermissionState.permissionGranted).isFalse() - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.TakePhoto)) + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.TakePhoto)) val stateWithAskingPermission = awaitItem() assertThat(stateWithAskingPermission.cameraPermissionState.showDialog).isTrue() fakePermissionsPresenter.setPermissionGranted() val stateWithPermission = awaitItem() assertThat(stateWithPermission.cameraPermissionState.permissionGranted).isTrue() val stateWithNewAvatar = awaitItem() - assertThat(stateWithNewAvatar.roomAvatarUrl).isEqualTo(anotherAvatarUri) + assertThat(stateWithNewAvatar.roomAvatarUrl).isEqualTo(anotherAvatarUri.toString()) // Do it again, no permission is requested fakePickerProvider.givenResult(roomAvatarUri) - stateWithNewAvatar.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.TakePhoto)) + stateWithNewAvatar.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.TakePhoto)) val stateWithNewAvatar2 = awaitItem() - assertThat(stateWithNewAvatar2.roomAvatarUrl).isEqualTo(roomAvatarUri) + assertThat(stateWithNewAvatar2.roomAvatarUrl).isEqualTo(AN_AVATAR_URL) deleteCallback.assertions().isCalledExactly(3).withSequence( listOf(value(null)), listOf(value(roomAvatarUri)), @@ -332,7 +334,6 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - canSendStateResult = { _, _ -> Result.success(true) } ) fakePickerProvider.givenResult(roomAvatarUri) val deleteCallback = lambdaRecorder {} @@ -344,32 +345,32 @@ class RoomDetailsEditPresenterTest { val initialState = awaitFirstItem() assertThat(initialState.saveButtonEnabled).isFalse() // Once a change is made, the save button is enabled - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("Name II")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("Name II")) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // If it's reverted then the save disables again - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("Name")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("Name")) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } // Make a change... - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("Another topic")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomTopic("Another topic")) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // Revert it... - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("My topic")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomTopic("My topic")) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } // Make a change... - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove)) + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.Remove)) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // Revert it... - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } @@ -382,7 +383,6 @@ class RoomDetailsEditPresenterTest { topic = null, displayName = "fallback", avatarUrl = null, - canSendStateResult = { _, _ -> Result.success(true) } ) fakePickerProvider.givenResult(roomAvatarUri) val deleteCallback = lambdaRecorder {} @@ -394,32 +394,32 @@ class RoomDetailsEditPresenterTest { val initialState = awaitFirstItem() assertThat(initialState.saveButtonEnabled).isFalse() // Once a change is made, the save button is enabled - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("Name II")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("Name II")) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // If it's reverted then the save disables again - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("fallback")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("fallback")) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } // Make a change... - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("Another topic")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomTopic("Another topic")) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // Revert it... - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomTopic("")) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } // Make a change... - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // Revert it... - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove)) + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.Remove)) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } @@ -438,7 +438,6 @@ class RoomDetailsEditPresenterTest { setNameResult = setNameResult, setTopicResult = setTopicResult, removeAvatarResult = removeAvatarResult, - canSendStateResult = { _, _ -> Result.success(true) } ) val deleteCallback = lambdaRecorder {} val presenter = createRoomDetailsEditPresenter( @@ -447,10 +446,10 @@ class RoomDetailsEditPresenterTest { ) presenter.test { val initialState = awaitFirstItem() - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("New name")) - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("New topic")) - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove)) - initialState.eventSink(RoomDetailsEditEvents.Save) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("New name")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomTopic("New topic")) + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.Remove)) + initialState.eventSink(RoomDetailsEditEvent.Save) skipItems(5) setNameResult.assertions().isCalledOnce().with(value("New name")) setTopicResult.assertions().isCalledOnce().with(value("New topic")) @@ -464,7 +463,6 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - canSendStateResult = { _, _ -> Result.success(true) } ) val deleteCallback = lambdaRecorder {} val presenter = createRoomDetailsEditPresenter( @@ -473,9 +471,9 @@ class RoomDetailsEditPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName(" Name ")) - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic(" My topic ")) - initialState.eventSink(RoomDetailsEditEvents.Save) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName(" Name ")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomTopic(" My topic ")) + initialState.eventSink(RoomDetailsEditEvent.Save) cancelAndIgnoreRemainingEvents() } } @@ -486,7 +484,6 @@ class RoomDetailsEditPresenterTest { topic = null, displayName = "Name", avatarUrl = AN_AVATAR_URL, - canSendStateResult = { _, _ -> Result.success(true) } ) val deleteCallback = lambdaRecorder {} val presenter = createRoomDetailsEditPresenter( @@ -495,8 +492,8 @@ class RoomDetailsEditPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("")) - initialState.eventSink(RoomDetailsEditEvents.Save) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomTopic("")) + initialState.eventSink(RoomDetailsEditEvent.Save) cancelAndIgnoreRemainingEvents() deleteCallback.assertions().isCalledOnce().with(value(null)) } @@ -508,7 +505,6 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - canSendStateResult = { _, _ -> Result.success(true) } ) val deleteCallback = lambdaRecorder {} val presenter = createRoomDetailsEditPresenter( @@ -517,8 +513,8 @@ class RoomDetailsEditPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("")) - initialState.eventSink(RoomDetailsEditEvents.Save) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("")) + initialState.eventSink(RoomDetailsEditEvent.Save) cancelAndIgnoreRemainingEvents() deleteCallback.assertions().isCalledOnce().with(value(null)) } @@ -532,24 +528,30 @@ class RoomDetailsEditPresenterTest { displayName = "Name", avatarUrl = AN_AVATAR_URL, updateAvatarResult = updateAvatarResult, - canSendStateResult = { _, _ -> Result.success(true) } ) - givenPickerReturnsFile() + val tmpFile = givenPickerReturnsFile() val deleteCallback = lambdaRecorder {} val presenter = createRoomDetailsEditPresenter( room = room, temporaryUriDeleter = FakeTemporaryUriDeleter(deleteCallback), ) - presenter.test { - val initialState = awaitItem() - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) - initialState.eventSink(RoomDetailsEditEvents.Save) - skipItems(4) - updateAvatarResult.assertions().isCalledOnce().with(value(MimeTypes.Jpeg), value(fakeFileContents)) - deleteCallback.assertions().isCalledExactly(2).withSequence( - listOf(value(null)), - listOf(value(roomAvatarUri)), - ) + try { + presenter.test { + val initialState = awaitItem() + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) + initialState.eventSink(RoomDetailsEditEvent.Save) + skipItems(4) + updateAvatarResult.assertions().isCalledOnce().with( + value(MimeTypes.Jpeg), + matching { it.contentEquals(fakeFileContents) } + ) + deleteCallback.assertions().isCalledExactly(2).withSequence( + listOf(value(null)), + listOf(value(roomAvatarUri)), + ) + } + } finally { + tmpFile.delete() } } @@ -559,7 +561,6 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - canSendStateResult = { _, _ -> Result.success(true) } ) fakePickerProvider.givenResult(anotherAvatarUri) fakeMediaPreProcessor.givenResult(Result.failure(RuntimeException("Oh no"))) @@ -570,8 +571,8 @@ class RoomDetailsEditPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) - initialState.eventSink(RoomDetailsEditEvents.Save) + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) + initialState.eventSink(RoomDetailsEditEvent.Save) skipItems(3) assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) } @@ -584,9 +585,8 @@ class RoomDetailsEditPresenterTest { displayName = "Name", avatarUrl = AN_AVATAR_URL, setNameResult = { Result.failure(RuntimeException("!")) }, - canSendStateResult = { _, _ -> Result.success(true) } ) - saveAndAssertFailure(room, RoomDetailsEditEvents.UpdateRoomName("New name"), deleteCallbackNumberOfInvocation = 1) + saveAndAssertFailure(room, RoomDetailsEditEvent.UpdateRoomName("New name"), deleteCallbackNumberOfInvocation = 1) } @Test @@ -596,9 +596,8 @@ class RoomDetailsEditPresenterTest { displayName = "Name", avatarUrl = AN_AVATAR_URL, setTopicResult = { Result.failure(RuntimeException("!")) }, - canSendStateResult = { _, _ -> Result.success(true) } ) - saveAndAssertFailure(room, RoomDetailsEditEvents.UpdateRoomTopic("New topic"), deleteCallbackNumberOfInvocation = 1) + saveAndAssertFailure(room, RoomDetailsEditEvent.UpdateRoomTopic("New topic"), deleteCallbackNumberOfInvocation = 1) } @Test @@ -608,33 +607,59 @@ class RoomDetailsEditPresenterTest { displayName = "Name", avatarUrl = AN_AVATAR_URL, removeAvatarResult = { Result.failure(RuntimeException("!")) }, - canSendStateResult = { _, _ -> Result.success(true) } ) - saveAndAssertFailure(room, RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove), deleteCallbackNumberOfInvocation = 2) + saveAndAssertFailure(room, RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.Remove), deleteCallbackNumberOfInvocation = 2) } @Test fun `present - sets save action to failure if setting avatar fails`() = runTest { - givenPickerReturnsFile() + val tmpFile = givenPickerReturnsFile() val room = aJoinedRoom( topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, updateAvatarResult = { _, _ -> Result.failure(RuntimeException("!")) }, - canSendStateResult = { _, _ -> Result.success(true) } ) - saveAndAssertFailure(room, RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto), deleteCallbackNumberOfInvocation = 2) + try { + saveAndAssertFailure(room, RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.ChoosePhoto), deleteCallbackNumberOfInvocation = 2) + } finally { + tmpFile.delete() + } } @Test fun `present - CancelSaveChanges resets save action state`() = runTest { - givenPickerReturnsFile() + val tmpFile = givenPickerReturnsFile() val room = aJoinedRoom( topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, setTopicResult = { Result.failure(RuntimeException("!")) }, - canSendStateResult = { _, _ -> Result.success(true) } + ) + val deleteCallback = lambdaRecorder {} + val presenter = createRoomDetailsEditPresenter( + room = room, + temporaryUriDeleter = FakeTemporaryUriDeleter(deleteCallback), + ) + try { + presenter.test { + val initialState = awaitItem() + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomTopic("foo")) + initialState.eventSink(RoomDetailsEditEvent.Save) + skipItems(3) + assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) + initialState.eventSink(RoomDetailsEditEvent.CloseDialog) + assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Uninitialized::class.java) + } + } finally { + tmpFile.delete() + } + } + + @Test + fun `present - leave without saving - cancel`() = runTest { + val room = aJoinedRoom( + displayName = "Name", ) val deleteCallback = lambdaRecorder {} val presenter = createRoomDetailsEditPresenter( @@ -642,19 +667,73 @@ class RoomDetailsEditPresenterTest { temporaryUriDeleter = FakeTemporaryUriDeleter(deleteCallback), ) presenter.test { - val initialState = awaitItem() - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("foo")) - initialState.eventSink(RoomDetailsEditEvents.Save) - skipItems(3) - assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) - initialState.eventSink(RoomDetailsEditEvents.CancelSaveChanges) - assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Uninitialized::class.java) + val initialState = awaitFirstItem() + assertThat(initialState.saveButtonEnabled).isFalse() + // Once a change is made, the save button is enabled + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("Name edited")) + awaitItem().apply { + assertThat(saveButtonEnabled).isTrue() + eventSink(RoomDetailsEditEvent.OnBackPress) + } + awaitItem().apply { + assertThat(saveAction).isEqualTo(AsyncAction.ConfirmingCancellation) + eventSink(RoomDetailsEditEvent.CloseDialog) + } + awaitItem().apply { + assertThat(saveAction).isEqualTo(AsyncAction.Uninitialized) + } + } + } + + @Test + fun `present - leave no changes, no confirmation`() = runTest { + val room = aJoinedRoom( + displayName = "Name", + ) + val presenter = createRoomDetailsEditPresenter( + room = room, + temporaryUriDeleter = FakeTemporaryUriDeleter {}, + ) + presenter.test { + val initialState = awaitFirstItem() + assertThat(initialState.saveButtonEnabled).isFalse() + initialState.eventSink(RoomDetailsEditEvent.OnBackPress) + assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Success(Unit)) + } + } + + @Test + fun `present - leave without saving - confirm`() = runTest { + val room = aJoinedRoom( + displayName = "Name", + canSendState = { _ -> true } + ) + val presenter = createRoomDetailsEditPresenter( + room = room, + temporaryUriDeleter = FakeTemporaryUriDeleter({}), + ) + presenter.test { + val initialState = awaitFirstItem() + assertThat(initialState.saveButtonEnabled).isFalse() + // Once a change is made, the save button is enabled + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("Name edited")) + awaitItem().apply { + assertThat(saveButtonEnabled).isTrue() + eventSink(RoomDetailsEditEvent.OnBackPress) + } + awaitItem().apply { + assertThat(saveAction).isEqualTo(AsyncAction.ConfirmingCancellation) + eventSink(RoomDetailsEditEvent.OnBackPress) + } + awaitItem().apply { + assertThat(saveAction).isEqualTo(AsyncAction.Success(Unit)) + } } } private suspend fun saveAndAssertFailure( room: JoinedRoom, - event: RoomDetailsEditEvents, + event: RoomDetailsEditEvent, deleteCallbackNumberOfInvocation: Int = 2, ) { val deleteCallback = lambdaRecorder {} @@ -665,7 +744,7 @@ class RoomDetailsEditPresenterTest { presenter.test { val initialState = awaitFirstItem() initialState.eventSink(event) - initialState.eventSink(RoomDetailsEditEvents.Save) + initialState.eventSink(RoomDetailsEditEvent.Save) skipItems(1) assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Loading::class.java) assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) @@ -673,20 +752,49 @@ class RoomDetailsEditPresenterTest { } } - private fun givenPickerReturnsFile() { - mockkStatic(File::readBytes) - val processedFile: File = mockk { - every { readBytes() } returns fakeFileContents - } + private fun givenPickerReturnsFile(): File { + val tmpFile = File.createTempFile("test", "jpg") + tmpFile.writeBytes(fakeFileContents) fakePickerProvider.givenResult(anotherAvatarUri) fakeMediaPreProcessor.givenResult( Result.success( MediaUploadInfo.AnyFile( - file = processedFile, + file = tmpFile, fileInfo = mockk(), ) ) ) + return tmpFile + } + + private fun aJoinedRoom( + avatarUrl: String? = AN_AVATAR_URL, + displayName: String = A_ROOM_NAME, + rawName: String = displayName, + topic: String? = A_ROOM_TOPIC, + setNameResult: (String) -> Result = { Result.success(Unit) }, + setTopicResult: (String) -> Result = { Result.success(Unit) }, + updateAvatarResult: (String, ByteArray) -> Result = { _, _ -> Result.success(Unit) }, + removeAvatarResult: () -> Result = { Result.success(Unit) }, + canSendState: (StateEventType) -> Boolean = { true }, + ): JoinedRoom { + return FakeJoinedRoom( + baseRoom = FakeBaseRoom( + roomPermissions = FakeRoomPermissions( + canSendState = canSendState, + ), + initialRoomInfo = aRoomInfo( + name = displayName, + topic = topic, + avatarUrl = avatarUrl, + rawName = rawName + ) + ), + setNameResult = setNameResult, + setTopicResult = setTopicResult, + updateAvatarResult = updateAvatarResult, + removeAvatarResult = removeAvatarResult, + ) } companion object { diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditViewTest.kt b/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditViewTest.kt similarity index 75% rename from features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditViewTest.kt rename to features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditViewTest.kt index 2ac3d4397c..71fb143074 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditViewTest.kt +++ b/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditViewTest.kt @@ -1,11 +1,11 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.edit +package io.element.android.features.roomdetailsedit.impl import androidx.activity.ComponentActivity import androidx.annotation.StringRes @@ -38,36 +38,60 @@ class RoomDetailsEditViewTest { @get:Rule val rule = createAndroidComposeRule() @Test - fun `clicking on back invoke back callback`() { - val eventsRecorder = EventsRecorder(expectEvents = false) - ensureCalledOnce { callback -> - rule.setRoomDetailsEditView( - aRoomDetailsEditState( - eventSink = eventsRecorder - ), - onBackClick = callback, - ) - rule.pressBack() - } + fun `clicking on back emits the expected Event`() { + val eventsRecorder = EventsRecorder() + rule.setRoomDetailsEditView( + aRoomDetailsEditState( + eventSink = eventsRecorder + ), + ) + rule.pressBack() + eventsRecorder.assertSingle(RoomDetailsEditEvent.OnBackPress) + } + + @Test + fun `clicking on discard when confirming exit emits the expected Event`() { + val eventsRecorder = EventsRecorder() + rule.setRoomDetailsEditView( + aRoomDetailsEditState( + saveAction = AsyncAction.ConfirmingCancellation, + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.action_discard) + eventsRecorder.assertSingle(RoomDetailsEditEvent.OnBackPress) + } + + @Test + fun `clicking on save when confirming exit emits the expected Event`() { + val eventsRecorder = EventsRecorder() + rule.setRoomDetailsEditView( + aRoomDetailsEditState( + saveAction = AsyncAction.ConfirmingCancellation, + eventSink = eventsRecorder, + ), + ) + rule.clickOn(CommonStrings.action_save, inDialog = true) + eventsRecorder.assertSingle(RoomDetailsEditEvent.Save) } @Test fun `when edition is successful, the expected callback is invoked`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, saveAction = AsyncAction.Success(Unit) ), - onRoomEdited = callback, + onDone = callback, ) } } @Test fun `when name is changed, the expected Event is emitted`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, @@ -75,12 +99,12 @@ class RoomDetailsEditViewTest { ), ) rule.onNodeWithText("Marketing").performTextInput("A") - eventsRecorder.assertSingle(RoomDetailsEditEvents.UpdateRoomName("AMarketing")) + eventsRecorder.assertSingle(RoomDetailsEditEvent.UpdateRoomName("AMarketing")) } @Test fun `when user cannot change name, nothing happen`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, @@ -93,7 +117,7 @@ class RoomDetailsEditViewTest { @Test fun `when topic is changed, the expected Event is emitted`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, @@ -101,12 +125,12 @@ class RoomDetailsEditViewTest { ), ) rule.onNodeWithText("My Topic").performTextInput("A") - eventsRecorder.assertSingle(RoomDetailsEditEvents.UpdateRoomTopic("AMy Topic")) + eventsRecorder.assertSingle(RoomDetailsEditEvent.UpdateRoomTopic("AMy Topic")) } @Test fun `when user cannot change topic, nothing happen`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, @@ -122,7 +146,7 @@ class RoomDetailsEditViewTest { fun `when avatar is changed with action to take photo, the expected Event is emitted`() { testAvatarChange( stringActionRes = CommonStrings.action_take_photo, - expectedEvent = RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.TakePhoto), + expectedEvent = RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.TakePhoto), ) } @@ -131,7 +155,7 @@ class RoomDetailsEditViewTest { fun `when avatar is changed with action to choose photo, the expected Event is emitted`() { testAvatarChange( stringActionRes = CommonStrings.action_choose_photo, - expectedEvent = RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto), + expectedEvent = RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.ChoosePhoto), ) } @@ -140,15 +164,15 @@ class RoomDetailsEditViewTest { fun `when avatar is changed with action to remove photo, the expected Event is emitted`() { testAvatarChange( stringActionRes = CommonStrings.action_remove, - expectedEvent = RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove), + expectedEvent = RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.Remove), ) } private fun testAvatarChange( @StringRes stringActionRes: Int, - expectedEvent: RoomDetailsEditEvents.HandleAvatarAction, + expectedEvent: RoomDetailsEditEvent.HandleAvatarAction, ) { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, @@ -163,7 +187,7 @@ class RoomDetailsEditViewTest { @Test fun `when user cannot change avatar, nothing happen`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, @@ -176,7 +200,7 @@ class RoomDetailsEditViewTest { @Test fun `when save is clicked, the expected Event is emitted`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, @@ -184,12 +208,12 @@ class RoomDetailsEditViewTest { ), ) rule.clickOn(CommonStrings.action_save) - eventsRecorder.assertSingle(RoomDetailsEditEvents.Save) + eventsRecorder.assertSingle(RoomDetailsEditEvent.Save) } @Test fun `when save is clicked, but nothing need to be saved, nothing happens`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, @@ -201,7 +225,7 @@ class RoomDetailsEditViewTest { @Test fun `when error is shown, closing the dialog emit the expected Event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, @@ -209,20 +233,18 @@ class RoomDetailsEditViewTest { ), ) rule.clickOn(CommonStrings.action_ok) - eventsRecorder.assertSingle(RoomDetailsEditEvents.CancelSaveChanges) + eventsRecorder.assertSingle(RoomDetailsEditEvent.CloseDialog) } } private fun AndroidComposeTestRule.setRoomDetailsEditView( state: RoomDetailsEditState, - onBackClick: () -> Unit = EnsureNeverCalled(), - onRoomEdited: () -> Unit = EnsureNeverCalled(), + onDone: () -> Unit = EnsureNeverCalled(), ) { setContent { RoomDetailsEditView( state = state, - onBackClick = onBackClick, - onRoomEditSuccess = onRoomEdited, + onDone = onDone, ) } } diff --git a/features/roomdetailsedit/test/build.gradle.kts b/features/roomdetailsedit/test/build.gradle.kts new file mode 100644 index 0000000000..ff9110eb49 --- /dev/null +++ b/features/roomdetailsedit/test/build.gradle.kts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.roomdetetailsedit.test" +} + +dependencies { + implementation(projects.features.roomdetailsedit.api) + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) + implementation(projects.tests.testutils) +} diff --git a/features/roomdetailsedit/test/src/main/kotlin/io/element/android/features/roomdetailsedit/test/FakeRoomDetailsEditEntryPoint.kt b/features/roomdetailsedit/test/src/main/kotlin/io/element/android/features/roomdetailsedit/test/FakeRoomDetailsEditEntryPoint.kt new file mode 100644 index 0000000000..df890ab2c6 --- /dev/null +++ b/features/roomdetailsedit/test/src/main/kotlin/io/element/android/features/roomdetailsedit/test/FakeRoomDetailsEditEntryPoint.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.roomdetailsedit.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.roomdetailsedit.api.RoomDetailsEditEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeRoomDetailsEditEntryPoint : RoomDetailsEditEntryPoint { + override fun createNode(parentNode: Node, buildContext: BuildContext): Node { + lambdaError() + } +} diff --git a/features/roomdirectory/api/build.gradle.kts b/features/roomdirectory/api/build.gradle.kts index ee27b5b21a..0f1be0e235 100644 --- a/features/roomdirectory/api/build.gradle.kts +++ b/features/roomdirectory/api/build.gradle.kts @@ -1,12 +1,13 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { - id("io.element.android-library") + id("io.element.android-compose-library") id("kotlin-parcelize") } diff --git a/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDescription.kt b/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDescription.kt index 7d479cbad9..86767fde27 100644 --- a/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDescription.kt +++ b/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDescription.kt @@ -1,14 +1,14 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.roomdirectory.api import android.os.Parcelable -import androidx.compose.runtime.Immutable import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.core.RoomAlias @@ -17,7 +17,6 @@ import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize -@Immutable data class RoomDescription( val roomId: RoomId, val name: String?, diff --git a/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt b/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt index 892719554b..db57b2e61d 100644 --- a/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt +++ b/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,14 +14,13 @@ import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint interface RoomDirectoryEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - - interface NodeBuilder { - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: Callback, + ): Node interface Callback : Plugin { - fun onResultClick(roomDescription: RoomDescription) + fun navigateToRoom(roomDescription: RoomDescription) } } diff --git a/features/roomdirectory/impl/build.gradle.kts b/features/roomdirectory/impl/build.gradle.kts index ec858ced09..1c63872fc8 100644 --- a/features/roomdirectory/impl/build.gradle.kts +++ b/features/roomdirectory/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt index 135b2d5aea..f12c31cdd2 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,29 +10,19 @@ package io.element.android.features.roomdirectory.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint import io.element.android.features.roomdirectory.impl.root.RoomDirectoryNode import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultRoomDirectoryEntryPoint : RoomDirectoryEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): RoomDirectoryEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : RoomDirectoryEntryPoint.NodeBuilder { - override fun callback(callback: RoomDirectoryEntryPoint.Callback): RoomDirectoryEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: RoomDirectoryEntryPoint.Callback, + ): Node { + return parentNode.createNode(buildContext, listOf(callback)) } } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt index 9c0672b54b..20e866f2bf 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt index 03d2be6e35..0e0ef509dc 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,12 +13,11 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode -import io.element.android.features.roomdirectory.api.RoomDescription import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.SessionScope @ContributesNode(SessionScope::class) @@ -27,18 +27,14 @@ class RoomDirectoryNode( @Assisted plugins: List, private val presenter: RoomDirectoryPresenter, ) : Node(buildContext, plugins = plugins) { - private fun onResultClick(roomDescription: RoomDescription) { - plugins().forEach { - it.onResultClick(roomDescription) - } - } + private val callback: RoomDirectoryEntryPoint.Callback = callback() @Composable override fun View(modifier: Modifier) { val state = presenter.present() RoomDirectoryView( state = state, - onResultClick = ::onResultClick, + onResultClick = callback::navigateToRoom, onBackClick = ::navigateUp, modifier = modifier ) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt index 4696dd4263..98338440a1 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -62,7 +63,7 @@ class RoomDirectoryPresenter( loadingMore = false } } - fun handleEvents(event: RoomDirectoryEvents) { + fun handleEvent(event: RoomDirectoryEvents) { when (event) { RoomDirectoryEvents.LoadMore -> { loadingMore = true @@ -77,7 +78,7 @@ class RoomDirectoryPresenter( query = searchQuery.orEmpty(), roomDescriptions = listState.items, displayLoadMoreIndicator = listState.hasMoreToLoad, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt index f28a8b8a49..a3647381dc 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt index 6a5ba74eba..4ee90fdf16 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt index 8b2944f745..f2e4ae44fa 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescription.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescription.kt index fba94c49f7..e6027c5ac9 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescription.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDescription.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryListState.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryListState.kt index 4f92f09400..cd31d0f458 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryListState.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryListState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdirectory/impl/src/main/res/values-hr/translations.xml b/features/roomdirectory/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..2057f54c18 --- /dev/null +++ b/features/roomdirectory/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,5 @@ + + + "Učitavanje nije uspjelo" + "Direktorij soba" + diff --git a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPointTest.kt b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPointTest.kt index d544f55000..2108c9aff9 100644 --- a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPointTest.kt +++ b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -35,11 +36,13 @@ class DefaultRoomDirectoryEntryPointTest { ) } val callback = object : RoomDirectoryEntryPoint.Callback { - override fun onResultClick(roomDescription: RoomDescription) = lambdaError() + override fun navigateToRoom(roomDescription: RoomDescription) = lambdaError() } - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + callback = callback, + ) assertThat(result).isInstanceOf(RoomDirectoryNode::class.java) assertThat(result.plugins).contains(callback) } diff --git a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenterTest.kt b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenterTest.kt index 4af983b307..6295e4beb6 100644 --- a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenterTest.kt +++ b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -42,14 +43,14 @@ class RoomDirectoryPresenterTest { @Test fun `present - room directory list emits empty state`() = runTest { - val directoryListStateFlow = MutableSharedFlow(replay = 1) + val directoryListStateFlow = MutableSharedFlow(replay = 1) val roomDirectoryList = FakeRoomDirectoryList(directoryListStateFlow) val roomDirectoryService = FakeRoomDirectoryService { roomDirectoryList } val presenter = createRoomDirectoryPresenter(roomDirectoryService = roomDirectoryService) presenter.test { skipItems(1) directoryListStateFlow.emit( - RoomDirectoryList.State(false, emptyList()) + RoomDirectoryList.SearchResult(false, emptyList()) ) awaitItem().also { state -> assertThat(state.displayEmptyState).isTrue() @@ -60,14 +61,14 @@ class RoomDirectoryPresenterTest { @Test fun `present - room directory list emits non-empty state`() = runTest { - val directoryListStateFlow = MutableSharedFlow(replay = 1) + val directoryListStateFlow = MutableSharedFlow(replay = 1) val roomDirectoryList = FakeRoomDirectoryList(directoryListStateFlow) val roomDirectoryService = FakeRoomDirectoryService { roomDirectoryList } val presenter = createRoomDirectoryPresenter(roomDirectoryService = roomDirectoryService) presenter.test { skipItems(1) directoryListStateFlow.emit( - RoomDirectoryList.State( + RoomDirectoryList.SearchResult( hasMoreToLoad = true, items = listOf(aRoomDescription()) ) @@ -105,9 +106,7 @@ class RoomDirectoryPresenterTest { @Test fun `present - emit load more event`() = runTest { - val loadMoreLambda = lambdaRecorder { -> - Result.success(Unit) - } + val loadMoreLambda = lambdaRecorder> { Result.success(Unit) } val roomDirectoryList = FakeRoomDirectoryList(loadMoreLambda = loadMoreLambda) val roomDirectoryService = FakeRoomDirectoryService { roomDirectoryList } val presenter = createRoomDirectoryPresenter(roomDirectoryService = roomDirectoryService) diff --git a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt index ff09212010..a50ad6a22c 100644 --- a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt +++ b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roommembermoderation/api/build.gradle.kts b/features/roommembermoderation/api/build.gradle.kts index aeea031757..e168d2c8bf 100644 --- a/features/roommembermoderation/api/build.gradle.kts +++ b/features/roommembermoderation/api/build.gradle.kts @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt index da25ca41eb..2238ff41df 100644 --- a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt +++ b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationPermissions.kt b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationPermissions.kt new file mode 100644 index 0000000000..87414b836e --- /dev/null +++ b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationPermissions.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.roommembermoderation.api + +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions + +data class RoomMemberModerationPermissions( + val canKick: Boolean, + val canBan: Boolean, +) { + // Unban requires both kick and ban permission instead of a dedicated unban permission + val canUnban = canBan && canKick + + companion object { + val DEFAULT = RoomMemberModerationPermissions( + canKick = false, + canBan = false, + ) + } +} + +fun RoomPermissions.roomMemberModerationPermissions(): RoomMemberModerationPermissions { + return RoomMemberModerationPermissions( + canKick = canOwnUserKick(), + canBan = canOwnUserBan(), + ) +} diff --git a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationRenderer.kt b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationRenderer.kt index afe8ab0f8b..8fb1ae0c03 100644 --- a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationRenderer.kt +++ b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationRenderer.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt index 368aa283ad..c9ee958f6f 100644 --- a/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt +++ b/features/roommembermoderation/api/src/main/kotlin/io/element/android/features/roommembermoderation/api/RoomMemberModerationState.kt @@ -1,15 +1,18 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.roommembermoderation.api +import androidx.compose.runtime.Immutable + +@Immutable interface RoomMemberModerationState { - val canKick: Boolean - val canBan: Boolean + val permissions: RoomMemberModerationPermissions val eventSink: (RoomMemberModerationEvents) -> Unit } diff --git a/features/roommembermoderation/impl/build.gradle.kts b/features/roommembermoderation/impl/build.gradle.kts index a58b0ce02f..3157552bae 100644 --- a/features/roommembermoderation/impl/build.gradle.kts +++ b/features/roommembermoderation/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/DefaultRoomMemberModerationRenderer.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/DefaultRoomMemberModerationRenderer.kt index 830e3ef984..05bf00c7eb 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/DefaultRoomMemberModerationRenderer.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/DefaultRoomMemberModerationRenderer.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.ui.Modifier import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.RoomMemberModerationRenderer import io.element.android.features.roommembermoderation.api.RoomMemberModerationState @@ -20,7 +20,6 @@ import io.element.android.libraries.matrix.api.user.MatrixUser import timber.log.Timber @ContributesBinding(RoomScope::class) -@Inject class DefaultRoomMemberModerationRenderer : RoomMemberModerationRenderer { @Composable override fun Render( diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvents.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvents.kt index 902c2bd21f..2bc76db761 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvents.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,6 +13,6 @@ import io.element.android.features.roommembermoderation.api.RoomMemberModeration sealed interface InternalRoomMemberModerationEvents : RoomMemberModerationEvents { data class DoKickUser(val reason: String) : InternalRoomMemberModerationEvents data class DoBanUser(val reason: String) : InternalRoomMemberModerationEvents - data object DoUnbanUser : InternalRoomMemberModerationEvents + data class DoUnbanUser(val reason: String) : InternalRoomMemberModerationEvents data object Reset : InternalRoomMemberModerationEvents } diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt index 3a650ee59c..f45a277992 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,14 +10,14 @@ package io.element.android.features.roommembermoderation.impl import io.element.android.features.roommembermoderation.api.ModerationActionState import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.collections.immutable.ImmutableList data class InternalRoomMemberModerationState( - override val canKick: Boolean, - override val canBan: Boolean, + override val permissions: RoomMemberModerationPermissions, val selectedUser: MatrixUser?, val actions: ImmutableList, val kickUserAsyncAction: AsyncAction, diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationStateProvider.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationStateProvider.kt index 2d4cc75aa9..120a299a7d 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationStateProvider.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/InternalRoomMemberModerationStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,6 +12,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.ModerationActionState import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.user.MatrixUser @@ -64,6 +66,14 @@ class InternalRoomMemberModerationStateProvider : PreviewParameterProvider = emptyList(), kickUserAsyncAction: AsyncAction = AsyncAction.Uninitialized, @@ -83,8 +92,7 @@ fun aRoomMembersModerationState( unbanUserAsyncAction: AsyncAction = AsyncAction.Uninitialized, eventSink: (RoomMemberModerationEvents) -> Unit = {}, ) = InternalRoomMemberModerationState( - canKick = canKick, - canBan = canBan, + permissions = permissions, selectedUser = selectedUser, actions = actions.toImmutableList(), kickUserAsyncAction = kickUserAsyncAction, diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt index 2ddb3ad34a..2a876fc484 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -20,20 +21,22 @@ import im.vector.app.features.analytics.plan.RoomModeration import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.ModerationActionState import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions import io.element.android.features.roommembermoderation.api.RoomMemberModerationState +import io.element.android.features.roommembermoderation.api.roomMemberModerationPermissions import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.coroutine.mapState import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembershipState +import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState import io.element.android.libraries.matrix.api.room.roomMembers import io.element.android.libraries.matrix.api.user.MatrixUser -import io.element.android.libraries.matrix.ui.room.canBanAsState -import io.element.android.libraries.matrix.ui.room.canKickAsState -import io.element.android.libraries.matrix.ui.room.userPowerLevelAsState +import io.element.android.libraries.matrix.ui.model.powerLevelOf import io.element.android.services.analytics.api.AnalyticsService import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -54,10 +57,14 @@ class RoomMemberModerationPresenter( @Composable override fun present(): RoomMemberModerationState { val coroutineScope = rememberCoroutineScope() - val syncUpdateFlow = room.syncUpdateFlow.collectAsState() - val canBan = room.canBanAsState(syncUpdateFlow.value) - val canKick = room.canKickAsState(syncUpdateFlow.value) - val currentUserMemberPowerLevel = room.userPowerLevelAsState(syncUpdateFlow.value) + val permissions by room.permissionsAsState(RoomMemberModerationPermissions.DEFAULT) { perms -> + perms.roomMemberModerationPermissions() + } + val currentUserPowerLevel by remember { + room.roomInfoFlow.mapState { info -> + info.powerLevelOf(room.sessionId) + } + }.collectAsState() val kickUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction) } @@ -79,9 +86,8 @@ class RoomMemberModerationPresenter( } moderationActions.value = computeModerationActions( member = member, - canKick = canKick.value, - canBan = canBan.value, - currentUserMemberPowerLevel = currentUserMemberPowerLevel.value, + permissions = permissions, + currentUserPowerLevel = currentUserPowerLevel, ) } is RoomMemberModerationEvents.ProcessAction -> { @@ -118,7 +124,7 @@ class RoomMemberModerationPresenter( } is InternalRoomMemberModerationEvents.DoUnbanUser -> { selectedUser?.let { - coroutineScope.unbanUser(it.userId, unbanUserAsyncAction) + coroutineScope.unbanUser(it.userId, event.reason, unbanUserAsyncAction) } selectedUser = null } @@ -133,39 +139,48 @@ class RoomMemberModerationPresenter( } return InternalRoomMemberModerationState( - canKick = canKick.value, - canBan = canBan.value, + permissions = permissions, selectedUser = selectedUser, actions = moderationActions.value, kickUserAsyncAction = kickUserAsyncAction.value, banUserAsyncAction = banUserAsyncAction.value, unbanUserAsyncAction = unbanUserAsyncAction.value, - eventSink = { handleEvent(it) }, + eventSink = ::handleEvent, ) } private fun computeModerationActions( member: RoomMember?, - canKick: Boolean, - canBan: Boolean, - currentUserMemberPowerLevel: Long, + permissions: RoomMemberModerationPermissions, + currentUserPowerLevel: Long, ): ImmutableList { return buildList { add(ModerationActionState(action = ModerationAction.DisplayProfile, isEnabled = true)) // Assume the member is a regular user when it's unknown val targetMemberPowerLevel = member?.powerLevel ?: 0 - val canModerateThisUser = currentUserMemberPowerLevel > targetMemberPowerLevel + val canModerateThisUser = currentUserPowerLevel > targetMemberPowerLevel // Assume the member is joined when it's unknown val membership = member?.membership ?: RoomMembershipState.JOIN - if (canKick) { - val isKickEnabled = canModerateThisUser && membership.isActive() - add(ModerationActionState(action = ModerationAction.KickUser, isEnabled = isKickEnabled)) - } - if (canBan) { - if (membership == RoomMembershipState.BAN) { - add(ModerationActionState(action = ModerationAction.UnbanUser, isEnabled = canModerateThisUser)) - } else { - add(ModerationActionState(action = ModerationAction.BanUser, isEnabled = canModerateThisUser)) + when (membership) { + RoomMembershipState.BAN -> { + if (permissions.canUnban) { + add(ModerationActionState(action = ModerationAction.UnbanUser, isEnabled = canModerateThisUser)) + } + } + RoomMembershipState.INVITE, + RoomMembershipState.JOIN, + RoomMembershipState.KNOCK -> { + if (permissions.canKick) { + add(ModerationActionState(action = ModerationAction.KickUser, isEnabled = canModerateThisUser)) + } + if (permissions.canBan) { + add(ModerationActionState(action = ModerationAction.BanUser, isEnabled = canModerateThisUser)) + } + } + RoomMembershipState.LEAVE -> { + if (permissions.canBan) { + add(ModerationActionState(action = ModerationAction.BanUser, isEnabled = canModerateThisUser)) + } } } }.toImmutableList() @@ -197,10 +212,14 @@ class RoomMemberModerationPresenter( private fun CoroutineScope.unbanUser( userId: UserId, + reason: String, unbanUserAction: MutableState>, ) = runActionAndWaitForMembershipChange(unbanUserAction) { analyticsService.capture(RoomModeration(RoomModeration.Action.UnbanMember)) - room.unbanUser(userId = userId) + room.unbanUser( + userId = userId, + reason = reason.takeIf { it.isNotBlank() }, + ) } private fun CoroutineScope.runActionAndWaitForMembershipChange( diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt index 248b6cd02b..ac6c5bc4cb 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -39,7 +40,6 @@ import io.element.android.libraries.designsystem.components.async.rememberAsyncI import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarType -import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.components.dialogs.TextFieldDialog import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementPreview @@ -92,12 +92,13 @@ private fun RoomMemberAsyncActions( TextFieldDialog( title = stringResource(R.string.screen_bottom_sheet_manage_room_member_kick_member_confirmation_title), submitText = stringResource(R.string.screen_bottom_sheet_manage_room_member_kick_member_confirmation_action), + destructiveSubmit = true, + minLines = 2, onSubmit = { reason -> state.eventSink(InternalRoomMemberModerationEvents.DoKickUser(reason = reason)) }, onDismissRequest = { state.eventSink(InternalRoomMemberModerationEvents.Reset) }, placeholder = stringResource(id = CommonStrings.common_reason), - label = stringResource(id = CommonStrings.common_reason), content = stringResource(R.string.screen_bottom_sheet_manage_room_member_kick_member_confirmation_description), value = "", ) @@ -131,12 +132,13 @@ private fun RoomMemberAsyncActions( TextFieldDialog( title = stringResource(R.string.screen_bottom_sheet_manage_room_member_ban_member_confirmation_title), submitText = stringResource(R.string.screen_bottom_sheet_manage_room_member_ban_member_confirmation_action), + destructiveSubmit = true, + minLines = 2, onSubmit = { reason -> state.eventSink(InternalRoomMemberModerationEvents.DoBanUser(reason = reason)) }, onDismissRequest = { state.eventSink(InternalRoomMemberModerationEvents.Reset) }, placeholder = stringResource(id = CommonStrings.common_reason), - label = stringResource(id = CommonStrings.common_reason), content = stringResource(R.string.screen_bottom_sheet_manage_room_member_ban_member_confirmation_description), value = "", ) @@ -166,18 +168,22 @@ private fun RoomMemberAsyncActions( } when (val action = state.unbanUserAsyncAction) { is AsyncAction.Confirming -> { - ConfirmationDialog( + TextFieldDialog( title = stringResource(R.string.screen_bottom_sheet_manage_room_member_unban_member_confirmation_title), - content = stringResource(R.string.screen_bottom_sheet_manage_room_member_unban_member_confirmation_description), submitText = stringResource(R.string.screen_bottom_sheet_manage_room_member_unban_member_confirmation_action), - onSubmitClick = { + destructiveSubmit = true, + minLines = 2, + onSubmit = { reason -> val userDisplayName = selectedUser?.getBestName().orEmpty() asyncIndicatorState.enqueue { AsyncIndicator.Loading(text = stringResource(R.string.screen_bottom_sheet_manage_room_member_unbanning_user, userDisplayName)) } - state.eventSink(InternalRoomMemberModerationEvents.DoUnbanUser) + state.eventSink(InternalRoomMemberModerationEvents.DoUnbanUser(reason = reason)) }, - onDismiss = { state.eventSink(InternalRoomMemberModerationEvents.Reset) }, + onDismissRequest = { state.eventSink(InternalRoomMemberModerationEvents.Reset) }, + placeholder = stringResource(id = CommonStrings.common_reason), + content = stringResource(R.string.screen_bottom_sheet_manage_room_member_unban_member_confirmation_description), + value = "", ) } is AsyncAction.Failure -> { @@ -226,32 +232,34 @@ private fun RoomMemberActionsBottomSheet( avatarData = user.getAvatarData(size = AvatarSize.RoomListManageUser), avatarType = AvatarType.User, modifier = Modifier - .padding(bottom = 28.dp) - .align(Alignment.CenterHorizontally) + .padding(bottom = 24.dp) + .align(Alignment.CenterHorizontally) ) - user.displayName?.let { - Text( - text = it, - style = ElementTheme.typography.fontHeadingLgBold, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Center, - modifier = Modifier - .padding(start = 16.dp, end = 16.dp, bottom = 8.dp) - .fillMaxWidth() - ) - } + val bestName = user.getBestName() Text( - text = user.userId.value, - style = ElementTheme.typography.fontBodyLgRegular, - color = ElementTheme.colors.textSecondary, + text = bestName, + style = ElementTheme.typography.fontHeadingLgBold, maxLines = 1, overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Center, modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 8.dp) + .fillMaxWidth() + ) + // Show user ID only if it's different from the display name + if (bestName != user.userId.value) { + Text( + text = user.userId.value, + style = ElementTheme.typography.fontBodyMdRegular, + color = ElementTheme.colors.textSecondary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center, + modifier = Modifier .padding(horizontal = 16.dp) .fillMaxWidth() - ) + ) + } Spacer(modifier = Modifier.height(32.dp)) for (actionState in actions) { @@ -324,8 +332,8 @@ internal fun RoomMemberModerationViewPreview(@PreviewParameter(InternalRoomMembe ElementPreview { Box( modifier = Modifier - .fillMaxWidth() - .heightIn(min = 64.dp) + .fillMaxWidth() + .heightIn(min = 64.dp) ) { RoomMemberModerationView( state = state, diff --git a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/di/RoomMemberModerationModule.kt b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/di/RoomMemberModerationModule.kt index 9e4592d475..ab82f6ced4 100644 --- a/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/di/RoomMemberModerationModule.kt +++ b/features/roommembermoderation/impl/src/main/kotlin/io/element/android/features/roommembermoderation/impl/di/RoomMemberModerationModule.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roommembermoderation/impl/src/main/res/values-cs/translations.xml b/features/roommembermoderation/impl/src/main/res/values-cs/translations.xml index 263aa9a2e0..a7d8966384 100644 --- a/features/roommembermoderation/impl/src/main/res/values-cs/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-cs/translations.xml @@ -4,12 +4,14 @@ "Vykázat" "Nebudou se moci znovu připojit k této místnosti, pokud budou pozváni." "Jste si jisti, že chcete vykázat tohoto člena?" + "Pokud budou pozváni, nebudou moci vstoupit do tohoto prostoru, ale stále si zachovají členství ve všech místnostech nebo podprostorech." "Vykazování %1$s" "Odebrat" "Pokud budou pozváni, budou se moci do této místnosti znovu připojit." "Opravdu chcete tohoto člena odebrat?" + "Pokud budou pozváni, budou moci vstoupit do tohoto prostoru a zachovají si členství ve všech místnostech nebo podprostorech." "Zobrazit profil" - "Odebrat z místnosti" + "Odebrat uživatele" "Odebrat člena a zakázat mu připojení v budoucnu?" "Odstraňování %1$s…" "Zrušit vykázání z místnosti" diff --git a/features/roommembermoderation/impl/src/main/res/values-da/translations.xml b/features/roommembermoderation/impl/src/main/res/values-da/translations.xml index 00defeeec6..392c1e891a 100644 --- a/features/roommembermoderation/impl/src/main/res/values-da/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-da/translations.xml @@ -4,12 +4,14 @@ "Spær" "De vil ikke være i stand til at deltage i dette rum igen, selv om de inviteres." "Er du stikker på, at du ønsker at spærre dette medlem?" + "De vil ikke kunne deltage i gruppen igen, selv hvis de bliver inviteret. Men vil beholde deres medlemskaber i rum og undergrupper." "Spærrer %1$s" "Fjern" "De vil være i stand til at deltage i dette rum igen, hvis de inviteres." "Er du sikker på, at du vil fjerne dette medlem?" + "De vil kunne deltage i dette rum igen, hvis de bliver inviteret, og de vil stadig beholde deres medlemskaber af eventuelle rum eller sub-grupper." "Se profil" - "Fjern fra rummet" + "Fjern bruger" "Fjern medlem og udeluk dem fra at deltage i fremtiden?" "Fjerner %1$s…" "Fjern brugerens spærring fra rummet" diff --git a/features/roommembermoderation/impl/src/main/res/values-et/translations.xml b/features/roommembermoderation/impl/src/main/res/values-et/translations.xml index 1a0a3ec431..fd046163b7 100644 --- a/features/roommembermoderation/impl/src/main/res/values-et/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-et/translations.xml @@ -4,10 +4,12 @@ "Sea suhtluskeeld" "Ta ei saa selle jututoaga liituda isegi kutse olemasolul." "Kas sa oled kindel, et soovid sellele kasutajale seada suhtluskeelu?" + "Ta ei saa ka kutsumise puhul selle kogukonnaga uuesti liituda, kuid liikmelisus jututubades või alamkogukondades säilib." "Seame kasutajale %1$s suhtluskeelu" "Eemalda" "Kutse olemasolul saab ta nüüd jututoaga uuesti liituda" "Kas sa oled kindel, et soovid selle osaleja eemaldada?" + "Ta saab kutsumise puhul selle kogukonnaga uuesti liituda ning liikmelisus jututubades või alamkogukondades säilib." "Vaata profiili" "Eemalda kasutaja jututoast" "Kas eemaldama kasutaja ja seame talle tulevikuks suhtluskeelu?" diff --git a/features/roommembermoderation/impl/src/main/res/values-fa/translations.xml b/features/roommembermoderation/impl/src/main/res/values-fa/translations.xml index a5b7e75950..f17660f418 100644 --- a/features/roommembermoderation/impl/src/main/res/values-fa/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-fa/translations.xml @@ -12,6 +12,7 @@ "برداشتن از اتاق" "برداشتن عضو و تحریم پیوستن در آینده؟" "برداشتن %1$s…" + "تحریم نکردن از اتاق" "رفع انسداد" "رفع تحریم %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-fi/translations.xml b/features/roommembermoderation/impl/src/main/res/values-fi/translations.xml index 4c93f73780..1eb78cd2ae 100644 --- a/features/roommembermoderation/impl/src/main/res/values-fi/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-fi/translations.xml @@ -4,12 +4,14 @@ "Anna porttikielto" "He eivät voi enää liittyä tähän huoneeseen, jos heidät kutsutaan." "Haluatko varmasti antaa tälle jäsenelle porttikiellon?" + "He eivät voi liittyä tähän tilaan uudelleen, vaikka heidät kutsuttaisiin, mutta he säilyttävät jäsenyytensä muissa huoneissa tai alitiloissa." "Annetaan porttikieltoa käyttäjälle %1$s" "Poista" "He voivat liittyä tähän huoneeseen uudelleen, jos heidät kutsutaan." "Haluatko varmasti poistaa tämän jäsenen?" + "He voivat liittyä tähän tilaan uudelleen, jos heidät kutsutaan, ja he säilyttävät jäsenyytensä kaikissa huoneissa tai alitiloissa." "Näytä profiili" - "Poista huoneesta" + "Poista käyttäjä" "Poistetaanko jäsen huoneesta ja kielletäänkö heitä liittymästä tulevaisuudessa?" "Poistetaan käyttäjää %1$s huoneesta…" "Poista porttikielto huoneesta" diff --git a/features/roommembermoderation/impl/src/main/res/values-fr/translations.xml b/features/roommembermoderation/impl/src/main/res/values-fr/translations.xml index fa01ddb49a..427d6e32c7 100644 --- a/features/roommembermoderation/impl/src/main/res/values-fr/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-fr/translations.xml @@ -4,12 +4,14 @@ "Bannir" "Ce compte ne pourra pas rejoindre le salon à nouveau, même si il est invité." "Êtes-vous certain de vouloir bannir ce membre ?" + "L’utilisateur ne pourra plus accéder à cet espace même sur invitation, mais il restera membre de tous les salons et sous-espaces dont il est déjà membre." "Bannissement de %1$s" "Retirer" "Il pourra rejoindre le salon à nouveau si il est invité." "Voulez-vous vraiment supprimer ce membre ?" + "L’utilisateur pourra rejoindre cet espace à nouveau s’il y est invité, et il restera membre de tous les salons et sous-espaces." "Voir le profil" - "Retirer le membre du salon" + "Exclure ce membre" "Retirer le membre et interdire l’adhésion à l’avenir ?" "Enlever %1$s…" "Débannir du salon" diff --git a/features/roommembermoderation/impl/src/main/res/values-hr/translations.xml b/features/roommembermoderation/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..48185160db --- /dev/null +++ b/features/roommembermoderation/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,22 @@ + + + "Zabrani korisnika" + "Zabrani" + "Neće se moći ponovno pridružiti ako budu pozvani." + "Jeste li sigurni da želite zabraniti pristup ovom članu?" + "Neće se moći ponovno pridružiti ovom prostoru ako budu pozvani, ali će i dalje zadržati članstvo u svim sobama ili podprostorima." + "Zabranjuje se pristup korisniku %1$s" + "Ukloni" + "Moći će se ponovno pridružiti ovoj sobi ako budu pozvani." + "Jeste li sigurni da želite ukloniti ovog člana?" + "Moći će se ponovno pridružiti ovom prostoru ako budu pozvani, a i dalje će zadržati članstvo u svim sobama ili podprostorima." + "Prikaži profil" + "Ukloni korisnika" + "Želite li ukloniti člana i zabraniti mu da se ubuduće pridruži?" + "Uklanjanje člana %1$s…" + "Poništi zabranu pristupa korisniku" + "Poništi zabranu" + "Mogli bi se ponovno pridružiti ako budu pozvani" + "Jeste li sigurni da želite poništiti zabranu pristupa ovom članu?" + "Uklanja se zabrana korisniku %1$s" + diff --git a/features/roommembermoderation/impl/src/main/res/values-hu/translations.xml b/features/roommembermoderation/impl/src/main/res/values-hu/translations.xml index 4c68806e7d..63fc984bcf 100644 --- a/features/roommembermoderation/impl/src/main/res/values-hu/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-hu/translations.xml @@ -4,12 +4,14 @@ "Kitiltás" "Többé nem csatlakozhat ehhez a szobához, akkor sem, ha meghívják." "Biztos, hogy kitiltja ezt a tagot?" + "Ha meghívják őket akkor sem tudnak újra csatlakozni ehhez a térhez, de továbbra is megtartják tagságukat a szobáikban vagy altereikben." "%1$s kitiltása" "Eltávolítás" "Ehhez a szobához is csatlakozhat, ha meghívják." "Biztos, hogy eltávolítja ezt a tagot?" + "Ha meghívják őket újra csatlakozhatnak ehhez a térhez, és továbbra is megtartják a tagságukat a szobáikban vagy altereikben." "Profil megtekintése" - "Eltávolítás a szobából" + "Felhasználó eltávolítása" "Eltávolítja a tagot, és megtiltja a jövőbeni csatlakozást?" "%1$s eltávolítása…" "Visszaengedés a szobába" diff --git a/features/roommembermoderation/impl/src/main/res/values-it/translations.xml b/features/roommembermoderation/impl/src/main/res/values-it/translations.xml index 7791d04546..4e423e2951 100644 --- a/features/roommembermoderation/impl/src/main/res/values-it/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-it/translations.xml @@ -4,12 +4,14 @@ "Escludi" "Non potrà entrare nuovamente in questa stanza se invitato." "Vuoi davvero escludere questo membro?" + "Se invitati, non potranno più unirsi a questo spazio, ma manterranno comunque la loro iscrizione a tutte le stanze o sottospazi." "Esclusione di %1$s" "Rimuovi" "Potrà entrare nuovamente in questa stanza se invitato." "Sei sicuro di voler rimuovere questo membro?" + "Potranno unirsi nuovamente a questo spazio se invitati e manterranno comunque la loro iscrizione a tutte le stanze o sottospazi." "Visualizza profilo" - "Rimuovi dalla stanza" + "Rimuovi utente" "Rimuovere e vietare l\'accesso in futuro?" "Rimozione di %1$s…" "Riammetti nella stanza" diff --git a/features/roommembermoderation/impl/src/main/res/values-nb/translations.xml b/features/roommembermoderation/impl/src/main/res/values-nb/translations.xml index 4507fce49c..392b271a1a 100644 --- a/features/roommembermoderation/impl/src/main/res/values-nb/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-nb/translations.xml @@ -9,7 +9,7 @@ "De vil kunne bli med i dette rommet igjen hvis de blir invitert." "Er du sikker på at du vil fjerne dette medlemmet?" "Vis profil" - "Fjern fra rommet" + "Fjern bruker" "Fjerne medlem og utestenge fra å bli med i fremtiden?" "Fjerner %1$s…" "Fjern utestengelsen fra rommet" diff --git a/features/roommembermoderation/impl/src/main/res/values-pt-rBR/translations.xml b/features/roommembermoderation/impl/src/main/res/values-pt-rBR/translations.xml index 4cbc0ba081..5a356e18f6 100644 --- a/features/roommembermoderation/impl/src/main/res/values-pt-rBR/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-pt-rBR/translations.xml @@ -4,12 +4,14 @@ "Banir" "Essa pessoa não poderá entrar nesta sala novamente se for convidada." "Tem certeza de que quer banir este membro?" + "Eles não poderão mais entrar no espaço novamente se forem convidados, mas manterão sua participação em quaisquer salas e sub-espaços." "Banindo %1$s" "Remover" "Esta pessoa poderá entrar nesta sala novamente se for convidada." "Tem certeza de que deseja remover este membro?" + "Eles poderão entrar no espaço novamente se convidados, e manterão sua participação em quaisquer salas e sub-espaços." "Ver perfil" - "Remover da sala" + "Remover usuário" "Remover membro e banir de entrar novamente no futuro?" "Removendo %1$s…" "Desbanir da sala" diff --git a/features/roommembermoderation/impl/src/main/res/values-pt/translations.xml b/features/roommembermoderation/impl/src/main/res/values-pt/translations.xml index 3989c4de7b..27113593ed 100644 --- a/features/roommembermoderation/impl/src/main/res/values-pt/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-pt/translations.xml @@ -9,7 +9,7 @@ "Poderão juntar-se novamente a esta sala se forem convidados." "Tens certeza que queres remover este membro?" "Ver perfil" - "Remover da sala" + "Remover utilizador" "Remover participante e proibir que entre no futuro?" "A remover %1$s…" "Desbanir da sala" diff --git a/features/roommembermoderation/impl/src/main/res/values-ro/translations.xml b/features/roommembermoderation/impl/src/main/res/values-ro/translations.xml index 4811b5263f..9f21c8d351 100644 --- a/features/roommembermoderation/impl/src/main/res/values-ro/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-ro/translations.xml @@ -4,10 +4,12 @@ "Interzicere" "Nu se vor putea alătura din nou acestei camere dacă sunt invitați." "Sunteți sigur că doriți să interziceți acest membru?" + "Nu vor putea să se alăture din nou acestui spațiu dacă sunt invitați, dar își vor păstra în continuare calitatea de membru al oricăror camere sau subspații." "Se interzice %1$s" "Îndepărtați" "Se vor putea alătura din nou acestei săli dacă sunt invitați." "Sunteți sigur că doriți să îndepărtați acest membru?" + "Vor putea să se alăture din nou acestui spațiu dacă sunt invitați și își vor păstra în continuare calitatea de membru al oricăror camere sau subspații." "Vizualizare profil" "Înlăturați membrul" "Înlăturați membrul și interziceți-i să se alăture în viitor?" diff --git a/features/roommembermoderation/impl/src/main/res/values-ru/translations.xml b/features/roommembermoderation/impl/src/main/res/values-ru/translations.xml index 6d89f42fe1..7e1691a089 100644 --- a/features/roommembermoderation/impl/src/main/res/values-ru/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-ru/translations.xml @@ -4,10 +4,12 @@ "Заблокировать" "Они не смогут снова присоединиться к этой комнате, если их пригласят." "Вы уверены, что хотите заблокировать этого участника?" + "Они не смогут снова присоединиться к этому пространству, если их пригласят, но они по-прежнему сохранят свое членство в любых комнатах или подпространствах." "Блокировка %1$s" "Удалить" "Они снова смогут присоединиться в эту комнату если их пригласят." "Вы действительно хотите удалить этого участника?" + "Они смогут снова присоединиться к этому пространству, если их пригласят и сохранят свое членство во всех комнатах или подпространствах." "Посмотреть профиль" "Удалить участника из комнаты" "Удалить участника и запретить присоединяться в будущем?" diff --git a/features/roommembermoderation/impl/src/main/res/values-sk/translations.xml b/features/roommembermoderation/impl/src/main/res/values-sk/translations.xml index ffdd634b0b..c852e7ab29 100644 --- a/features/roommembermoderation/impl/src/main/res/values-sk/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-sk/translations.xml @@ -4,12 +4,14 @@ "Zakázať" "Nebudú sa môcť pripojiť k tejto miestnosti znova ani ak budú pozvaní." "Ste si istý, že chcete zakázať tohto člena?" + "Ak dostanú pozvánku, nebudú sa môcť k tomuto priestoru znova pripojiť, ale stále si ponechajú členstvo vo všetkých miestnostiach alebo podpriestoroch." "Zakazuje sa %1$s" "Odstrániť" "V prípade pozvania sa budú môcť znova pripojiť k tejto miestnosti." "Ste si istý, že chcete odstrániť tohto člena?" + "Ak dostanú pozvánku, budú sa môcť k tomuto priestoru znova pripojiť a stále si ponechajú členstvo vo všetkých miestnostiach alebo podpriestoroch." "Zobraziť profil" - "Odstrániť z miestnosti" + "Odstrániť používateľa" "Odstrániť člena a zakázať vstup v budúcnosti?" "Odstraňuje sa %1$s…" "Zrušiť zákaz prístupu do miestnosti" diff --git a/features/roommembermoderation/impl/src/main/res/values-uz/translations.xml b/features/roommembermoderation/impl/src/main/res/values-uz/translations.xml index d7b09f0e8d..9b8c57ede3 100644 --- a/features/roommembermoderation/impl/src/main/res/values-uz/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-uz/translations.xml @@ -5,11 +5,16 @@ "Taklif qilingan taqdirda ham, ular bu xonaga boshqa qo‘shila olmaydilar." "Haqiqatan ham bu aʼzoni taqiqlamoqchimisiz?" "Taqiqlash %1$s" + "Oʻchirish" "Agar taklif qilinsa, ular bu xonaga qayta qo‘shilishlari mumkin." + "Haqiqatan ham bu a’zoni olib tashlaysizmi?" "Profilni koʻrish" - "Xonadan olib tashlash" + "Foydalanuvchini olib tashlash" "Aʻzo oʻchirilsinmi va kelgusida qoʻshilish taqiqlansinmi?" "Oʻchirish %1$s …" + "Xonadan taqiqni olib tashlash" "Taqiqni bekor qilish" + "Agar taklif qilinsa, ular xonaga yana qo‘shilishlari mumkin" + "Haqiqatan ham bu a’zoni blokdan chiqarmoqchimisiz?" "Taqiqni bekor qilish %1$s" diff --git a/features/roommembermoderation/impl/src/main/res/values-zh-rTW/translations.xml b/features/roommembermoderation/impl/src/main/res/values-zh-rTW/translations.xml index de8a9712ee..14a044a7dc 100644 --- a/features/roommembermoderation/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-zh-rTW/translations.xml @@ -4,12 +4,14 @@ "加入黑名單" "即使收到邀請,他們仍然無法加入聊天室。" "您確定要將此成員加入黑名單?" + "即使被邀請,他們也無法再次加入此空間,但他們仍將保留其在任何聊天室或子空間的成員資格。" "正在將 %1$s 加入黑名單" "移除" "如果收到邀請,他們能再次加入聊天室。" "您真的想要移除此成員嗎?" + "若受邀,他們將可以再次加入此空間,並保留所有聊天室與子空間的成員資格。" "查看個人檔案" - "踢出聊天室" + "移除使用者" "移除成員並禁止未來再度加入?" "正在踢出 %1$s…" "從聊天室解除封鎖" diff --git a/features/roommembermoderation/impl/src/main/res/values-zh/translations.xml b/features/roommembermoderation/impl/src/main/res/values-zh/translations.xml index a27d2ded05..aa8b2e3df8 100644 --- a/features/roommembermoderation/impl/src/main/res/values-zh/translations.xml +++ b/features/roommembermoderation/impl/src/main/res/values-zh/translations.xml @@ -4,12 +4,14 @@ "封禁" "即使受到邀请,他们也无法再次加入聊天室。" "您确定要封禁该成员吗?" + "即使再次受邀,他们也无法加入这个空间,但他们仍将保留其在任何房间或子空间的成员资格。" "封禁 %1$s" "移除" "如果受到邀请,他们可以重新加入聊天室。" "您确定要移除此成员吗?" + "如果受到邀请,他们将能够再次加入这个空间,并且他们仍将保留其在任何房间或子空间的成员资格。" "查看个人资料" - "从聊天室移除" + "移除用户" "删除成员并禁止重新加入?" "正在移除 %1$s……" "从房间取消解封" diff --git a/features/roommembermoderation/impl/src/main/res/values/localazy.xml b/features/roommembermoderation/impl/src/main/res/values/localazy.xml index 16b013d537..3d23c8763a 100644 --- a/features/roommembermoderation/impl/src/main/res/values/localazy.xml +++ b/features/roommembermoderation/impl/src/main/res/values/localazy.xml @@ -1,20 +1,22 @@ - "Ban from room" + "Ban user" "Ban" - "They won’t be able to join this room again if invited." + "They won’t be able to join again if invited." "Are you sure you want to ban this member?" + "They won’t be able to join this space again if invited, but they’ll still keep their memberships of any rooms or subspaces." "Banning %1$s" "Remove" "They will be able to join this room again if invited." "Are you sure you want to remove this member?" + "They will be able to join this space again if invited, and they’ll still keep their memberships of any rooms or subspaces." "View profile" - "Remove from room" + "Remove user" "Remove member and ban from joining in the future?" "Removing %1$s…" - "Unban from room" + "Unban user" "Unban" - "They would be able to join the room again if invited" + "They would be able to join again if invited" "Are you sure you want to unban this member?" "Unbanning %1$s" diff --git a/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt index 40e09b3620..3397f3a40a 100644 --- a/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt +++ b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationPresenterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,6 +13,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.ModerationActionState import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents +import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.core.coroutine.CoroutineDispatchers @@ -19,17 +21,22 @@ import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembersState import io.element.android.libraries.matrix.api.room.RoomMembershipState +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom +import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomMember +import io.element.android.libraries.matrix.test.room.defaultRoomPowerLevelValues +import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.test import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest @@ -47,8 +54,7 @@ class RoomMemberModerationPresenterTest { val room = aJoinedRoom() createRoomMemberModerationPresenter(room = room).test { val initialState = awaitState() - assertThat(initialState.canKick).isFalse() - assertThat(initialState.canBan).isFalse() + assertThat(initialState.permissions).isEqualTo(RoomMemberModerationPermissions.DEFAULT) assertThat(initialState.selectedUser).isNull() assertThat(initialState.banUserAsyncAction).isEqualTo(AsyncAction.Uninitialized) assertThat(initialState.kickUserAsyncAction).isEqualTo(AsyncAction.Uninitialized) @@ -159,7 +165,6 @@ class RoomMemberModerationPresenterTest { assertThat(updatedState.selectedUser).isEqualTo(targetUser) assertThat(updatedState.actions).containsExactly( ModerationActionState(action = ModerationAction.DisplayProfile, isEnabled = true), - ModerationActionState(action = ModerationAction.KickUser, isEnabled = false), ModerationActionState(action = ModerationAction.UnbanUser, isEnabled = true), ) } @@ -221,9 +226,11 @@ class RoomMemberModerationPresenterTest { val room = aJoinedRoom() room.baseRoom.givenUpdateMembersResult { // Simulate the member list being updated - room.givenRoomMembersState(RoomMembersState.Ready( - persistentListOf(aRoomMember()) - )) + room.givenRoomMembersState( + RoomMembersState.Ready( + persistentListOf(aRoomMember()) + ) + ) } createRoomMemberModerationPresenter(room = room).test { val initialState = awaitState() @@ -249,9 +256,11 @@ class RoomMemberModerationPresenterTest { val room = aJoinedRoom() room.baseRoom.givenUpdateMembersResult { // Simulate the member list being updated - room.givenRoomMembersState(RoomMembersState.Ready( - persistentListOf(aRoomMember()) - )) + room.givenRoomMembersState( + RoomMembersState.Ready( + persistentListOf(aRoomMember()) + ) + ) } createRoomMemberModerationPresenter(room = room).test { val initialState = awaitState() @@ -277,9 +286,11 @@ class RoomMemberModerationPresenterTest { val room = aJoinedRoom() room.baseRoom.givenUpdateMembersResult { // Simulate the member list being updated - room.givenRoomMembersState(RoomMembersState.Ready( - persistentListOf(aRoomMember()) - )) + room.givenRoomMembersState( + RoomMembersState.Ready( + persistentListOf(aRoomMember()) + ) + ) } createRoomMemberModerationPresenter(room = room).test { val initialState = awaitState() @@ -290,7 +301,7 @@ class RoomMemberModerationPresenterTest { ) ) skipItems(2) - initialState.eventSink(InternalRoomMemberModerationEvents.DoUnbanUser) + initialState.eventSink(InternalRoomMemberModerationEvents.DoUnbanUser("Reason")) skipItems(1) val loadingState = awaitState() assertThat(loadingState.unbanUserAsyncAction).isInstanceOf(AsyncAction.Loading::class.java) @@ -354,10 +365,18 @@ class RoomMemberModerationPresenterTest { banUserResult = { _, _ -> banUserResult }, unBanUserResult = { _, _ -> unBanUserResult }, baseRoom = FakeBaseRoom( - canBanResult = { _ -> Result.success(canBan) }, - canKickResult = { _ -> Result.success(canKick) }, + roomPermissions = FakeRoomPermissions( + canBan = canBan, + canKick = canKick + ), userRoleResult = { Result.success(myUserRole) }, - updateMembersResult = { Result.success(Unit) } + updateMembersResult = { Result.success(Unit) }, + initialRoomInfo = aRoomInfo( + roomPowerLevels = RoomPowerLevels( + values = defaultRoomPowerLevelValues(), + users = persistentMapOf(A_USER_ID to myUserRole.powerLevel) + ) + ) ), ).apply { val roomMembers = listOfNotNull(targetRoomMember).toImmutableList() diff --git a/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationViewTest.kt b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationViewTest.kt index fc1b815562..6508b28053 100644 --- a/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationViewTest.kt +++ b/features/roommembermoderation/impl/src/test/kotlin/io/element/android/features/roommembermoderation/impl/RoomMemberModerationViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -181,7 +182,7 @@ class RoomMemberModerationViewTest { ), ) rule.pressTag(TestTags.dialogPositive.value) - eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.DoUnbanUser) + eventsRecorder.assertSingle(InternalRoomMemberModerationEvents.DoUnbanUser("")) } @Test diff --git a/features/securebackup/api/build.gradle.kts b/features/securebackup/api/build.gradle.kts index f4bf5bd4b9..2c8fa42d5b 100644 --- a/features/securebackup/api/build.gradle.kts +++ b/features/securebackup/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/api/src/main/kotlin/io/element/android/features/securebackup/api/SecureBackupEntryPoint.kt b/features/securebackup/api/src/main/kotlin/io/element/android/features/securebackup/api/SecureBackupEntryPoint.kt index 4fb2ab00cc..9c9d612b8f 100644 --- a/features/securebackup/api/src/main/kotlin/io/element/android/features/securebackup/api/SecureBackupEntryPoint.kt +++ b/features/securebackup/api/src/main/kotlin/io/element/android/features/securebackup/api/SecureBackupEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -32,15 +33,14 @@ interface SecureBackupEntryPoint : FeatureEntryPoint { data class Params(val initialElement: InitialTarget) : NodeInputs - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder + fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: Params, + callback: Callback, + ): Node interface Callback : Plugin { fun onDone() } - - interface NodeBuilder { - fun params(params: Params): NodeBuilder - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } } diff --git a/features/securebackup/impl/build.gradle.kts b/features/securebackup/impl/build.gradle.kts index 92a6013580..b6117271f7 100644 --- a/features/securebackup/impl/build.gradle.kts +++ b/features/securebackup/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/DefaultSecureBackupEntryPoint.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/DefaultSecureBackupEntryPoint.kt index 6d5358299b..46d93da152 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/DefaultSecureBackupEntryPoint.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/DefaultSecureBackupEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,33 +10,22 @@ package io.element.android.features.securebackup.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.securebackup.api.SecureBackupEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultSecureBackupEntryPoint : SecureBackupEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): SecureBackupEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : SecureBackupEntryPoint.NodeBuilder { - override fun params(params: SecureBackupEntryPoint.Params): SecureBackupEntryPoint.NodeBuilder { - plugins += params - return this - } - - override fun callback(callback: SecureBackupEntryPoint.Callback): SecureBackupEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: SecureBackupEntryPoint.Params, + callback: SecureBackupEntryPoint.Callback, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf(params, callback) + ) } } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/LoggerTag.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/LoggerTag.kt index b6233044de..fe2460760f 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/LoggerTag.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/LoggerTag.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt index 4d79f8ea1b..d9fd8a1785 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/SecureBackupFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,6 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push @@ -29,6 +29,7 @@ import io.element.android.features.securebackup.impl.setup.SecureBackupSetupNode import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.appyx.canPop +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope import kotlinx.parcelize.Parcelize @@ -71,25 +72,25 @@ class SecureBackupFlowNode( data object ResetIdentity : NavTarget } - private val callbacks = plugins() + private val callback: SecureBackupEntryPoint.Callback = callback() override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Root -> { val callback = object : SecureBackupRootNode.Callback { - override fun onSetupClick() { + override fun navigateToSetup() { backstack.push(NavTarget.Setup) } - override fun onChangeClick() { + override fun navigateToChange() { backstack.push(NavTarget.Change) } - override fun onDisableClick() { + override fun navigateToDisable() { backstack.push(NavTarget.Disable) } - override fun onConfirmRecoveryKeyClick() { + override fun navigateToEnterRecoveryKey() { backstack.push(NavTarget.EnterRecoveryKey) } } @@ -116,7 +117,7 @@ class SecureBackupFlowNode( if (backstack.canPop()) { backstack.pop() } else { - callbacks.forEach { it.onDone() } + callback.onDone() } } } @@ -125,7 +126,7 @@ class SecureBackupFlowNode( is NavTarget.ResetIdentity -> { val callback = object : ResetIdentityFlowNode.Callback { override fun onDone() { - callbacks.forEach { it.onDone() } + callback.onDone() } } createNode(buildContext, listOf(callback)) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableEvents.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableEvents.kt index 1e25962a1b..05d05a4840 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableEvents.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableNode.kt index 8af4f3e613..6877893e83 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenter.kt index aa1de8fde9..67b6816097 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -36,7 +37,7 @@ class SecureBackupDisablePresenter( Timber.tag(loggerTagDisable.value).d("backupState: $backupState") val disableAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val coroutineScope = rememberCoroutineScope() - fun handleEvents(event: SecureBackupDisableEvents) { + fun handleEvent(event: SecureBackupDisableEvents) { when (event) { is SecureBackupDisableEvents.DisableBackup -> coroutineScope.disableBackup(disableAction) SecureBackupDisableEvents.DismissDialogs -> { @@ -49,7 +50,7 @@ class SecureBackupDisablePresenter( backupState = backupState, disableAction = disableAction.value, appName = buildMeta.applicationName, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableState.kt index 7e98265441..a4f6b210ab 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableState.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableStateProvider.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableStateProvider.kt index 5b0a1bdc2e..6aa10d9a0b 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableStateProvider.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableView.kt index 3042702d04..24678148b4 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisableView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyEvents.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyEvents.kt index d503690e48..644756b957 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyEvents.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyNode.kt index 77d1fe8f32..81b06e1719 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,10 +13,10 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.SessionScope @ContributesNode(SessionScope::class) @@ -29,7 +30,7 @@ class SecureBackupEnterRecoveryKeyNode( fun onEnterRecoveryKeySuccess() } - private val callback = plugins().first() + private val callback: Callback = callback() @Composable override fun View(modifier: Modifier) { diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenter.kt index b56a4542b0..9a71cf1727 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -44,7 +45,7 @@ class SecureBackupEnterRecoveryKeyPresenter( mutableStateOf(AsyncAction.Uninitialized) } - fun handleEvents(event: SecureBackupEnterRecoveryKeyEvents) { + fun handleEvent(event: SecureBackupEnterRecoveryKeyEvents) { when (event) { SecureBackupEnterRecoveryKeyEvents.ClearDialog -> { submitAction.value = AsyncAction.Uninitialized @@ -78,7 +79,7 @@ class SecureBackupEnterRecoveryKeyPresenter( ), isSubmitEnabled = recoveryKey.isNotEmpty() && submitAction.value.isUninitialized(), submitAction = submitAction.value, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyState.kt index 60b73b37a3..45a6989c15 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyState.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.features.securebackup.impl.enter import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyViewState import io.element.android.libraries.architecture.AsyncAction -// Do not use default value, so no member get forgotten in the presenters. data class SecureBackupEnterRecoveryKeyState( val recoveryKeyViewState: RecoveryKeyViewState, val isSubmitEnabled: Boolean, diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyStateProvider.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyStateProvider.kt index 6296986317..5e95b5713e 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyStateProvider.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt index 3d4434d3df..8b2bc4dc90 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManager.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManager.kt index 4ffaa9c344..dce1a35ac7 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManager.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManager.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowNode.kt index dfc9425ebe..297fd538d1 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -20,7 +21,6 @@ import androidx.lifecycle.LifecycleOwner import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.push import dev.zacsweers.metro.Assisted @@ -33,6 +33,7 @@ import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTa import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.di.SessionScope @@ -63,6 +64,8 @@ class ResetIdentityFlowNode( fun onDone() } + private val callback: Callback = callback() + sealed interface NavTarget : Parcelable { @Parcelize data object Root : NavTarget @@ -86,7 +89,7 @@ class ResetIdentityFlowNode( cancelResetJob() resetIdentityFlowManager.whenResetIsDone { - plugins().forEach { it.onDone() } + callback.onDone() } } } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordEvent.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordEvent.kt index e96240deab..8fd008fb04 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordEvent.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordEvent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordNode.kt index 3c22673bff..539b18584c 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordPresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordPresenter.kt index 26a6ae1fac..8f99fc205a 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordPresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -39,7 +40,7 @@ class ResetIdentityPasswordPresenter( return ResetIdentityPasswordState( resetAction = resetAction.value, - eventSink = ::handleEvent + eventSink = ::handleEvent, ) } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordState.kt index 4042dd52ce..87ee5e1674 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordState.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordStateProvider.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordStateProvider.kt index 368560174c..fc4ce2430e 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordStateProvider.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt index 998b46046c..8d32433ac5 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootEvent.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootEvent.kt index de6cf0cf5f..8da89e588e 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootEvent.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootEvent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootNode.kt index 8267242f97..057360c2b9 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,6 +16,7 @@ import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.SessionScope @ContributesNode(SessionScope::class) @@ -27,8 +29,8 @@ class ResetIdentityRootNode( fun onContinue() } + private val callback: Callback = callback() private val presenter = ResetIdentityRootPresenter() - private val callback: Callback = plugins.filterIsInstance().first() @Composable override fun View(modifier: Modifier) { diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootPresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootPresenter.kt index 909cdc1379..90fe89b952 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootPresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -28,7 +29,7 @@ class ResetIdentityRootPresenter : Presenter { return ResetIdentityRootState( displayConfirmationDialog = displayConfirmDialog, - eventSink = ::handleEvent + eventSink = ::handleEvent, ) } } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootState.kt index 7a502e743d..994f106424 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootState.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootStateProvider.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootStateProvider.kt index 3d3e8cab59..697401367f 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootStateProvider.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootView.kt index 9e4a5e1b62..97c82f446d 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootEvents.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootEvents.kt index 512d549933..9d371e3c30 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootEvents.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootNode.kt index 6d4db197d3..329a406381 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,11 +15,11 @@ import androidx.compose.ui.platform.UriHandler import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.appconfig.LearnMoreConfig +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.SessionScope @ContributesNode(SessionScope::class) @@ -32,27 +33,13 @@ class SecureBackupRootNode( plugins = plugins ) { interface Callback : Plugin { - fun onSetupClick() - fun onChangeClick() - fun onDisableClick() - fun onConfirmRecoveryKeyClick() + fun navigateToSetup() + fun navigateToChange() + fun navigateToDisable() + fun navigateToEnterRecoveryKey() } - private fun onSetupClick() { - plugins().forEach { it.onSetupClick() } - } - - private fun onChangeClick() { - plugins().forEach { it.onChangeClick() } - } - - private fun onDisableClick() { - plugins().forEach { it.onDisableClick() } - } - - private fun onConfirmRecoveryKeyClick() { - plugins().forEach { it.onConfirmRecoveryKeyClick() } - } + private val callback: Callback = callback() private fun onLearnMoreClick(uriHandler: UriHandler) { uriHandler.openUri(LearnMoreConfig.SECURE_BACKUP_URL) @@ -65,10 +52,10 @@ class SecureBackupRootNode( SecureBackupRootView( state = state, onBackClick = ::navigateUp, - onSetupClick = ::onSetupClick, - onChangeClick = ::onChangeClick, - onDisableClick = ::onDisableClick, - onConfirmRecoveryKeyClick = ::onConfirmRecoveryKeyClick, + onSetupClick = callback::navigateToSetup, + onChangeClick = callback::navigateToChange, + onDisableClick = callback::navigateToDisable, + onConfirmRecoveryKeyClick = callback::navigateToEnterRecoveryKey, onLearnMoreClick = { onLearnMoreClick(uriHandler) }, modifier = modifier, ) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenter.kt index 67ec1c9960..9a97bfac25 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -58,7 +59,7 @@ class SecureBackupRootPresenter( } } - fun handleEvents(event: SecureBackupRootEvents) { + fun handleEvent(event: SecureBackupRootEvents) { when (event) { SecureBackupRootEvents.RetryKeyBackupState -> localCoroutineScope.getKeyBackupStatus(doesBackupExistOnServerAction) SecureBackupRootEvents.EnableKeyStorage -> localCoroutineScope.enableBackup(enableAction) @@ -78,7 +79,7 @@ class SecureBackupRootPresenter( appName = buildMeta.applicationName, displayKeyStorageDisabledError = displayKeyStorageDisabledError, snackbarMessage = snackbarMessage, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt index fb9e6422a7..1cbf917ca5 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateProvider.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateProvider.kt index 3a502cf582..5dc3bea7e8 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateProvider.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt index 904f326120..0aff81aafa 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupEvents.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupEvents.kt index ecf0ec3711..f61e65ba61 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupEvents.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupNode.kt index 6adcb890ae..e0f57cb9f0 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenter.kt index 58a6c4b43c..9f27bc9566 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -52,7 +53,7 @@ class SecureBackupSetupPresenter( } var showSaveConfirmationDialog by remember { mutableStateOf(false) } - fun handleEvents(event: SecureBackupSetupEvents) { + fun handleEvent(event: SecureBackupSetupEvents) { when (event) { SecureBackupSetupEvents.CreateRecoveryKey -> { coroutineScope.createOrChangeRecoveryKey(stateAndDispatch) @@ -81,7 +82,7 @@ class SecureBackupSetupPresenter( recoveryKeyViewState = recoveryKeyViewState, setupState = setupState, showSaveConfirmationDialog = showSaveConfirmationDialog, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupState.kt index 91f710d07d..752b5e4851 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupState.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.features.securebackup.impl.setup import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyViewState -// Do not use default value, so no member get forgotten in the presenters. data class SecureBackupSetupState( val isChangeRecoveryKeyUserStory: Boolean, val recoveryKeyViewState: RecoveryKeyViewState, diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupStateMachine.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupStateMachine.kt index 24a8be7f77..150aeeddc7 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupStateMachine.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupStateMachine.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupStateProvider.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupStateProvider.kt index f414e6e8e7..a780fa0c36 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupStateProvider.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupView.kt index 188c87e513..497c64f072 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -115,11 +116,12 @@ private fun Content( ) { val context = LocalContext.current val formattedRecoveryKey = state.recoveryKeyViewState.formattedRecoveryKey + val toastMessage = stringResource(R.string.screen_recovery_key_copied_to_clipboard) val clickLambda = if (formattedRecoveryKey != null) { { context.copyToClipboard( - formattedRecoveryKey, - context.getString(R.string.screen_recovery_key_copied_to_clipboard) + text = formattedRecoveryKey, + toastMessage = toastMessage, ) state.eventSink.invoke(SecureBackupSetupEvents.RecoveryKeyHasBeenSaved) } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupViewChangePreview.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupViewChangePreview.kt index 39d9646061..39fa7debe9 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupViewChangePreview.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupViewChangePreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt index c9ef2886bf..198891473c 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyViewState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyViewState.kt index 46bb957492..8a8be03b88 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyViewState.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyViewState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyViewStateProvider.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyViewStateProvider.kt index e34300a219..c1a841aa7b 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyViewStateProvider.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/setup/views/RecoveryKeyViewStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyTools.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyTools.kt index 5805146c71..76afd45af5 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyTools.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyTools.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyVisualTransformation.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyVisualTransformation.kt index e82d9ead1c..5447b79a43 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyVisualTransformation.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyVisualTransformation.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/main/res/values-eo/translations.xml b/features/securebackup/impl/src/main/res/values-eo/translations.xml deleted file mode 100644 index 4b8e1833a3..0000000000 --- a/features/securebackup/impl/src/main/res/values-eo/translations.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - "Delete message backup" - "Store your account security and messages securely on the server. This will allow you to view your message history on any new devices. %1$s." - "Message backup" - "Turn on message backup to set it up." - "Upload messages from this device" - "Allow message backup" - "Change backup password" - "Restore your account security and message history with a backup password if you\'ve lost all your existing devices." - "Enter backup password" - "Your message backup is currently out of sync." - "Set up backup" - "When asked to confirm your device, select %1$s" - "Follow the instructions to create a new backup password" - "Save your new backup password in a password manager or encrypted note" - "You will need to confirm all your existing devices and verify contacts again" - "Only start fresh if you don\'t have access to another signed-in device and you\'ve lost your backup password." - "Can\'t confirm? You\'ll need to start fresh." - "Deleting message backup will remove your account security and messages from the server and turn off the following security features:" - "Are you sure you want to turn off message backup and delete it?" - "Get a new backup password if you\'ve lost your existing one. After changing your backup password, your old one will no longer work." - "Generate a new backup password" - "Backup password changed" - "Change backup password?" - "Create new backup password" - "Please try again to confirm access to your message backup." - "Incorrect backup password" - "You might have seen the terms \"recovery key\", \"security key\" or \"security phrase\" instead of \"backup password\". Don\'t worry, this is all the same." - "Lost your backup password?" - "Backup password confirmed" - "Enter your backup password" - "Copied backup password" - "Save backup password" - "Write down this backup password somewhere safe, like a password manager, encrypted note, or a physical safe." - "Tap to copy backup password" - "Save your backup password somewhere safe" - "You will not be able to access your new backup password after this step." - "Have you saved your backup password?" - "Your message backup is protected by a backup password. If you need a new backup password after setup, you can recreate it by selecting ‘Change backup password’." - "Generate your backup password" - "Backup setup successful" - "Set up backup" - "Are you sure you want to start fresh?" - "Confirm that you want to start fresh." - diff --git a/features/securebackup/impl/src/main/res/values-hr/translations.xml b/features/securebackup/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..dcdfe830a7 --- /dev/null +++ b/features/securebackup/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,70 @@ + + + "Brisanje pohrane ključeva" + "Uključivanje sigurnosnog kopiranja" + "Sigurno pohranite svoj kriptografski identitet i ključeve poruka na poslužitelju. To će vam omogućiti pregled povijesti poruka na svim novim uređajima. %1$s." + "Pohrana ključeva" + "Za postavljanje oporavka mora biti uključena pohrana ključeva." + "Prenesi ključeve s ovog uređaja" + "Dopusti pohranu ključeva" + "Promjena ključa za oporavak" + "Ako ste izgubili sve postojeće uređaje, oporavite svoj kriptografski identitet i povijest poruka pomoću ključa za oporavak." + "Unesi ključ za oporavak" + "Vaša pohrana ključeva trenutačno nije sinkronizirana." + "Postavljanje oporavka" + "Pristupite svojim šifriranim porukama ako se odjavite iz aplikacije s%1$s sa svih uređaja ili ih izgubite." + "Otvorite %1$s na stolnom uređaju" + "Ponovno se prijavite na svoj račun" + "Kada se od vas zatraži da potvrdite svoj uređaj, odaberite %1$s" + "“Poništi sve”" + "Pridržavajte se uputa za izradu novog ključa za oporavak" + "Spremite svoj novi ključ za oporavak u upravitelj zaporki ili šifriranu bilješku" + "Resetirajte šifriranje za svoj račun pomoću drugog uređaja" + "Nastavi s poništavanjem" + "Sačuvat će se podatci o vašem računu, kontakti, postavke i popis razgovora" + "Izgubit ćete svu povijest poruka koja je pohranjena samo na poslužitelju" + "Morat ćete ponovno potvrditi sve svoje postojeće uređaje i kontakte" + "Poništite svoj identitet samo ako nemate pristup drugom prijavljenom uređaju i ako ste izgubili ključ za oporavak." + "Ne možete potvrditi? Morat ćete poništiti svoj identitet." + "Isključi" + "Izgubit ćete šifrirane poruke ako se odjavite sa svih uređaja." + "Jeste li sigurni da želite isključiti sigurnosno kopiranje?" + "Brisanjem pohrane ključeva uklonit ćete svoj kriptografski identitet i ključeve poruka s poslužitelja te isključiti sljedeće sigurnosne značajke:" + "Na novim uređajima nećete imati šifriranu povijest poruka" + "Ako se odjavite iz aplikacije %1$s na svim uređajima, izgubit ćete pristup svojim šifriranim porukama." + "Jeste li sigurni da želite isključiti pohranu ključeva i izbrisati je?" + "Izradite novi ključ za oporavak ako ste izgubili postojeći. Nakon promjene ključa za oporavak, prijašnji više neće funkcionirati." + "Generiraj novi ključ za oporavak" + "Ne dijelite ovo ni s kim!" + "Ključ za oporavak je promijenjen" + "Želite li promijeniti ključ za oporavak?" + "Izradi novi ključ za oporavak" + "Pazite da nitko ne vidi ovaj zaslon!" + "Pokušajte ponovno potvrditi pristup pohrani ključeva." + "Neispravan ključ za oporavak" + "Ako imate sigurnosni ključ ili sigurnosnu frazu, i ovo će funkcionirati." + "Unos…" + "Izgubili ste ključ za oporavak?" + "Ključ za oporavak je potvrđen" + "Unesite svoj ključ za oporavak" + "Kopirani ključ za oporavak" + "Generiranje…" + "Spremi ključ za oporavak" + "Zapišite ovaj ključ za oporavak i čuvajte ga na sigurnom mjestu, poput upravitelja zaporki, šifrirane bilješke ili fizičkog sefa." + "Dodirnite za kopiranje ključa za oporavak" + "Spremite svoj ključ za oporavak na sigurno mjesto" + "Nakon ovog koraka nećete moći pristupiti svom novom ključu za oporavak." + "Jeste li spremili svoj ključ za oporavak?" + "Vaša pohrana ključeva zaštićena je ključem za oporavak. Ako vam je nakon postavljanja potreban novi ključ za oporavak, možete ga ponovno izraditi odabirom mogućnosti ‘Promijeni ključ za oporavak’." + "Generirajte svoj ključ za oporavak" + "Ne dijelite ovo ni s kim!" + "Postavljanje oporavka je uspjelo" + "Postavljanje oporavka" + "Da, poništi sada" + "Ovaj je proces nepovratan." + "Jeste li sigurni da želite poništiti svoj identitet?" + "Došlo je do nepoznate pogreške. Provjerite je li zaporka vašeg računa ispravna i pokušajte ponovno." + "Unos…" + "Potvrdite da želite poništiti svoj identitet." + "Unesite zaporku računa kako biste nastavili" + diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/DefaultSecureBackupEntryPointTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/DefaultSecureBackupEntryPointTest.kt index 7741b5141a..9e984f1ec0 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/DefaultSecureBackupEntryPointTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/DefaultSecureBackupEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -37,10 +38,12 @@ class DefaultSecureBackupEntryPointTest { override fun onDone() = lambdaError() } val params = SecureBackupEntryPoint.Params(SecureBackupEntryPoint.InitialTarget.ResetIdentity) - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .params(params) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + params = params, + callback = callback, + ) assertThat(result).isInstanceOf(SecureBackupFlowNode::class.java) assertThat(result.plugins).contains(params) assertThat(result.plugins).contains(callback) diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenterTest.kt index 9301dd04e1..899726d806 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenterTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/disable/SecureBackupDisablePresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenterTest.kt index 648f9cf81d..a6ffb57dc4 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenterTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyViewTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyViewTest.kt index 8e8bb8ef70..d9324fdb91 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyViewTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/enter/SecureBackupEnterRecoveryKeyViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManagerTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManagerTest.kt index 702b10a1bf..0fbb729534 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManagerTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManagerTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordPresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordPresenterTest.kt index d6892673a1..3c2269e048 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordPresenterTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordViewTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordViewTest.kt index b41a0c7fa5..6cfd061103 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordViewTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootPresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootPresenterTest.kt index b5d8c89372..9bff023fa6 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootPresenterTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootViewTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootViewTest.kt index 3eee154e60..a913a9af27 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootViewTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenterTest.kt index cc072c81b2..63adfc789e 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenterTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateTest.kt index 1e4f58a84a..10b7a9daff 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/root/SecureBackupRootStateTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenterTest.kt index 2d9a262c2c..a3cf920d82 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenterTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/setup/SecureBackupSetupPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyToolsTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyToolsTest.kt index be0515de8f..ce66a066e6 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyToolsTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyToolsTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyVisualTransformationTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyVisualTransformationTest.kt index 9a7210a2f3..98605c03c6 100644 --- a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyVisualTransformationTest.kt +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/tools/RecoveryKeyVisualTransformationTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/securityandprivacy/api/build.gradle.kts b/features/securityandprivacy/api/build.gradle.kts new file mode 100644 index 0000000000..4260bea661 --- /dev/null +++ b/features/securityandprivacy/api/build.gradle.kts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-compose-library") + id("kotlin-parcelize") +} + +android { + namespace = "io.element.android.features.securityandprivacy.api" +} +dependencies { + implementation(projects.libraries.architecture) + implementation(projects.libraries.core) + implementation(projects.libraries.matrix.api) +} diff --git a/features/securityandprivacy/api/src/main/kotlin/io/element/android/features/securityandprivacy/api/SecurityAndPrivacyEntryPoint.kt b/features/securityandprivacy/api/src/main/kotlin/io/element/android/features/securityandprivacy/api/SecurityAndPrivacyEntryPoint.kt new file mode 100644 index 0000000000..be2a4dc9e2 --- /dev/null +++ b/features/securityandprivacy/api/src/main/kotlin/io/element/android/features/securityandprivacy/api/SecurityAndPrivacyEntryPoint.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.securityandprivacy.api + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import io.element.android.libraries.architecture.FeatureEntryPoint + +fun interface SecurityAndPrivacyEntryPoint : FeatureEntryPoint { + interface Callback : Plugin { + fun onDone() + } + + fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: Callback, + ): Node +} diff --git a/features/securityandprivacy/api/src/main/kotlin/io/element/android/features/securityandprivacy/api/SecurityAndPrivacyPermissions.kt b/features/securityandprivacy/api/src/main/kotlin/io/element/android/features/securityandprivacy/api/SecurityAndPrivacyPermissions.kt new file mode 100644 index 0000000000..f42d792b6f --- /dev/null +++ b/features/securityandprivacy/api/src/main/kotlin/io/element/android/features/securityandprivacy/api/SecurityAndPrivacyPermissions.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.securityandprivacy.api + +import io.element.android.libraries.matrix.api.room.StateEventType +import io.element.android.libraries.matrix.api.room.join.JoinRule +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions + +data class SecurityAndPrivacyPermissions( + val canChangeRoomAccess: Boolean, + val canChangeHistoryVisibility: Boolean, + val canChangeEncryption: Boolean, + val canChangeRoomVisibility: Boolean, +) { + fun hasAny(isSpace: Boolean, joinRule: JoinRule?): Boolean { + val canChangeRoomVisibility = when (joinRule) { + is JoinRule.Public, + is JoinRule.Knock, + is JoinRule.KnockRestricted -> canChangeRoomVisibility + else -> false + } + return if (isSpace) { + canChangeRoomAccess || canChangeRoomVisibility + } else { + canChangeRoomAccess || canChangeRoomVisibility || canChangeHistoryVisibility || canChangeEncryption + } + } + + companion object { + val DEFAULT = SecurityAndPrivacyPermissions( + canChangeRoomAccess = false, + canChangeHistoryVisibility = false, + canChangeEncryption = false, + canChangeRoomVisibility = false, + ) + } +} + +fun RoomPermissions.securityAndPrivacyPermissions(): SecurityAndPrivacyPermissions { + return SecurityAndPrivacyPermissions( + canChangeRoomAccess = canOwnUserSendState(StateEventType.RoomJoinRules), + canChangeHistoryVisibility = canOwnUserSendState(StateEventType.RoomHistoryVisibility), + canChangeEncryption = canOwnUserSendState(StateEventType.RoomEncryption), + canChangeRoomVisibility = canOwnUserSendState(StateEventType.RoomCanonicalAlias), + ) +} diff --git a/features/securityandprivacy/impl/build.gradle.kts b/features/securityandprivacy/impl/build.gradle.kts new file mode 100644 index 0000000000..bb9764eb60 --- /dev/null +++ b/features/securityandprivacy/impl/build.gradle.kts @@ -0,0 +1,50 @@ +import extension.setupDependencyInjection +import extension.testCommonDependencies + +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-compose-library") + id("kotlin-parcelize") +} + +android { + namespace = "io.element.android.features.securityandprivacy.impl" + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } +} + +setupDependencyInjection() + +dependencies { + api(projects.features.securityandprivacy.api) + implementation(projects.appconfig) + implementation(projects.appnav) + implementation(projects.libraries.androidutils) + implementation(projects.libraries.architecture) + implementation(projects.libraries.core) + implementation(projects.libraries.designsystem) + implementation(projects.libraries.matrix.api) + // For test fixtures used in previews + implementation(projects.libraries.previewutils) + implementation(projects.libraries.matrixui) + implementation(projects.libraries.uiStrings) + implementation(projects.services.analytics.api) + implementation(projects.libraries.featureflag.api) + + testCommonDependencies(libs, true) + testImplementation(projects.services.analytics.test) + testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.featureflag.test) + testImplementation(projects.libraries.testtags) +} diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/DefaultSecurityAndPrivacyEntryPoint.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/DefaultSecurityAndPrivacyEntryPoint.kt new file mode 100644 index 0000000000..d2a40c6836 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/DefaultSecurityAndPrivacyEntryPoint.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.securityandprivacy.impl + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import dev.zacsweers.metro.ContributesBinding +import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyEntryPoint +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.RoomScope + +@ContributesBinding(RoomScope::class) +class DefaultSecurityAndPrivacyEntryPoint : SecurityAndPrivacyEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: SecurityAndPrivacyEntryPoint.Callback, + ): Node { + return parentNode.createNode(buildContext, listOf(callback)) + } +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyFlowNode.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyFlowNode.kt similarity index 54% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyFlowNode.kt rename to features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyFlowNode.kt index a0295dde36..3d62e7b5d7 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyFlowNode.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyFlowNode.kt @@ -1,15 +1,19 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.securityandprivacy +package io.element.android.features.securityandprivacy.impl import android.os.Parcelable import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin @@ -17,11 +21,21 @@ import com.bumble.appyx.navmodel.backstack.BackStack import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode -import io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress.EditRoomAddressNode +import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyEntryPoint +import io.element.android.features.securityandprivacy.api.securityAndPrivacyPermissions +import io.element.android.features.securityandprivacy.impl.editroomaddress.EditRoomAddressNode +import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyNode import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.matrix.api.room.JoinedRoom +import io.element.android.libraries.matrix.api.room.powerlevels.use +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize @ContributesNode(RoomScope::class) @@ -29,6 +43,7 @@ import kotlinx.parcelize.Parcelize class SecurityAndPrivacyFlowNode( @Assisted buildContext: BuildContext, @Assisted plugins: List, + private val room: JoinedRoom, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.SecurityAndPrivacy, @@ -45,7 +60,26 @@ class SecurityAndPrivacyFlowNode( data object EditRoomAddress : NavTarget } - private val navigator = BackstackSecurityAndPrivacyNavigator(backstack) + private val callback: SecurityAndPrivacyEntryPoint.Callback = callback() + private val navigator = BackstackSecurityAndPrivacyNavigator(callback, backstack) + + override fun onBuilt() { + super.onBuilt() + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.CREATED) { + room.roomInfoFlow + .map { roomInfo -> + room.roomPermissions().use(false) { perms -> + perms.securityAndPrivacyPermissions().hasAny(roomInfo.isSpace, roomInfo.joinRule) + } + } + .filter { canEdit -> !canEdit } + .first() + // If the user can no longer edit security and privacy, exit the flow + callback.onDone() + } + } + } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyNavigator.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyNavigator.kt similarity index 71% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyNavigator.kt rename to features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyNavigator.kt index ade6479fd9..092da87943 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyNavigator.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyNavigator.kt @@ -1,25 +1,33 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.securityandprivacy +package io.element.android.features.securityandprivacy.impl import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push +import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyEntryPoint interface SecurityAndPrivacyNavigator : Plugin { + fun onDone() fun openEditRoomAddress() fun closeEditRoomAddress() } class BackstackSecurityAndPrivacyNavigator( + private val callback: SecurityAndPrivacyEntryPoint.Callback, private val backStack: BackStack ) : SecurityAndPrivacyNavigator { + override fun onDone() { + callback.onDone() + } + override fun openEditRoomAddress() { backStack.push(SecurityAndPrivacyFlowNode.NavTarget.EditRoomAddress) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressEvents.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressEvents.kt similarity index 75% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressEvents.kt rename to features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressEvents.kt index 485a9d2fa0..e42236d31c 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressEvents.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressEvents.kt @@ -1,11 +1,12 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress +package io.element.android.features.securityandprivacy.impl.editroomaddress sealed interface EditRoomAddressEvents { data object Save : EditRoomAddressEvents diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressNode.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressNode.kt similarity index 85% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressNode.kt rename to features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressNode.kt index 76cb1311fc..ba92cfb6d0 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressNode.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressNode.kt @@ -1,11 +1,12 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress +package io.element.android.features.securityandprivacy.impl.editroomaddress import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -16,7 +17,7 @@ import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode -import io.element.android.features.roomdetails.impl.securityandprivacy.SecurityAndPrivacyNavigator +import io.element.android.features.securityandprivacy.impl.SecurityAndPrivacyNavigator import io.element.android.libraries.di.RoomScope @ContributesNode(RoomScope::class) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressPresenter.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressPresenter.kt similarity index 95% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressPresenter.kt rename to features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressPresenter.kt index 95aee73c13..8dcea0d880 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressPresenter.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressPresenter.kt @@ -1,11 +1,12 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress +package io.element.android.features.securityandprivacy.impl.editroomaddress import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState @@ -19,7 +20,7 @@ import androidx.compose.runtime.setValue import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedInject -import io.element.android.features.roomdetails.impl.securityandprivacy.SecurityAndPrivacyNavigator +import io.element.android.features.securityandprivacy.impl.SecurityAndPrivacyNavigator import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState @@ -62,7 +63,7 @@ class EditRoomAddressPresenter( ) } - fun handleEvents(event: EditRoomAddressEvents) { + fun handleEvent(event: EditRoomAddressEvents) { when (event) { EditRoomAddressEvents.Save -> coroutineScope.save( saveAction = saveAction, @@ -92,7 +93,7 @@ class EditRoomAddressPresenter( roomAddressValidity = roomAddressValidity.value, roomAddress = newRoomAddress, saveAction = saveAction.value, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressState.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressState.kt similarity index 82% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressState.kt rename to features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressState.kt index 9bc256daba..89315ab8ce 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressState.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressState.kt @@ -1,11 +1,12 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress +package io.element.android.features.securityandprivacy.impl.editroomaddress import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressStateProvider.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressStateProvider.kt similarity index 91% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressStateProvider.kt rename to features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressStateProvider.kt index 6ef8658bcd..7b82175bb7 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressStateProvider.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressStateProvider.kt @@ -1,11 +1,12 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress +package io.element.android.features.securityandprivacy.impl.editroomaddress import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncAction diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressView.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressView.kt similarity index 95% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressView.kt rename to features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressView.kt index 93c2523f65..da18b963fa 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressView.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressView.kt @@ -1,11 +1,12 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress +package io.element.android.features.securityandprivacy.impl.editroomaddress import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.consumeWindowInsets @@ -20,7 +21,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp -import io.element.android.features.roomdetails.impl.R +import io.element.android.features.securityandprivacy.impl.R import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults import io.element.android.libraries.designsystem.components.button.BackButton diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/RoomAlias.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/RoomAlias.kt similarity index 80% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/RoomAlias.kt rename to features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/RoomAlias.kt index 39e955da66..f9ae4bf958 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/RoomAlias.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/RoomAlias.kt @@ -1,11 +1,12 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress +package io.element.android.features.securityandprivacy.impl.editroomaddress import io.element.android.libraries.matrix.api.core.RoomAlias diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyEvent.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyEvent.kt new file mode 100644 index 0000000000..39abedab8c --- /dev/null +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyEvent.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.securityandprivacy.impl.root + +sealed interface SecurityAndPrivacyEvent { + data object EditRoomAddress : SecurityAndPrivacyEvent + data object Save : SecurityAndPrivacyEvent + data object Exit : SecurityAndPrivacyEvent + data object DismissExitConfirmation : SecurityAndPrivacyEvent + data class ChangeRoomAccess(val roomAccess: SecurityAndPrivacyRoomAccess) : SecurityAndPrivacyEvent + data object ToggleEncryptionState : SecurityAndPrivacyEvent + data object CancelEnableEncryption : SecurityAndPrivacyEvent + data object ConfirmEnableEncryption : SecurityAndPrivacyEvent + data class ChangeHistoryVisibility(val historyVisibility: SecurityAndPrivacyHistoryVisibility) : SecurityAndPrivacyEvent + data object ToggleRoomVisibility : SecurityAndPrivacyEvent + data object DismissSaveError : SecurityAndPrivacyEvent +} diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyNode.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyNode.kt new file mode 100644 index 0000000000..d5fb72e72e --- /dev/null +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyNode.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.securityandprivacy.impl.root + +import android.app.Activity +import androidx.activity.compose.LocalActivity +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject +import io.element.android.annotations.ContributesNode +import io.element.android.compound.theme.ElementTheme +import io.element.android.features.securityandprivacy.impl.SecurityAndPrivacyNavigator +import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab +import io.element.android.libraries.architecture.appyx.launchMolecule +import io.element.android.libraries.di.RoomScope + +@ContributesNode(RoomScope::class) +@AssistedInject +class SecurityAndPrivacyNode( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + presenterFactory: SecurityAndPrivacyPresenter.Factory, +) : Node(buildContext, plugins = plugins) { + private val navigator = plugins().first() + private val presenter = presenterFactory.create(navigator) + + private val stateFlow = launchMolecule { presenter.present() } + + private fun onOpenExternalUrl(activity: Activity, darkTheme: Boolean, url: String) { + activity.openUrlInChromeCustomTab(null, darkTheme, url) + } + + @Composable + override fun View(modifier: Modifier) { + val activity = requireNotNull(LocalActivity.current) + val isDark = ElementTheme.isLightTheme.not() + val state by stateFlow.collectAsState() + SecurityAndPrivacyView( + state = state, + onLinkClick = { url -> + onOpenExternalUrl(activity, isDark, url) + }, + modifier = modifier + ) + } +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenter.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenter.kt similarity index 74% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenter.kt rename to features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenter.kt index abc0ff72af..e627fef40c 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenter.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenter.kt @@ -1,11 +1,12 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.securityandprivacy +package io.element.android.features.securityandprivacy.impl.root import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -20,19 +21,24 @@ import androidx.compose.runtime.setValue import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedInject -import io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress.matchesServer -import io.element.android.features.roomdetails.impl.securityandprivacy.permissions.securityAndPrivacyPermissionsAsState +import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions +import io.element.android.features.securityandprivacy.api.securityAndPrivacyPermissions +import io.element.android.features.securityandprivacy.impl.SecurityAndPrivacyNavigator +import io.element.android.features.securityandprivacy.impl.editroomaddress.matchesServer import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.architecture.runUpdatingState +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.RoomInfo import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility import io.element.android.libraries.matrix.api.room.join.JoinRule +import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async @@ -45,6 +51,7 @@ class SecurityAndPrivacyPresenter( @Assisted private val navigator: SecurityAndPrivacyNavigator, private val matrixClient: MatrixClient, private val room: JoinedRoom, + private val featureFlagService: FeatureFlagService, ) : Presenter { @AssistedFactory interface Factory { @@ -55,9 +62,11 @@ class SecurityAndPrivacyPresenter( override fun present(): SecurityAndPrivacyState { val coroutineScope = rememberCoroutineScope() + val isKnockEnabled by remember { + featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock) + }.collectAsState(false) val saveAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } val homeserverName = remember { matrixClient.userIdServerName() } - val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val roomInfo by room.roomInfoFlow.collectAsState() val savedIsVisibleInRoomDirectory = remember { mutableStateOf>(AsyncData.Uninitialized) } @@ -67,12 +76,11 @@ class SecurityAndPrivacyPresenter( val savedSettings by remember { derivedStateOf { - val historyVisibility = roomInfo.historyVisibility.map() SecurityAndPrivacySettings( roomAccess = roomInfo.joinRule.map(), isEncrypted = roomInfo.isEncrypted == true, isVisibleInRoomDirectory = savedIsVisibleInRoomDirectory.value, - historyVisibility = historyVisibility, + historyVisibility = roomInfo.historyVisibility.map(), address = roomInfo.firstDisplayableAlias(homeserverName)?.value, ) } @@ -99,11 +107,13 @@ class SecurityAndPrivacyPresenter( ) var showEnableEncryptionConfirmation by remember(savedSettings.isEncrypted) { mutableStateOf(false) } - val permissions by room.securityAndPrivacyPermissionsAsState(syncUpdateFlow.value) + val permissions by room.permissionsAsState(SecurityAndPrivacyPermissions.DEFAULT) { perms -> + perms.securityAndPrivacyPermissions() + } - fun handleEvents(event: SecurityAndPrivacyEvents) { + fun handleEvent(event: SecurityAndPrivacyEvent) { when (event) { - SecurityAndPrivacyEvents.Save -> { + SecurityAndPrivacyEvent.Save -> { coroutineScope.save( saveAction = saveAction, isVisibleInRoomDirectory = savedIsVisibleInRoomDirectory, @@ -111,36 +121,52 @@ class SecurityAndPrivacyPresenter( editedSettings = editedSettings ) } - is SecurityAndPrivacyEvents.ChangeRoomAccess -> { + is SecurityAndPrivacyEvent.ChangeRoomAccess -> { editedRoomAccess = event.roomAccess } - is SecurityAndPrivacyEvents.ToggleEncryptionState -> { + is SecurityAndPrivacyEvent.ToggleEncryptionState -> { if (editedIsEncrypted) { editedIsEncrypted = false } else { showEnableEncryptionConfirmation = true } } - is SecurityAndPrivacyEvents.ChangeHistoryVisibility -> { + is SecurityAndPrivacyEvent.ChangeHistoryVisibility -> { editedHistoryVisibility = event.historyVisibility } - SecurityAndPrivacyEvents.ToggleRoomVisibility -> { + SecurityAndPrivacyEvent.ToggleRoomVisibility -> { editedVisibleInRoomDirectory = when (val edited = editedVisibleInRoomDirectory) { is AsyncData.Success -> AsyncData.Success(!edited.data) else -> edited } } - SecurityAndPrivacyEvents.EditRoomAddress -> navigator.openEditRoomAddress() - SecurityAndPrivacyEvents.CancelEnableEncryption -> { + SecurityAndPrivacyEvent.EditRoomAddress -> navigator.openEditRoomAddress() + SecurityAndPrivacyEvent.CancelEnableEncryption -> { showEnableEncryptionConfirmation = false } - SecurityAndPrivacyEvents.ConfirmEnableEncryption -> { + SecurityAndPrivacyEvent.ConfirmEnableEncryption -> { showEnableEncryptionConfirmation = false editedIsEncrypted = true } - SecurityAndPrivacyEvents.DismissSaveError -> { + SecurityAndPrivacyEvent.DismissSaveError -> { saveAction.value = AsyncAction.Uninitialized } + SecurityAndPrivacyEvent.Exit -> { + saveAction.value = if (savedSettings == editedSettings || saveAction.value == AsyncAction.ConfirmingCancellation) { + AsyncAction.Success(Unit) + } else { + AsyncAction.ConfirmingCancellation + } + } + SecurityAndPrivacyEvent.DismissExitConfirmation -> { + saveAction.value = AsyncAction.Uninitialized + } + } + } + + LaunchedEffect(saveAction.value.isSuccess()) { + if (saveAction.value.isSuccess()) { + navigator.onDone() } } @@ -149,14 +175,27 @@ class SecurityAndPrivacyPresenter( editedSettings = editedSettings, homeserverName = homeserverName, showEnableEncryptionConfirmation = showEnableEncryptionConfirmation, + isKnockEnabled = isKnockEnabled, saveAction = saveAction.value, permissions = permissions, - eventSink = ::handleEvents + isSpace = roomInfo.isSpace, + eventSink = ::handleEvent, ) - // If the history visibility is not available for the current access, use the fallback. - LaunchedEffect(state.availableHistoryVisibilities) { - if (editedSettings.historyVisibility !in state.availableHistoryVisibilities) { + // Revert changes that the user is not allowed to make anymore + LaunchedEffect(permissions, state.editedSettings.roomAccess) { + if (!state.showRoomAccessSection) { + editedRoomAccess = savedSettings.roomAccess + } + if (!state.showEncryptionSection) { + editedIsEncrypted = savedSettings.isEncrypted + } + if (!state.showRoomVisibilitySections) { + editedVisibleInRoomDirectory = savedSettings.isVisibleInRoomDirectory + } + if (!state.showHistoryVisibilitySection) { + editedHistoryVisibility = savedSettings.historyVisibility + } else if (editedSettings.historyVisibility !in state.availableHistoryVisibilities) { editedHistoryVisibility = editedSettings.historyVisibility.fallback() } } @@ -262,21 +301,21 @@ private fun SecurityAndPrivacyRoomAccess.map(): JoinRule? { private fun RoomHistoryVisibility?.map(): SecurityAndPrivacyHistoryVisibility { return when (this) { - RoomHistoryVisibility.WorldReadable -> SecurityAndPrivacyHistoryVisibility.Anyone RoomHistoryVisibility.Joined, - RoomHistoryVisibility.Invited -> SecurityAndPrivacyHistoryVisibility.SinceInvite - RoomHistoryVisibility.Shared -> SecurityAndPrivacyHistoryVisibility.SinceSelection - // All other cases are not supported so we default to SinceSelection + RoomHistoryVisibility.Invited -> SecurityAndPrivacyHistoryVisibility.Invited + RoomHistoryVisibility.Shared -> SecurityAndPrivacyHistoryVisibility.Shared + RoomHistoryVisibility.WorldReadable -> SecurityAndPrivacyHistoryVisibility.WorldReadable + // All other cases are not supported so we default to Shared is RoomHistoryVisibility.Custom, - null -> SecurityAndPrivacyHistoryVisibility.SinceSelection + null -> SecurityAndPrivacyHistoryVisibility.Shared } } private fun SecurityAndPrivacyHistoryVisibility.map(): RoomHistoryVisibility { return when (this) { - SecurityAndPrivacyHistoryVisibility.SinceSelection -> RoomHistoryVisibility.Shared - SecurityAndPrivacyHistoryVisibility.SinceInvite -> RoomHistoryVisibility.Invited - SecurityAndPrivacyHistoryVisibility.Anyone -> RoomHistoryVisibility.WorldReadable + SecurityAndPrivacyHistoryVisibility.Invited -> RoomHistoryVisibility.Invited + SecurityAndPrivacyHistoryVisibility.Shared -> RoomHistoryVisibility.Shared + SecurityAndPrivacyHistoryVisibility.WorldReadable -> RoomHistoryVisibility.WorldReadable } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyState.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyState.kt similarity index 57% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyState.kt rename to features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyState.kt index eb22ec2597..e64cf633a8 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyState.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyState.kt @@ -1,16 +1,17 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.securityandprivacy +package io.element.android.features.securityandprivacy.impl.root -import io.element.android.features.roomdetails.impl.securityandprivacy.permissions.SecurityAndPrivacyPermissions +import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData -import kotlinx.collections.immutable.toImmutableSet +import kotlinx.collections.immutable.toImmutableList data class SecurityAndPrivacyState( // the settings that are currently applied on the room. @@ -19,25 +20,34 @@ data class SecurityAndPrivacyState( val editedSettings: SecurityAndPrivacySettings, val homeserverName: String, val showEnableEncryptionConfirmation: Boolean, + val isKnockEnabled: Boolean, val saveAction: AsyncAction, + val isSpace: Boolean, private val permissions: SecurityAndPrivacyPermissions, - val eventSink: (SecurityAndPrivacyEvents) -> Unit + val eventSink: (SecurityAndPrivacyEvent) -> Unit ) { val canBeSaved = savedSettings != editedSettings - val availableHistoryVisibilities = buildSet { - add(SecurityAndPrivacyHistoryVisibility.SinceSelection) + // Logic is in https://github.com/element-hq/element-meta/issues/3029 + val availableHistoryVisibilities = buildList { + // Shared is always available + add(SecurityAndPrivacyHistoryVisibility.Shared) if (editedSettings.roomAccess == SecurityAndPrivacyRoomAccess.Anyone && !editedSettings.isEncrypted) { - add(SecurityAndPrivacyHistoryVisibility.Anyone) + add(SecurityAndPrivacyHistoryVisibility.WorldReadable) } else { - add(SecurityAndPrivacyHistoryVisibility.SinceInvite) + add(SecurityAndPrivacyHistoryVisibility.Invited) } - }.toImmutableSet() + } + .sorted() + .toImmutableList() val showRoomAccessSection = permissions.canChangeRoomAccess - val showRoomVisibilitySections = permissions.canChangeRoomVisibility && editedSettings.roomAccess != SecurityAndPrivacyRoomAccess.InviteOnly - val showHistoryVisibilitySection = permissions.canChangeHistoryVisibility - val showEncryptionSection = permissions.canChangeEncryption + + val showRoomVisibilitySections = permissions.canChangeRoomVisibility && + editedSettings.roomAccess.canConfigureRoomVisibility() + + val showHistoryVisibilitySection = permissions.canChangeHistoryVisibility && !isSpace + val showEncryptionSection = permissions.canChangeEncryption && !isSpace } data class SecurityAndPrivacySettings( @@ -49,18 +59,19 @@ data class SecurityAndPrivacySettings( ) enum class SecurityAndPrivacyHistoryVisibility { - SinceSelection, - SinceInvite, - Anyone; + // Order matters, and is from the most to the least restrictive + Invited, + Shared, + WorldReadable; /** * Returns the fallback visibility when the current visibility is not available. */ fun fallback(): SecurityAndPrivacyHistoryVisibility { return when (this) { - SinceSelection, - SinceInvite -> SinceSelection - Anyone -> SinceInvite + Invited, + Shared -> Shared + WorldReadable -> Invited } } } @@ -69,7 +80,14 @@ enum class SecurityAndPrivacyRoomAccess { InviteOnly, AskToJoin, Anyone, - SpaceMember + SpaceMember; + + fun canConfigureRoomVisibility(): Boolean { + return when (this) { + InviteOnly, SpaceMember -> false + AskToJoin, Anyone -> true + } + } } sealed class SecurityAndPrivacyFailures : Exception() { diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStateProvider.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStateProvider.kt new file mode 100644 index 0000000000..223d524ca3 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStateProvider.kt @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.securityandprivacy.impl.root + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.AsyncData + +open class SecurityAndPrivacyStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = commonSecurityAndPrivacyStates(isSpace = false) + + commonSecurityAndPrivacyStates(isSpace = true) + + sequenceOf( + aSecurityAndPrivacyState( + saveAction = AsyncAction.Loading, + isSpace = false, + ), + aSecurityAndPrivacyState( + saveAction = AsyncAction.Failure(SecurityAndPrivacyFailures.SaveFailed), + isSpace = false, + ), + aSecurityAndPrivacyState( + saveAction = AsyncAction.ConfirmingCancellation, + isSpace = false, + ), + aSecurityAndPrivacyState( + showEncryptionConfirmation = true, + isSpace = false, + ), + ) +} + +private fun commonSecurityAndPrivacyStates(isSpace: Boolean): Sequence = sequenceOf( + aSecurityAndPrivacyState(isSpace = isSpace), + aSecurityAndPrivacyState( + editedSettings = aSecurityAndPrivacySettings( + roomAccess = SecurityAndPrivacyRoomAccess.AskToJoin, + ), + isSpace = isSpace, + ), + aSecurityAndPrivacyState( + savedSettings = aSecurityAndPrivacySettings( + roomAccess = SecurityAndPrivacyRoomAccess.AskToJoin + ), + isSpace = isSpace, + isKnockEnabled = false, + ), + aSecurityAndPrivacyState( + editedSettings = aSecurityAndPrivacySettings( + roomAccess = SecurityAndPrivacyRoomAccess.Anyone, + isEncrypted = false, + ), + isSpace = isSpace, + ), + aSecurityAndPrivacyState( + savedSettings = aSecurityAndPrivacySettings( + roomAccess = SecurityAndPrivacyRoomAccess.SpaceMember + ), + isSpace = isSpace, + isKnockEnabled = false, + ), + aSecurityAndPrivacyState( + editedSettings = aSecurityAndPrivacySettings( + roomAccess = SecurityAndPrivacyRoomAccess.Anyone, + address = "#therapy:myserver.xyz" + ), + isSpace = isSpace, + ), + aSecurityAndPrivacyState( + editedSettings = aSecurityAndPrivacySettings( + roomAccess = SecurityAndPrivacyRoomAccess.Anyone, + isVisibleInRoomDirectory = AsyncData.Loading() + ), + isSpace = isSpace, + ), + aSecurityAndPrivacyState( + editedSettings = aSecurityAndPrivacySettings( + roomAccess = SecurityAndPrivacyRoomAccess.Anyone, + isVisibleInRoomDirectory = AsyncData.Success(true) + ), + isSpace = isSpace, + ), +) + +fun aSecurityAndPrivacySettings( + roomAccess: SecurityAndPrivacyRoomAccess = SecurityAndPrivacyRoomAccess.InviteOnly, + isEncrypted: Boolean = true, + address: String? = null, + historyVisibility: SecurityAndPrivacyHistoryVisibility = SecurityAndPrivacyHistoryVisibility.Shared, + isVisibleInRoomDirectory: AsyncData = AsyncData.Uninitialized, +) = SecurityAndPrivacySettings( + roomAccess = roomAccess, + isEncrypted = isEncrypted, + address = address, + historyVisibility = historyVisibility, + isVisibleInRoomDirectory = isVisibleInRoomDirectory +) + +fun aSecurityAndPrivacyState( + savedSettings: SecurityAndPrivacySettings = aSecurityAndPrivacySettings(), + editedSettings: SecurityAndPrivacySettings = savedSettings, + homeserverName: String = "myserver.xyz", + showEncryptionConfirmation: Boolean = false, + saveAction: AsyncAction = AsyncAction.Uninitialized, + permissions: SecurityAndPrivacyPermissions = SecurityAndPrivacyPermissions( + canChangeRoomAccess = true, + canChangeHistoryVisibility = true, + canChangeEncryption = true, + canChangeRoomVisibility = true + ), + isKnockEnabled: Boolean = true, + isSpace: Boolean = false, + eventSink: (SecurityAndPrivacyEvent) -> Unit = {} +) = SecurityAndPrivacyState( + editedSettings = editedSettings, + savedSettings = savedSettings, + homeserverName = homeserverName, + showEnableEncryptionConfirmation = showEncryptionConfirmation, + saveAction = saveAction, + isKnockEnabled = isKnockEnabled, + permissions = permissions, + isSpace = isSpace, + eventSink = eventSink, +) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyView.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyView.kt similarity index 76% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyView.kt rename to features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyView.kt index c46f8a7a6f..bf6a5ffdf2 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyView.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyView.kt @@ -1,12 +1,14 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.securityandprivacy +package io.element.android.features.securityandprivacy.impl.root +import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope @@ -25,21 +27,26 @@ import androidx.compose.material3.ListItemDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import io.element.android.appconfig.LearnMoreConfig import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons -import io.element.android.features.roomdetails.impl.R +import io.element.android.features.securityandprivacy.impl.R +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog +import io.element.android.libraries.designsystem.components.dialogs.SaveChangesDialog import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight +import io.element.android.libraries.designsystem.text.stringWithLink import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.IconSource import io.element.android.libraries.designsystem.theme.components.ListItem @@ -48,22 +55,27 @@ import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.ui.strings.CommonStrings -import kotlinx.collections.immutable.ImmutableSet +import kotlinx.collections.immutable.ImmutableList @Composable fun SecurityAndPrivacyView( state: SecurityAndPrivacyState, - onBackClick: () -> Unit, + onLinkClick: (String) -> Unit, modifier: Modifier = Modifier, ) { + BackHandler { + state.eventSink(SecurityAndPrivacyEvent.Exit) + } Scaffold( modifier = modifier, topBar = { SecurityAndPrivacyToolbar( isSaveActionEnabled = state.canBeSaved, - onBackClick = onBackClick, + onBackClick = { + state.eventSink(SecurityAndPrivacyEvent.Exit) + }, onSaveClick = { - state.eventSink(SecurityAndPrivacyEvents.Save) + state.eventSink(SecurityAndPrivacyEvent.Save) }, ) } @@ -81,7 +93,8 @@ fun SecurityAndPrivacyView( modifier = Modifier.padding(top = 24.dp), edited = state.editedSettings.roomAccess, saved = state.savedSettings.roomAccess, - onSelectOption = { state.eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(it)) }, + isKnockEnabled = state.isKnockEnabled, + onSelectOption = { state.eventSink(SecurityAndPrivacyEvent.ChangeRoomAccess(it)) }, ) } if (state.showRoomVisibilitySections) { @@ -89,10 +102,10 @@ fun SecurityAndPrivacyView( RoomAddressSection( roomAddress = state.editedSettings.address, homeserverName = state.homeserverName, - onRoomAddressClick = { state.eventSink(SecurityAndPrivacyEvents.EditRoomAddress) }, + onRoomAddressClick = { state.eventSink(SecurityAndPrivacyEvent.EditRoomAddress) }, isVisibleInRoomDirectory = state.editedSettings.isVisibleInRoomDirectory, onVisibilityChange = { - state.eventSink(SecurityAndPrivacyEvents.ToggleRoomVisibility) + state.eventSink(SecurityAndPrivacyEvent.ToggleRoomVisibility) }, ) } @@ -101,10 +114,10 @@ fun SecurityAndPrivacyView( isRoomEncrypted = state.editedSettings.isEncrypted, // encryption can't be disabled once enabled canToggleEncryption = !state.savedSettings.isEncrypted, - onToggleEncryption = { state.eventSink(SecurityAndPrivacyEvents.ToggleEncryptionState) }, + onToggleEncryption = { state.eventSink(SecurityAndPrivacyEvent.ToggleEncryptionState) }, showConfirmation = state.showEnableEncryptionConfirmation, - onDismissConfirmation = { state.eventSink(SecurityAndPrivacyEvents.CancelEnableEncryption) }, - onConfirmEncryption = { state.eventSink(SecurityAndPrivacyEvents.ConfirmEnableEncryption) }, + onDismissConfirmation = { state.eventSink(SecurityAndPrivacyEvent.CancelEnableEncryption) }, + onConfirmEncryption = { state.eventSink(SecurityAndPrivacyEvent.ConfirmEnableEncryption) }, ) } if (state.showHistoryVisibilitySection) { @@ -112,7 +125,8 @@ fun SecurityAndPrivacyView( editedOption = state.editedSettings.historyVisibility, savedOptions = state.savedSettings.historyVisibility, availableOptions = state.availableHistoryVisibilities, - onSelectOption = { state.eventSink(SecurityAndPrivacyEvents.ChangeHistoryVisibility(it)) }, + onSelectOption = { state.eventSink(SecurityAndPrivacyEvent.ChangeHistoryVisibility(it)) }, + onLinkClick = onLinkClick, ) } } @@ -120,14 +134,24 @@ fun SecurityAndPrivacyView( AsyncActionView( async = state.saveAction, onSuccess = { }, - onErrorDismiss = { state.eventSink(SecurityAndPrivacyEvents.DismissSaveError) }, + onErrorDismiss = { state.eventSink(SecurityAndPrivacyEvent.DismissSaveError) }, + confirmationDialog = { confirming -> + when (confirming) { + is AsyncAction.ConfirmingCancellation -> + SaveChangesDialog( + onSaveClick = { state.eventSink(SecurityAndPrivacyEvent.Save) }, + onDiscardClick = { state.eventSink(SecurityAndPrivacyEvent.Exit) }, + onDismiss = { state.eventSink(SecurityAndPrivacyEvent.DismissExitConfirmation) } + ) + } + }, errorMessage = { stringResource(CommonStrings.error_unknown) }, progressDialog = { AsyncActionViewDefaults.ProgressDialog( progressText = stringResource(CommonStrings.common_saving), ) }, - onRetry = { state.eventSink(SecurityAndPrivacyEvents.Save) }, + onRetry = { state.eventSink(SecurityAndPrivacyEvent.Save) }, ) } @@ -141,7 +165,7 @@ private fun SecurityAndPrivacyToolbar( ) { TopAppBar( modifier = modifier, - titleStr = stringResource(R.string.screen_room_details_security_and_privacy_title), + titleStr = stringResource(R.string.screen_security_and_privacy_title), navigationIcon = { BackButton(onClick = onBackClick) }, actions = { TextButton( @@ -157,6 +181,7 @@ private fun SecurityAndPrivacyToolbar( private fun SecurityAndPrivacySection( title: String, modifier: Modifier = Modifier, + subtitle: AnnotatedString? = null, content: @Composable ColumnScope.() -> Unit, ) { Column( @@ -168,6 +193,15 @@ private fun SecurityAndPrivacySection( color = ElementTheme.colors.textPrimary, modifier = Modifier.padding(horizontal = 16.dp), ) + if (subtitle != null) { + Spacer(Modifier.height(8.dp)) + Text( + text = subtitle, + style = ElementTheme.typography.fontBodyMdRegular, + color = ElementTheme.colors.textSecondary, + modifier = Modifier.padding(horizontal = 16.dp), + ) + } content() } } @@ -176,6 +210,7 @@ private fun SecurityAndPrivacySection( private fun RoomAccessSection( edited: SecurityAndPrivacyRoomAccess, saved: SecurityAndPrivacyRoomAccess, + isKnockEnabled: Boolean, onSelectOption: (SecurityAndPrivacyRoomAccess) -> Unit, modifier: Modifier = Modifier, ) { @@ -183,33 +218,45 @@ private fun RoomAccessSection( title = stringResource(R.string.screen_security_and_privacy_room_access_section_header), modifier = modifier, ) { - ListItem( - headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_invite_only_option_title)) }, - supportingContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_invite_only_option_description)) }, - trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.InviteOnly), - onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.InviteOnly) }, - ) - ListItem( - headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_ask_to_join_option_title)) }, - supportingContent = { Text(text = stringResource(R.string.screen_security_and_privacy_ask_to_join_option_description)) }, - trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.AskToJoin), - onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.AskToJoin) }, - ) ListItem( headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_anyone_option_title)) }, supportingContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_anyone_option_description)) }, trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.Anyone), + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Public())), onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.Anyone) }, ) // Show space member option, but disabled as we don't support this option for now. if (saved == SecurityAndPrivacyRoomAccess.SpaceMember) { ListItem( headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_space_members_option_title)) }, - supportingContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_space_members_option_description)) }, - trailingContent = ListItemContent.RadioButton(selected = true, enabled = false), + supportingContent = { + Text(text = stringResource(R.string.screen_security_and_privacy_room_access_space_members_option_unavailable_description)) + }, + trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.SpaceMember, enabled = false), + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Space())), enabled = false, ) } + // Show Ask to join option in two cases: + // - the Knock FF is enabled + // - AskToJoin is the current saved value + if (saved == SecurityAndPrivacyRoomAccess.AskToJoin || isKnockEnabled) { + ListItem( + headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_ask_to_join_option_title)) }, + supportingContent = { Text(text = stringResource(R.string.screen_security_and_privacy_ask_to_join_option_description)) }, + trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.AskToJoin), + onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.AskToJoin) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.UserAdd())), + enabled = isKnockEnabled, + ) + } + ListItem( + headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_invite_only_option_title)) }, + supportingContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_invite_only_option_description)) }, + trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.InviteOnly), + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Lock())), + onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.InviteOnly) }, + ) } } @@ -258,7 +305,7 @@ private fun RoomAddressSection( ListItem( headlineContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_directory_visibility_toggle_title)) }, supportingContent = { - Text(text = stringResource(R.string.screen_security_and_privacy_room_directory_visibility_section_footer, homeserverName)) + Text(text = stringResource(R.string.screen_security_and_privacy_room_directory_visibility_toggle_description, homeserverName)) }, onClick = if (isVisibleInRoomDirectory.isSuccess()) onVisibilityChange else null, trailingContent = when (isVisibleInRoomDirectory) { @@ -327,12 +374,18 @@ private fun EncryptionSection( private fun HistoryVisibilitySection( editedOption: SecurityAndPrivacyHistoryVisibility?, savedOptions: SecurityAndPrivacyHistoryVisibility?, - availableOptions: ImmutableSet, + availableOptions: ImmutableList, onSelectOption: (SecurityAndPrivacyHistoryVisibility) -> Unit, + onLinkClick: (String) -> Unit, modifier: Modifier = Modifier, ) { SecurityAndPrivacySection( title = stringResource(R.string.screen_security_and_privacy_room_history_section_header), + subtitle = stringWithLink( + textRes = R.string.screen_security_and_privacy_room_history_section_footer, + url = LearnMoreConfig.HISTORY_VISIBLE_URL, + onLinkClick = onLinkClick, + ), modifier = modifier, ) { for (availableOption in availableOptions) { @@ -364,9 +417,9 @@ private fun HistoryVisibilityItem( isEnabled: Boolean = true, ) { val headlineText = when (option) { - SecurityAndPrivacyHistoryVisibility.SinceSelection -> stringResource(R.string.screen_security_and_privacy_room_history_since_selecting_option_title) - SecurityAndPrivacyHistoryVisibility.SinceInvite -> stringResource(R.string.screen_security_and_privacy_room_history_since_invite_option_title) - SecurityAndPrivacyHistoryVisibility.Anyone -> stringResource(R.string.screen_security_and_privacy_room_history_anyone_option_title) + SecurityAndPrivacyHistoryVisibility.Invited -> stringResource(R.string.screen_security_and_privacy_room_history_since_invite_option_title) + SecurityAndPrivacyHistoryVisibility.Shared -> stringResource(R.string.screen_security_and_privacy_room_history_since_selecting_option_title) + SecurityAndPrivacyHistoryVisibility.WorldReadable -> stringResource(R.string.screen_security_and_privacy_room_history_anyone_option_title) } ListItem( headlineContent = { Text(text = headlineText) }, @@ -392,6 +445,6 @@ internal fun SecurityAndPrivacyViewDarkPreview(@PreviewParameter(SecurityAndPriv private fun ContentToPreview(state: SecurityAndPrivacyState) { SecurityAndPrivacyView( state = state, - onBackClick = {}, + onLinkClick = {}, ) } diff --git a/features/securityandprivacy/impl/src/main/res/values-be/translations.xml b/features/securityandprivacy/impl/src/main/res/values-be/translations.xml new file mode 100644 index 0000000000..4587ac191c --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-be/translations.xml @@ -0,0 +1,5 @@ + + + "Папрасіце далучыцца" + "Хто заўгодна" + diff --git a/features/securityandprivacy/impl/src/main/res/values-bg/translations.xml b/features/securityandprivacy/impl/src/main/res/values-bg/translations.xml new file mode 100644 index 0000000000..348c9d491f --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-bg/translations.xml @@ -0,0 +1,21 @@ + + + "Добавяне на адрес на стаята" + "Да, включване на шифроването" + "Да се включи ли шифроването?" + "Веднъж включено, шифроването не може да бъде изключено." + "Шифроване" + "Включване на шифроване от край до край" + "Всеки може да намери и да се присъедини" + "Всеки" + "Хората могат да се присъединят само ако са поканени" + "Само с покана" + "Достъп до стаята" + "Членове на пространството" + "Пространствата в момента не се поддържат" + "Видима в директорията на обществените стаи" + "Кой може да чете историята" + "Само за членове откакто са поканени" + "Само за членове от избирането на тази опция" + "Защита и поверителност" + diff --git a/features/securityandprivacy/impl/src/main/res/values-cs/translations.xml b/features/securityandprivacy/impl/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..a70ccdbf72 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-cs/translations.xml @@ -0,0 +1,41 @@ + + + "Budete potřebovat adresu místnosti, aby byla viditelná v adresáři místností." + "Upravit adresu" + "Přidat adresu" + "Všichni musí požádat o přístup." + "Požádat o připojení" + "Ano, povolit šifrování" + "Po aktivaci nelze šifrování místnosti deaktivovat. Historie zpráv bude viditelná pouze pro členy místnosti od doby, kdy byli pozváni nebo od té doby, co do místnosti vstoupili. +Nikdo kromě členů místnosti nebude moci číst zprávy. To může bránit správnému fungování robotů a propojení. +Nedoporučujeme povolovat šifrování pro místnosti, které může kdokoli najít a vstoupit do nich." + "Povolit šifrování?" + "Jakmile je povoleno, šifrování nelze zakázat." + "Šifrování" + "Povolit koncové šifrování" + "Vstoupit může kdokoli." + "Kdokoliv" + "Vyberte, kteří členové prostorů se k této místnosti mohou připojit bez pozvánky. %1$s" + "Vstoupit mohou pouze pozvaní lidé." + "Pouze pro zvané" + "Přístup" + "Vstoupit může kdokoli z autorizovaných prostorů." + "Vstoupit může kdokoli v %1$s." + "Členové prostoru" + "Prostory nejsou aktuálně podporovány" + "Budete potřebovat adresu místnosti, aby byla viditelná v adresáři místností." + "Adresa" + "Umožněte nalezení této místnosti prohledáním adresáře veřejných místností na %1$s" + "Umožnit nalezení vyhledáváním ve veřejném adresáři." + "Viditelné ve veřejném adresáři" + "Kdokoliv" + "Kdo může číst historii" + "Pouze členové od té doby, co byli pozváni" + "Pouze členové od výběru této možnosti" + "Adresy místností představují způsoby, jak najít místnosti a získat k nim přístup. Díky tomu můžete svoji místnost snadno sdílet s ostatními. +Můžete se rozhodnout publikovat svou místnost ve veřejném adresáři místnosti vašeho domovského serveru." + "Publikování místnosti" + "Adresy slouží k vyhledávání a přístupu do místností a prostorů. Díky tomu je také můžete snadno sdílet s ostatními." + "Viditelnost" + "Zabezpečení a soukromí" + diff --git a/features/securityandprivacy/impl/src/main/res/values-cy/translations.xml b/features/securityandprivacy/impl/src/main/res/values-cy/translations.xml new file mode 100644 index 0000000000..ce812b0f83 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-cy/translations.xml @@ -0,0 +1,36 @@ + + + "Bydd angen cyfeiriad ystafell arnoch i\'w wneud yn weladwy yn y cyfeiriadur." + "Cyfeiriad yr ystafell" + "Ychwanegu cyfeiriad ystafell" + "Gall unrhyw un ofyn am gael ymuno â\'r ystafell ond bydd rhaid i weinyddwr neu gymedrolwr dderbyn y cais." + "Gofyn i gael ymuno" + "Iawn, galluogi amgryptio" + "Unwaith y bydd wedi\'i alluogi, does dim modd analluogi amgryptio ar gyfer ystafell, dim ond ar gyfer aelodau\'r ystafell y bydd hanes neges yn weladwy ers iddyn nhw gael eu gwahodd neu ers iddyn nhw ymuno â\'r ystafell. +Fydd neb ar wahân i aelodau\'r ystafell yn gallu darllen negeseuon. Gall hyn atal botiau a phontydd i weithio\'n gywir. +Nid ydym yn argymell galluogi amgryptio ar gyfer ystafelloedd y gall unrhyw un ddod o hyd iddynt ac ymuno â nhw." + "Galluogi amgryptio?" + "Unwaith y bydd wedi\'i alluogi, does dim modd analluogi amgryptio." + "Amgryptiad" + "Galluogi amgryptio o\'r dechrau i\'r diwedd" + "Gall unrhyw un ddod o hyd iddo ac ymuno" + "Unrhyw un" + "Dim ond os cawn nhw wahoddiad gall pobl ymuno" + "Gwahoddiad yn unig" + "Mynediad ystafell" + "Aelodau gofod" + "Nid yw gofodau\'n cael eu cefnogi ar hyn o bryd" + "Bydd angen cyfeiriad ystafell arnoch i\'w wneud yn weladwy yn y cyfeiriadur." + "Cyfeiriad yr ystafell" + "Caniatáu i\'r ystafell hon gael ei chanfod trwy chwilio cyfeiriadur ystafelloedd cyhoeddus %1$s" + "Gweladwy yn y cyfeiriadur ystafelloedd cyhoeddus" + "Unrhyw un" + "Pwy all ddarllen hanes" + "Yn aelodau ond dim ond ers cael eu gwahodd" + "Yn aelodau dim ond ers dewis y dewis hwn" + "Mae cyfeiriadau ystafelloedd yn ffyrdd o ddod o hyd i ystafelloedd a chael mynediad iddyn nhw. Mae hyn hefyd yn sicrhau y gallwch chi rannu\'ch ystafell yn hawdd ag eraill. +Gallwch ddewis cyhoeddi eich ystafell yng ngweinydd cartref eich cyfeiriadur ystafelloedd cyhoeddus." + "Cyhoeddi ystafell" + "Gwelededd yr ystafell" + "Diogelwch a phreifatrwydd" + diff --git a/features/securityandprivacy/impl/src/main/res/values-da/translations.xml b/features/securityandprivacy/impl/src/main/res/values-da/translations.xml new file mode 100644 index 0000000000..12ab70d44e --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-da/translations.xml @@ -0,0 +1,36 @@ + + + "Du skal bruge en adresse for at gøre det synligt i det offentlige register." + "Redigér adresse" + "Tilføj adresse" + "Alle skal anmode om adgang." + "Spørg om at deltage" + "Ja, aktivér kryptering" + "Når det først er aktiveret, kan kryptering for et rum ikke deaktiveres igen. Beskedhistorik vil kun være synlig for rummedlemmer, siden de blev inviteret, eller siden de blev medlem af rummet. +Ingen udover medlemmer af rummet vil være i stand til at læse beskeder. Dette kan forhindre bots og broer i at fungere korrekt. +Vi anbefaler ikke at aktivere kryptering for rum, som alle kan finde og deltage i." + "Aktivér kryptering?" + "Når kryptering først er aktiveret, kan den ikke deaktiveres igen." + "Kryptering" + "Aktivér end-to-end-kryptering" + "Alle kan være med." + "Enhver" + "Kun inviterede personer kan deltage i dette rum." + "Kun inviterede" + "Adgang" + "Medlemmer af gruppen" + "Grupper understøttes ikke i øjeblikket" + "Du skal bruge en adresse for at gøre det synligt i det offentlige register." + "Adresse" + "Tillad, at dette rum kan findes ved at søge i %1$s fortegnelse over offentlige rum" + "Gør det muligt at blive fundet ved søgninger i det offentlige register." + "Synlig i det offentlige register" + "Hvem kan læse historikken?" + "Kun medlemmer, efter de blev inviteret" + "Kun medlemmer siden valg af denne mulighed" + "Rum-adresser er en måde at finde og få adgang til værelser på. Dette sikrer også, at du nemt kan dele dit rum med andre. +Du kan vælge at offentliggøre dit rum i din hjemmeservers offentlige katalog over rum." + "Udgivelse af rum" + "Synlighed" + "Sikkerhed og privatliv" + diff --git a/features/securityandprivacy/impl/src/main/res/values-de/translations.xml b/features/securityandprivacy/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..c37481560d --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,36 @@ + + + "Du benötigst eine Chat-Adresse, um den Chat im Verzeichnis sichtbar zu machen." + "Chat-Adresse" + "Chat-Adresse hinzufügen" + "Jeder kann den Beitritt zum Chat anfragen, aber ein Admin oder Moderator müssen die Anfrage akzeptieren." + "Beitritt beantragen" + "Ja, Verschlüsselung aktivieren" + "Einmal angeschaltet kann die Verschlüsselung für einen Chat nicht mehr deaktiviert werden. Der Nachrichtenverlauf ist für Mitglieder nur sichtbar, seit sie eingeladen wurden oder dem Chat beigetreten sind. +Niemand außer den Chat Mitgliedern kann Nachrichten lesen. Dies kann verhindern, dass Bots und Bridges richtig funktionieren. +Wir empfehlen keine Verschlüsselung für Chats zu aktivieren, die jeder finden und denen jeder beitreten darf." + "Verschlüsselung aktivieren?" + "Einmal angeschaltet kann die Verschlüsselung nicht mehr deaktiviert werden." + "Verschlüsselung" + "Ende-zu-Ende-Verschlüsselung aktivieren" + "Jeder kann diesen Chat finden und ihm beitreten" + "Jeder" + "Personen können nur beitreten, wenn sie eingeladen werden." + "Nur auf Einladung" + "Chat Zugang" + "Spacemitglieder" + "Spaces werden zur Zeit nicht unterstützt." + "Du benötigst eine Chat-Adresse, um den Chat im Verzeichnis sichtbar zu machen." + "Chatroomadresse" + "Erlaube das Auffinden dieses Chats durch Suche im öffentlichen Verzeichnis von %1$s" + "Sichtbar im öffentlichen Verzeichnis" + "Jeder" + "Wer hat Zugriff auf den Nachrichtenverlauf" + "Nur Mitglieder, aber erst seit deren Einladung" + "Nur Mitglieder seit Auswahl dieser Option" + "Chat-Adressen machen es möglich, Chats zu finden und ihnen beizutreten. Dies erleichtert es, Chats mit anderen zu teilen. +Auf Wunsch kannst du deinen Chat im öffentlichen Verzeichnis deines Homeservers veröffentlichen." + "Veröffentlichung von Chats" + "Chatroomsichtbarkeit." + "Sicherheit & Datenschutz" + diff --git a/features/securityandprivacy/impl/src/main/res/values-el/translations.xml b/features/securityandprivacy/impl/src/main/res/values-el/translations.xml new file mode 100644 index 0000000000..c9a65b058d --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-el/translations.xml @@ -0,0 +1,33 @@ + + + "Θα χρειαστείτε μια διεύθυνση αίθουσας για να την κάνετε ορατή στον κατάλογο." + "Διεύθυνση αίθουσας" + "Προσθήκη διεύθυνσης αίθουσας" + "Οποιοσδήποτε μπορεί να ζητήσει να συμμετάσχει στην αίθουσα, αλλά ένας διαχειριστής ή συντονιστής θα πρέπει να αποδεχτεί το αίτημα." + "Αίτημα συμμετοχής" + "Ναι, ενεργοποιήστε την κρυπτογράφηση" + "Μόλις ενεργοποιηθεί, η κρυπτογράφηση για μια αίθουσα δεν μπορεί να απενεργοποιηθεί, το ιστορικό μηνυμάτων θα είναι ορατό μόνο για τα μέλη της αίθουσας από τότε που προσκλήθηκαν ή από τότε που συμμετείχαν στην αίθουσα. +Κανείς άλλος εκτός από τα μέλη της αίθουσας δεν θα μπορεί να διαβάσει τα μηνύματα. Αυτό μπορεί να εμποδίσει τη σωστή λειτουργία των bots και των γεφυρών. +Δεν συνιστούμε την ενεργοποίηση της κρυπτογράφησης για αίθουσες που μπορεί να βρει και να συμμετάσχει ο καθένας." + "Ενεργοποίηση κρυπτογράφησης;" + "Μόλις ενεργοποιηθεί, η κρυπτογράφηση δεν μπορεί να απενεργοποιηθεί." + "Κρυπτογράφηση" + "Ενεργοποίηση κρυπτογράφησης από άκρο σε άκρο" + "Οποιοσδήποτε μπορεί να βρει και να συμμετάσχει" + "Οποιοσδήποτε" + "Τα άτομα μπορούν να συμμετάσχουν μόνο εάν έχουν προσκληθεί" + "Μόνο πρόσκληση" + "Πρόσβαση στην αίθουσα" + "Μέλη χώρου" + "Οι χώροι δεν υποστηρίζονται προς το παρόν" + "Θα χρειαστείτε μια διεύθυνση αίθουσας για να την κάνετε ορατή στον κατάλογο." + "Επιστρέψτε την εύρεση αυτής της αίθουσας με αναζήτηση στον κατάλογο %1$s δημοσίων αιθουσών" + "Ορατή στον κατάλογο δημόσιων αιθουσών" + "Ποιος μπορεί να διαβάσει το ιστορικό" + "Μόνο μέλη από τη στιγμή που προσκλήθηκαν" + "Μόνο για μέλη μετά από αυτήν την επιλογή" + "Οι διευθύνσεις αιθουσών είναι τρόποι εύρεσης και πρόσβασης σε αίθουσες. Αυτό διασφαλίζει επίσης ότι μπορείτε εύκολα να μοιραστείτε την αίθουσα με άλλους. +Μπορείτε να επιλέξετε να δημοσιεύσετε την αίθουσά σας στον δημόσιο κατάλογο αιθουσών του αρχικού διακομιστή σας." + "Δημοσίευση αίθουσας" + "Ασφάλεια & απόρρητο" + diff --git a/features/securityandprivacy/impl/src/main/res/values-en-rUS/translations.xml b/features/securityandprivacy/impl/src/main/res/values-en-rUS/translations.xml new file mode 100644 index 0000000000..9a4b56e287 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-en-rUS/translations.xml @@ -0,0 +1,5 @@ + + + "Anyone in authorized spaces can join, but everyone else must request access." + "Anyone in authorized spaces can join." + diff --git a/features/securityandprivacy/impl/src/main/res/values-es/translations.xml b/features/securityandprivacy/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..c63bee50ef --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,33 @@ + + + "Necesitarás una dirección de sala para que sea visible en el directorio." + "Dirección de la sala" + "Agregar dirección de sala" + "Cualquiera puede solicitar unirse a la sala, pero un administrador o moderador tendrá que aceptar la solicitud." + "Solicitud para unirse" + "Sí, activar cifrado" + "Una vez activado, el cifrado de una sala no se puede desactivar. El historial de mensajes solo será visible para los miembros de la sala desde que fueron invitados o desde que se unieron a la sala. +Nadie más que los miembros de la sala podrán leer los mensajes. Esto puede impedir que los bots y los puentes funcionen correctamente. +No recomendamos habilitar el cifrado para las salas que cualquiera pueda encontrar y unirse." + "¿Activar cifrado?" + "Una vez activado, el cifrado no se puede desactivar." + "Cifrado" + "Activar el cifrado de extremo a extremo" + "Cualquiera puede encontrarla y unirse" + "Cualquiera" + "Las personas solo pueden unirse si están invitadas" + "Solo por invitación" + "Acceso a la sala" + "Miembros del espacio" + "No se admiten los espacios por el momento." + "Necesitarás una dirección de sala para que sea visible en el directorio." + "Permite encontrar esta sala buscando en el directorio de salas públicas de %1$s" + "Visible en el directorio de salas públicas" + "Quién puede leer el historial" + "Solo participantes desde que fueron invitados" + "Solo participantes desde que se selecciona esta opción" + "Las direcciones de sala son formas de buscar salas y acceder a ellas. Esto también garantiza que puedas compartir fácilmente tu sala con otras personas. +Puedes optar por publicar tu sala en el directorio de salas públicas de tu servidor base." + "Publicación de la sala" + "Seguridad y privacidad" + diff --git a/features/securityandprivacy/impl/src/main/res/values-et/translations.xml b/features/securityandprivacy/impl/src/main/res/values-et/translations.xml new file mode 100644 index 0000000000..8d74c35733 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-et/translations.xml @@ -0,0 +1,45 @@ + + + "Selleks, et jututuba oleks nähtav jututubade avalikus kataloogis, vajab ta aadressi." + "Muuda aadressi" + "Lisa aadress" + "Liituda saavad kõik volitatud kogukondade liikmed, kuid kõik teised peavad küsima võimalust ligipääsuks." + "Kõik võivad paluda jututoaga liitumist." + "Küsi võimalust liitumiseks" + "Liituda saavad kõik „%1$s“ kogukonna liikmed, kuid kõik teised peavad küsima võimalust ligipääsuks." + "Jah, lülita krüptimine sisse" + "Kui jututoa krüptimine on kord sisse lülitatud, siis seda välja lülitada ei saa. Sõnumite ajalugu on nähtav vaid jututoa liikmetele alates kutse saamise või liitumise hetkest. +Keegi teine peale jututoa liikmete ei saa sõnumeid lugeda. See võib takistada suhtlusrobotite ja/või võrgusildade toimimist. +Me ei soovita krüptimise kasutamist selliste avalike jututubade puhul, millega kõik võivad liituda." + "Kas võtame krüptimise kasutusele?" + "Kui krüptimine on kasutusel, siis seda enam väljalülitada ei saa." + "Krüptimine" + "Võta läbiv krüptimine kasutusele" + "Kõik võivad jututoaga liituda" + "Kõik kasutajad" + "Vali kogukonnad, mille liikmed saavad selle jututoaga liituda ilma kutseta. %1$s" + "Halda kogukondi" + "Liituda saab vaid kutse olemasolul" + "Vaid kutsega" + "Ligipääs" + "Liituda saavad kõik volitatud kogukondade liikmed." + "Liituda võivad kõik „%1$s“ liikmed." + "Kogukonna liikmed" + "Kogukondade tugi veel puudub" + "Selleks, et jututuba oleks nähtav jututubade avalikus kataloogis, vajab ta aadressi." + "Aadress" + "Võimalda leida seda jututuba avalikust kataloogist otsides „%1$s“" + "Luba leitavus avaliku kataloogi otsingust." + "Nähtav avalikus kataloogis" + "Kõik (ajalugu on avalik)" + "Muudatused ei mõjuta varasemaid sõnumeid, ainult uusi. %1$s" + "Kes võivad lugeda jututoa ajalugu" + "Liikmed peale kutse saamist" + "Liikmed (terviklik ajalugu)" + "Jututoa aadressid annavad võimaluse neid leida ning saada neile ligi. Samuti võimaldab see jututuba teistele huvilistele jagada. +Lisaks võid sa jututoa avaldada oma koduserveri avalikus jututubade kataloogis." + "Jututoa avaldamine" + "Aadressid on mugav viis jututubade ja kogukondade leidmiseks ning tagab mugava võimaluse nende jagamiseks." + "Nähtavus" + "Turvalisus ja privaatsus" + diff --git a/features/securityandprivacy/impl/src/main/res/values-eu/translations.xml b/features/securityandprivacy/impl/src/main/res/values-eu/translations.xml new file mode 100644 index 0000000000..7024f1c40b --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-eu/translations.xml @@ -0,0 +1,21 @@ + + + "Gelaren helbidea" + "Gehitu gelaren helbidea" + "Bai, gaitu zifratzea" + "Zifratzea" + "Edonork aurkitu eta bat egin dezake" + "Edonork" + "Gonbidatutako pertsonak bakarrik sartu ahal izango dira" + "Gonbidapen bidez" + "Gelarako sarbidea" + "Guneko kideak" + "Gaur-gaurkoz ez da guneekin bateragarria" + "Gelaren helbidea" + "Gela publikoen direktorioan ikusgai" + "Nork irakur dezake historia" + "Kideek bakarrik, gonbidatu zituztenetik" + "Kideek bakarrik, aukera hau hautatu zenetik" + "Gelaren ikusgarritasuna" + "Segurtasuna eta pribatutasuna" + diff --git a/features/securityandprivacy/impl/src/main/res/values-fa/translations.xml b/features/securityandprivacy/impl/src/main/res/values-fa/translations.xml new file mode 100644 index 0000000000..8cfa4147b5 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-fa/translations.xml @@ -0,0 +1,22 @@ + + + "نشانی اتاق" + "افزودن نشانی اتاق" + "درخواست دعوت" + "بله. به کار انداختن رمزنگاری" + "رمزگذاری فعال شود؟" + "پس از به کار افتادن، رمزنگاری قابل از کار انداختن نیست." + "رمزنگاری" + "هرکسی می‌تواند یافته و بپیوندد" + "هرکسی" + "افراد فقط در صورت دعوت می‌توانند بپیوندند" + "فقط دعوتی" + "دسترسی اتاق" + "اعضای فضا" + "در حال حاضر فضاها پشتیبانی نمی‌شوند" + "نشانی اتاق" + "هرکسی" + "انتشار اتاق" + "نمایانی اتاق" + "امنیت و محرمانگی" + diff --git a/features/securityandprivacy/impl/src/main/res/values-fi/translations.xml b/features/securityandprivacy/impl/src/main/res/values-fi/translations.xml new file mode 100644 index 0000000000..0f42d87675 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-fi/translations.xml @@ -0,0 +1,37 @@ + + + "Tarvitset osoitteen, jotta se näkyy julkisessa hakemistossa." + "Muokkaa osoitetta" + "Lisää osoite" + "Kaikkien on pyydettävä pääsyä." + "Pyydä liittymistä" + "Kyllä, ota salaus käyttöön" + "Kun salaus on kerran otettu käyttöön, sitä ei voi poistaa käytöstä. Viestihistoria näkyy vain huoneen jäsenille kutsusta tai liittymisestä lähtien. +Kukaan muu kuin huoneen jäsenet eivät pysty lukemaan viestejä. Tämä voi estää botteja tai siltoja toimimasta oikein. +Emme suosittele salauksen ottamista käyttöön huoneissa, jotka kuka tahansa voi löytää ja joihin kuka tahansa voi liittyä." + "Otetaanko salaus käyttöön?" + "Kun salaus on kerran otettu käyttöön, sitä ei voi poistaa käytöstä." + "Salaus" + "Ota päästä päähän -salaus käyttöön" + "Kuka tahansa voi liittyä." + "Kuka tahansa" + "Vain kutsutut henkilöt voivat liittyä." + "Vain kutsutut" + "Pääsy" + "Tilan jäsenet" + "Tiloja ei tällä hetkellä tueta" + "Tarvitset osoitteen, jotta se näkyy julkisessa hakemistossa." + "Osoite" + "Salli tämän huoneen löytäminen hakemalla %1$s -palvelimen julkisesta huonehakemistosta." + "Anna muiden löytää tämä julkisen hakemiston kautta." + "Näkyy julkisessa hakemistossa" + "Kuka tahansa" + "Kuka voi lukea viestihistoriaa" + "Jäsenet vasta kutsusta lähtien" + "Jäsenet tämän vaihtoehdon valinnan jälkeen" + "Huoneosoitteet ovat tapoja löytää ja käyttää huoneita. Näin voit myös helposti jakaa huoneesi muiden kanssa. +Voit halutessasi julkaista huoneesi kotipalvelimesi julkisessa huonehakemistossa." + "Huoneen julkaiseminen" + "Näkyvyys" + "Turvallisuus ja yksityisyys" + diff --git a/features/securityandprivacy/impl/src/main/res/values-fr/translations.xml b/features/securityandprivacy/impl/src/main/res/values-fr/translations.xml new file mode 100644 index 0000000000..5426d71706 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-fr/translations.xml @@ -0,0 +1,42 @@ + + + "Vous aurez besoin d’une adresse pour le rendre visible dans l’annuaire public." + "Modifier l’adresse" + "Ajouter une adresse" + "Tout le monde doit demander un accès." + "Demander à rejoindre" + "Oui, activer le chiffrement" + "Une fois activé, le chiffrement d’un salon ne peut pas être désactivé. L’historique des messages ne sera visible que pour les membres depuis qu’ils ont été invités ou depuis qu’ils ont rejoint le salon. +Personne d’autre que les membres du salon ne pourra lire les messages. Cela peut empêcher les bots et les bridges de fonctionner correctement. +Nous ne recommandons pas d’activer le chiffrement pour les salons que tout le monde peut trouver et rejoindre." + "Activer le chiffrement ?" + "Une fois activé, le chiffrement ne peut pas être désactivé." + "Chiffrement" + "Activer le chiffrement de bout en bout" + "Tout le monde peut rejoindre." + "Tout le monde" + "Choisissez les espaces dont les membres peuvent rejoindre ce salon sans invitation. %1$s" + "Gérer les espaces" + "Seules les personnes invitées peuvent rejoindre." + "Sur invitation uniquement" + "Accès" + "Toute personne se trouvant dans un espace autorisé peut joindre le salon." + "Toute personne de l’espace %1$s peut joindre le salon." + "Membres de l’espace" + "Les Espaces ne sont pas encore supportés" + "Vous aurez besoin d’une adresse pour le rendre visible dans l’annuaire public." + "Adresse" + "Autoriser le salon à apparaître dans les résultats de recherche dans le répertoire %1$s des salons publics" + "Permet d’être trouvé en recherchant dans l’annuaire public." + "Visible dans l’annuaire public" + "Tout le monde" + "Qui peux lire l’historique" + "Les membres uniquement depuis qu’ils ont été invités" + "Les membres uniquement depuis la sélection de cette option" + "Les adresses de salon sont un moyen de trouver et d’accéder aux salons. Cela vous permet également de partager facilement votre salon avec d’autres personnes. +Vous pouvez choisir de publier votre salon dans l’annuaire des salons publics de votre serveur d’accueil." + "Publication du salon" + "Les adresses permettent de trouver et d’accéder aux salons et aux espaces. Elles facilitent également leur partage avec d’autres personnes." + "Visibilité" + "Sécurité & confidentialité" + diff --git a/features/securityandprivacy/impl/src/main/res/values-hr/translations.xml b/features/securityandprivacy/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..401fe806f8 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,45 @@ + + + "Trebat će vam adresa kako bi bila vidljiva u javnom direktoriju." + "Uredi adresu" + "Dodaj adresu" + "Svatko tko se nalazi u ovlaštenim prostorima može se pridružiti, ali svi ostali moraju zatražiti pristup." + "Svi moraju zatražiti pristup." + "Zatraži pridruživanje" + "Svatko u %1$s može se pridružiti, ali svi ostali moraju zatražiti pristup." + "Da, omogući šifriranje" + "Nakon što se šifriranje za sobu omogući, više se neće moći onemogućiti. Povijest poruka bit će vidljiva samo članovima sobe otkad su pozvani ili otkad su joj se pridružili. +Nitko osim članova sobe neće moći čitati poruke. Zbog toga botovi i mostovi možda neće ispravno funkcionirati. +Ne preporučujemo omogućavanje šifriranja za sobe koje svatko može pronaći i pridružiti im se." + "Želite li omogućiti šifriranje?" + "Nakon što se šifriranje omogući, više se neće moći onemogućiti." + "Šifriranje" + "Omogući sveobuhvatno šifriranje" + "Svatko se može pridružiti." + "Svatko" + "Odaberite iz kojih se prostora članovi mogu pridružiti ovoj sobi bez pozivnice. %1$s" + "Upravljaj prostorima" + "Samo pozvane osobe mogu se pridružiti." + "Samo s pozivnicom" + "Pristup" + "Svatko tko se nalazi u ovlaštenim prostorima može se pridružiti." + "Svatko u %1$s može se pridružiti." + "Članovi prostora" + "Prostori trenutačno nisu podržani" + "Trebat će vam adresa kako bi bila vidljiva u javnom direktoriju." + "Adresa" + "Omogući pronalazak ove sobe pretraživanjem %1$s javnog direktorija soba" + "Omogući pronalazak pretraživanjem javnog direktorija." + "Vidljivo u javnom direktoriju" + "Svatko (povijest je javna)" + "Promjene neće utjecati na prethodne poruke, samo na nove. %1$s" + "Tko zna čitati povijest" + "Samo za članove nakon što su pozvani" + "Članovi (cjelokupna povijest)" + "Adrese soba služe za pronalaženje i pristup sobama. Time se također osigurava jednostavno dijeljenje sobe s drugim korisnicima. +Možete odabrati objavljivanje svoje sobe u javnom direktoriju soba na matičnom poslužitelju." + "Objavljivanje sobe" + "Adrese služe za pronalaženje soba i prostora te pristup njima. Tako ih ujedno možete jednostavno dijeliti s drugima." + "Vidljivost" + "Sigurnost i privatnost" + diff --git a/features/securityandprivacy/impl/src/main/res/values-hu/translations.xml b/features/securityandprivacy/impl/src/main/res/values-hu/translations.xml new file mode 100644 index 0000000000..503fdc8efe --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-hu/translations.xml @@ -0,0 +1,37 @@ + + + "Szüksége lesz egy szobacímre, hogy láthatóvá tegye a szobakatalógusban." + "Cím szerkesztése" + "Cím hozzáadása" + "Mindenkinek hozzáférést kell kérnie." + "Csatlakozás kérése" + "Igen, engedélyezze a titkosítást" + "Az engedélyezés után a szoba titkosítása nem tiltható le. Az üzenetek előzményei csak a szobatagok számára láthatók, amikor meghívást kaptak, vagy mióta csatlakoztak a szobához. +A szobatagokon kívül senki sem tudja olvasni az üzeneteket. Ez megakadályozhatja a botok és a hidak megfelelő működését. +Nem javasoljuk a titkosítás engedélyezését az olyan szobákban, amelyeket bárki megtalálhat és csatlakozhat." + "Engedélyezi a titkosítást?" + "Engedélyezés után a titkosítás nem tiltható le." + "Titkosítás" + "Végpontok közötti titkosítás engedélyezése" + "Bárki csatlakozhat." + "Bárki" + "Csak a meghívott emberek léphetnek be." + "Csak meghívásos" + "Hozzáférés" + "A tér tagjai" + "A terek jelenleg nem támogatottak" + "Szüksége lesz egy szobacímre, hogy láthatóvá tegye a szobakatalógusban." + "Cím" + "A szoba megtalálhatóvá tétele a(z) %1$s nyilvános szobakatalógusában való kereséssel." + "Lehetővé teszi, hogy a nyilvános szobakatalógusban megtalálható legyen." + "Látható a nyilvános szobakatalógusban" + "Bárki" + "Ki olvashatja az előzményeket" + "Csak a tagok, a meghívásuktól kezdődően" + "Csak a tagok, a beállítás választásától kezdődően" + "A szobacímek a szobák megtalálásának és elérésnek módjai. Ez azt is biztosítja, hogy könnyen megoszthatja a szobáját másokkal. +Kiválaszthatja, hogy szobáját közzéteszi-e a Matrix-kiszolgáló nyilvános szobakatalógusában." + "Szoba közzététele" + "Láthatóság" + "Biztonság és adatvédelem" + diff --git a/features/securityandprivacy/impl/src/main/res/values-in/translations.xml b/features/securityandprivacy/impl/src/main/res/values-in/translations.xml new file mode 100644 index 0000000000..ea7668dac5 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-in/translations.xml @@ -0,0 +1,32 @@ + + + "Anda akan memerlukan alamat ruangan untuk membuatnya terlihat dalam direktori." + "Alamat ruangan" + "Tambahkan alamat ruangan" + "Siapa pun dapat meminta untuk bergabung dengan ruangan tetapi administrator atau moderator harus menerima permintaan tersebut." + "Minta untuk bergabung" + "Ya, aktifkan enkripsi" + "Setelah diaktifkan, encryption untuk sebuah ruangan tidak dapat dinonaktifkan, Riwayat pesan hanya akan terlihat oleh anggota ruangan sejak mereka diundang atau sejak mereka bergabung dengan ruangan tersebut. +Tidak ada orang lain selain anggota ruangan yang dapat membaca pesan. Hal ini dapat mencegah bot dan jembatan bekerja dengan benar. +Kami tidak menyarankan untuk mengaktifkan enkripsi untuk ruangan yang dapat ditemukan dan diikuti oleh siapa pun." + "Aktifkan enkripsi?" + "Setelah diaktifkan, enkripsi tidak dapat dinonaktifkan." + "Enkripsi" + "Aktifkan enkripsi ujung ke ujung" + "Siapa pun dapat menemukan dan bergabung" + "Siapa pun" + "Orang hanya dapat bergabung jika mereka diundang" + "Hanya undangan" + "Akses ruangan" + "Anggota space" + "Space saat ini tidak didukung" + "Anda akan memerlukan alamat ruangan untuk membuatnya terlihat dalam direktori." + "Izinkan ruangan ini ditemukan dengan mencari direktori ruangan %1$s publik" + "Terlihat di direktori ruangan publik" + "Siapa yang bisa membaca riwayat" + "Hanya anggota sejak mereka diundang" + "Hanya anggota sejak memilih opsi ini" + "Alamat ruangan adalah cara untuk menemukan dan mengakses ruangan. Ini juga memastikan Anda dapat dengan mudah berbagi ruangan dengan orang lain. Anda dapat memilih untuk menerbitkan ruangan Anda di direktori ruangan publik homeserver Anda." + "Penerbitan ruangan" + "Keamanan & privasi" + diff --git a/features/securityandprivacy/impl/src/main/res/values-it/translations.xml b/features/securityandprivacy/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..2854a69ce1 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,38 @@ + + + "Per renderlo visibile nell\'elenco pubblico, avrai bisogno di un indirizzo." + "Modifica indirizzo" + "Aggiungi indirizzo" + "Chiunque deve richiedere l\'accesso." + "Chiedi di entrare" + "Sì, attiva la crittografia" + "Una volta attivata, la crittografia di una stanza non può essere disattivata, la cronologia dei messaggi sarà visibile solo ai membri della stanza da quando sono stati invitati o da quando sono entrati nella stanza. +Nessuno, oltre ai membri della stanza, sarà in grado di leggere i messaggi. Ciò potrebbe impedire ai bot e ai bridge di funzionare correttamente. +Non consigliamo di attivare la crittografia per le stanze che chiunque può trovare e in cui può entrare." + "Attivare la crittografia?" + "Una volta attivata, la crittografia non può essere disattivata." + "Crittografia" + "Attiva la crittografia end-to-end" + "Chiunque può partecipare." + "Chiunque" + "Solo le persone invitate possono entrare." + "Solo su invito" + "Accesso" + "Membri dello spazio" + "Gli spazi non sono attualmente supportati" + "Per renderlo visibile nell\'elenco pubblico, avrai bisogno di un indirizzo." + "Indirizzo" + "Consenti la ricerca di questa stanza effettuando una ricerca nell\'elenco delle stanze pubbliche di %1$s" + "Consenti di essere trovato effettuando una ricerca nell\'elenco pubblico." + "Visibile nell\'elenco pubblico" + "Chiunque" + "Chi può leggere la cronologia messaggi" + "Solo membri da quando sono stati invitati" + "Solo membri da dopo aver selezionato questa opzione" + "Gli indirizzi delle stanze sono modi per trovare e accedervi. In questo modo puoi anche condividere facilmente la tua stanze con altri. +Puoi scegliere di pubblicare la tua stanza nell\'elenco delle stanza pubbliche dell\'homeserver." + "Pubblicazione della stanza" + "Gli indirizzi sono un modo per trovare e accedere a stanze e spazi. Questo ti consente anche di condividerli facilmente con altri." + "Visibilità" + "Sicurezza e privacy" + diff --git a/features/securityandprivacy/impl/src/main/res/values-ko/translations.xml b/features/securityandprivacy/impl/src/main/res/values-ko/translations.xml new file mode 100644 index 0000000000..e18ba05140 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-ko/translations.xml @@ -0,0 +1,33 @@ + + + "디렉토리에 표시하려면 방 주소가 필요합니다." + "방 주소" + "방 주소 추가" + "누구나 방에 참여 요청을 할 수 있지만, 관리자나 운영자가 요청을 수락해야 합니다." + "참가 요청" + "예, 암호화 활성화" + "일단 활성화되면, 방의 암호화는 비활성화할 수 없습니다. 메시지 기록은 방에 초대된 후 또는 방에 참여한 이후부터 방 구성원만 볼 수 있습니다. +방 구성원 외에는 아무도 메시지를 읽을 수 없습니다. 이로 인해 봇과 브리지가 제대로 작동하지 않을 수 있습니다. +누구나 찾고 참여할 수 있는 방에는 암호화를 활성화하지 않는 것이 좋습니다." + "암호화 활성화?" + "일단 활성화되면, 암호화는 비활성화할 수 없습니다." + "암호화" + "종단간 암호화 활성화" + "누구나 찾을 수 있고 참여할 수 있습니다." + "누구나" + "초대받은 사용자만 가입할 수 있습니다." + "초대 전용" + "방 액세스" + "스페이스 멤버들" + "스페이스는 현재 지원되지 않습니다" + "디렉토리에 표시하려면 방 주소가 필요합니다." + "%1$s 공개 방 디렉토리에서 이 방을 검색할 수 있도록 허용합니다" + "공개 룸 디렉토리에 표시됨" + "누가 기록을 읽을 수 있는가" + "초대받은 회원만 이용 가능합니다" + "이 옵션을 선택한 회원만 이용 가능합니다." + "방 주소는 방을 찾고 액세스하는 방법입니다. 이를 통해 다른 사람들과 방을 쉽게 공유할 수 있습니다. +홈서버의 공개 방 디렉토리에 방을 공개할지 여부를 선택할 수 있습니다." + "방 게시" + "보안 및 개인정보 보호" + diff --git a/features/securityandprivacy/impl/src/main/res/values-nb/translations.xml b/features/securityandprivacy/impl/src/main/res/values-nb/translations.xml new file mode 100644 index 0000000000..3a9d90345d --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-nb/translations.xml @@ -0,0 +1,36 @@ + + + "Du trenger en adresse for å gjøre den synlig i den offentlige katalogen." + "Rediger adresse" + "Legg til adresse" + "Alle må be om tilgang." + "Be om å bli med" + "Ja, aktiver kryptering" + "Når kryptering for et rom er aktivert, kan den ikke deaktiveres. Meldingshistorikken vil bare være synlig for rommedlemmer siden de ble invitert eller siden de ble med i rommet. +Ingen andre enn rommedlemmene vil kunne lese meldingene. Dette kan føre til at bots og broer ikke fungerer som de skal. +Vi anbefaler ikke å aktivere kryptering for rom som hvem som helst kan finne og bli med i." + "Vil du aktivere kryptering?" + "Når kryptering er aktivert, kan det ikke deaktiveres." + "Kryptering" + "Aktiver ende-til-ende-kryptering" + "Alle kan bli med." + "Alle" + "Bare inviterte personer kan bli med." + "Kun for inviterte" + "Tilgang" + "Medlemmer av område" + "Områder støttes ikke for øyeblikket" + "Du trenger en adresse for å gjøre den synlig i den offentlige katalogen." + "Adresse" + "Tillat at dette rommet blir funnet ved å søke %1$s offentlig romkatalog" + "Synlig i offentlig katalog" + "Alle (historikken er offentlig)" + "Hvem kan lese historikk" + "Medlemmer siden de ble invitert" + "Medlemmer (full historikk)" + "Romadresser er måter å finne og få tilgang til rom på. Dette sikrer også at du enkelt kan dele rommet ditt med andre. +Du kan velge å publisere rommet ditt i hjemeserverens offentlige romkatalog." + "Publisering av rom" + "Synlighet" + "Sikkerhet og personvern" + diff --git a/features/securityandprivacy/impl/src/main/res/values-nl/translations.xml b/features/securityandprivacy/impl/src/main/res/values-nl/translations.xml new file mode 100644 index 0000000000..6925a40289 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-nl/translations.xml @@ -0,0 +1,5 @@ + + + "Vraag om toe te treden" + "Iedereen" + diff --git a/features/securityandprivacy/impl/src/main/res/values-pl/translations.xml b/features/securityandprivacy/impl/src/main/res/values-pl/translations.xml new file mode 100644 index 0000000000..f49f944872 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-pl/translations.xml @@ -0,0 +1,36 @@ + + + "Aby pokój był widoczny w katalogu, potrzebny jest adres pokoju." + "Adres pokoju" + "Dodaj adres pokoju" + "Każdy może poprosić o dołączenie do pokoju, ale administrator lub moderator będzie musiał zatwierdzić żądanie." + "Poproś o dołączenie" + "Tak, włącz szyfrowanie" + "Po włączeniu szyfrowanie pokoju nie może zostać wyłączone, a historia wiadomości będzie widoczna tylko dla członków od momentu, w którym dołączyli lub zostali zaproszeni. +Nikt poza członkami pokoju nie będzie mógł czytać wiadomości. Może to wpłynąć na prawidłowe działanie botów lub mostków. +Odradzamy włączanie szyfrowania dla pokoi, które każdy może znaleźć i do których każdy może dołączyć." + "Włączyć szyfrowanie?" + "Po włączeniu szyfrowania nie można wyłączyć." + "Szyfrowanie" + "Włącz szyfrowanie end-to-end" + "Każdy może znaleźć i dołączyć" + "Wszyscy" + "Tylko osoby z zaproszeniem mogą dołączyć" + "Tylko zaproszenie" + "Dostęp do pokoju" + "Członkowie przestrzeni" + "Przestrzenie nie są obecnie wspierane" + "Aby pokój był widoczny w katalogu, potrzebny jest adres pokoju." + "Adres pokoju" + "Zezwól na znalezienie tego pokoju wyszukując %1$s w katalogu pokoi publicznych" + "Widoczny w katalogu pokoi publicznych" + "Ktokolwiek" + "Kto może czytać historię" + "Od momentu kiedy członkowie zostali zaproszeni" + "Członkowie od momentu włączenia tej opcji" + "Adresy pokoju umożliwiają łatwe znalezienie i dołączenie do pokojów. +Również możesz się zdecydować na upublicznienie Twojego serwera w katalogu pokoi publicznych." + "Publikowanie pokoju" + "Widoczność pokoju" + "Bezpieczeństwo i prywatność" + diff --git a/features/securityandprivacy/impl/src/main/res/values-pt-rBR/translations.xml b/features/securityandprivacy/impl/src/main/res/values-pt-rBR/translations.xml new file mode 100644 index 0000000000..658067be2d --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-pt-rBR/translations.xml @@ -0,0 +1,43 @@ + + + "Você precisará de um endereço para torná-la visível no diretório." + "Editar endereço" + "Adicionar endereço" + "Qualquer um nos espaços autorizados podem entrar, mas todos os outros devem pedir acesso." + "Qualquer um pode pedir acesso, mas um administrador terá que aceitar o pedido." + "Pedir para entrar" + "Qualquer um em %1$s pode entrar, mas todos os outros devem pedir acesso." + "Sim, ativar a criptografia" + "Uma vez ativada, a criptografia de uma sala não pode ser desativada. O histórico de mensagens só será visível para os membros da sala desde que foram convidados ou desde que entraram na sala. +Ninguém além dos membros da sala poderá ler as mensagens. Isso pode impedir que os bots e as pontes funcionem corretamente. +Não recomendamos que você ative a criptografia para salas que qualquer pessoa possa encontrar e participar." + "Ativar a criptografia?" + "Uma vez ativada, a criptografia não poderá ser desativada." + "Criptografia" + "Ativar a criptografia de ponta a ponta" + "Qualquer um pode entrar" + "Qualquer pessoa" + "Escolha os espaços dos quais os membros podem entrar nesta sala sem um convite. %1$s" + "Gerenciar espaços" + "Apenas pessoas convidadas podem entrar." + "Privado" + "Acesso" + "Qualquer um em espaços autorizados podem entrar." + "Qualquer um em %1$s pode entrar." + "Membros do espaço" + "No momento, não há suporte aos espaços" + "Você precisará de um endereço para torná-la visível no diretório." + "Endereço publicado" + "Permitir que esta sala seja encontrada pesquisando diretório de salas públicas de %1$s" + "Permite que seja encontrada ao buscar no diretório público." + "Visível no diretório público" + "Quem pode ler o histórico" + "Membros desde o convite" + "Membros (histórico completo)" + "Os endereços das salas são formas de encontrar e acessar as salas. Isso também garante que você possa compartilhar facilmente sua sala com outras pessoas. +Você pode optar por publicar sua sala no diretório público de salas do seu servidor-casa." + "Publicação da sala" + "Os endereços das salas são formas de encontrar e acessar as salas. Isso também garante que você possa compartilhá-las facilmente com outras pessoas." + "Visibilidade" + "Segurança e privacidade" + diff --git a/features/securityandprivacy/impl/src/main/res/values-pt/translations.xml b/features/securityandprivacy/impl/src/main/res/values-pt/translations.xml new file mode 100644 index 0000000000..26585dc4f9 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-pt/translations.xml @@ -0,0 +1,36 @@ + + + "É necessário um endereço para tornar a sala visível no diretório." + "Endereço da sala" + "Adicionar endereço de sala" + "Qualquer pessoa pode pedir para entrar na sala, mas um administrador ou moderador tem que aceitar o pedido." + "Pedir para participar" + "Sim, ativar cifragem" + "Uma vez ativada, a cifragem não pode ser desativada. O histórico de mensagens só será visível a membros a partir do momento em que foram convidados ou que entraram na sala. +Ninguém além dos membros poderão ler quaisquer mensagens. Isto pode impedir que robôs (\"bots\") e pontes (\"bridges\") funcionem devidamente. +Não recomendamos ativar a cifragem em salas que qualquer pessoa possa encontrar e entrar." + "Ativar cifragem?" + "Uma vez ativada, a cifragem não pode ser desativada." + "Cifragem" + "Ativar cifragem ponta-a-ponta" + "Qualquer pessoa pode encontrar a sala e entrar" + "Qualquer pessoa" + "Só é possível entrar tendo um convite" + "Apenas por convite" + "Acesso à sala" + "Membros do espaço" + "Os espaços ainda não estão implementados" + "É necessário um endereço para tornar a sala visível no diretório." + "Endereço da sala" + "Permite que esta sala seja encontrada através do diretório público do %1$s." + "Visível no diretório público de salas" + "Qualquer pessoa" + "Quem pode ler o histórico de mensagens" + "Apenas membros, desde o momento em que forem convidados" + "Apenas membros, desde o memento em que esta opção for selecionada" + "Estes endereços permitem encontrar e aceder a sala, bem como a sua fácil partilha com outros. +Podes escolher publicar a sala no diretório público do teu servidor." + "Publicar sala" + "Visibilidade da sala" + "Segurança e privacidade" + diff --git a/features/securityandprivacy/impl/src/main/res/values-ro/translations.xml b/features/securityandprivacy/impl/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..701cb72370 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-ro/translations.xml @@ -0,0 +1,43 @@ + + + "Veți avea nevoie de o adresă pentru a o face vizibilă în directorul public." + "Editați adresa" + "Adăugați o adresă" + "Oricine se află în spațiile autorizate se poate alătura, dar toți ceilalți trebuie să solicite accesul." + "Toată lumea trebuie să solicite acces." + "Cereți să vă alăturați" + "Oricine în %1$s se poate alătura, dar toți ceilalți trebuie să solicite acces." + "Da, activați criptarea" + "Odată activată, criptarea pentru o cameră nu poate fi dezactivată. Mesajele anterioare vor fi vizibile numai pentru membrii camerei de la momentul la care au fost invitați sau de la momentul la care s-au alăturat camerei. +Nimeni în afară de membrii camerei nu va putea citi messaje. Acest lucru poate împiedica funcționarea corectă a boților și a punților. +Nu recomandăm activarea criptării pentru camerele pe care oricine le poate găsi și la care se poate alătura." + "Activați criptarea?" + "Odată activată, criptarea nu poate fi dezactivată." + "Criptare" + "Activați criptarea end-to-end" + "Oricine se poate alătura." + "Oricine" + "Alegeți membrii căror spații se pot alătura acestei camere fără invitație. %1$s" + "Gestionați spațiile" + "Doar persoanele invitate se pot alătura." + "Doar pe bază de invitație" + "Acces" + "Oricine se află într-un spațiu autorizat poate participa." + "Oricine din %1$s se poate alătura." + "Membrii spațiului" + "Spațiile nu sunt momentan suportate." + "Veți avea nevoie de o adresă pentru a o face vizibilă în directorul public." + "Adresă" + "Permiteți găsirea acestei camere prin căutarea în directorul de camere publice al %1$s" + "Permiteți găsirea prin căutarea în directorul public." + "Vizibilă în directorul de camere publice" + "Cine poate citi mesajele anterioare" + "Doar pentru membri, de la momentul în care au fost invitați" + "Doar pentru membri, după selectarea acestei opțiuni" + "Adresele camerelor sunt modalități de a găsi și accesa camere. Acest lucru vă asigură, de asemenea, că puteți partaja cu ușurință camera dumneavoastră cu alte persoane. +Puteți alege să publicați camera în directorul public al camerelor serverului dumneavoastră." + "Publicare cameră" + "Adresele sunt o modalitate de a găsi și accesa camere și spații. Acest lucru asigură, de asemenea, că le puteți partaja cu ușurință cu alții." + "Vizibilitate" + "Securitate & confidențialitate" + diff --git a/features/securityandprivacy/impl/src/main/res/values-ru/translations.xml b/features/securityandprivacy/impl/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..1a138fbb94 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-ru/translations.xml @@ -0,0 +1,37 @@ + + + "Вам понадобится адрес комнаты, чтобы сделать ее видимой в каталоге." + "Редактировать адрес комнаты" + "Добавить адрес" + "Каждый должен запросить доступ." + "Попросить присоединиться" + "Да, включить шифрование" + "Шифрование комнаты нельзя будет отключить, история сообщений будет видна только участникам комнаты с момента их приглашения или с момента присоединения к комнате. +Никто, кроме членов комнаты, не сможет читать сообщения. Это может помешать ботам и мостам работать корректно. +Мы не рекомендуем включать шифрование для комнат, в которые может найти и присоединиться любой желающий." + "Включить шифрование?" + "После включения, шифрование не может быть отключено." + "Шифрование" + "Включить сквозное шифрование" + "Любой желающий может найти и присоединиться" + "Любой" + "Присоединиться могут только приглашенные люди." + "Только по приглашению" + "Доступ" + "Участники пространства" + "Пространства в настоящее время не поддерживаются." + "Вам понадобится адрес комнаты, чтобы сделать ее видимой в каталоге." + "Адрес" + "Опубликовать %1$s в каталоге публичных комнат" + "Разрешить поиск в публичном каталоге." + "Доступна в списке публичных комнат" + "Любой" + "Кто может читать историю" + "Участники только с тех пор, как они были приглашены" + "Только для участников с момента выбора этой опции" + "Адреса комнат — это способ найти комнату и получить к ней доступ. Это также гарантирует, что вы сможете легко поделиться своей комнатой с другими. +Вы можете опубликовать свою комнату в каталоге общедоступных комнат на домашнем сервере." + "Публикация комнат" + "Видимость" + "Безопасность и конфиденциальность" + diff --git a/features/securityandprivacy/impl/src/main/res/values-sk/translations.xml b/features/securityandprivacy/impl/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..ee346dd5d0 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-sk/translations.xml @@ -0,0 +1,42 @@ + + + "Budete potrebovať adresu, aby sa zobrazovala vo verejnom adresári." + "Upraviť adresu" + "Pridať adresu" + "Všetci musia požiadať o prístup." + "Požiadať o pripojenie" + "Áno, povoliť šifrovanie" + "Po aktivácii nie je možné zakázať šifrovanie pre miestnosť. História správ bude viditeľná len pre členov miestnosti, odkedy boli pozvaní alebo keď vstúpili do miestnosti. +Nikto okrem členov miestnosti nebude môcť čítať správy. +To môže brániť správnemu fungovaniu robotov a premostení. Neodporúčame povoliť šifrovanie pre miestnosti, ktoré môže ktokoľvek nájsť a pripojiť sa k nim." + "Povoliť šifrovanie?" + "Po zapnutí už šifrovanie nie je možné vypnúť." + "Šifrovanie" + "Povoliť end-to-end šifrovanie" + "Pripojiť sa môže ktokoľvek." + "Ktokoľvek" + "Vyberte, ktorých členovia priestorov sa môžu pripojiť k tejto miestnosti bez pozvánky. %1$s" + "Spravovať priestory" + "Pripojiť sa môžu iba pozvaní ľudia." + "Iba na pozvánku" + "Prístup" + "Ktokoľvek v povolených priestoroch sa môže pripojiť." + "Ktokoľvek v %1$s sa môže pripojiť." + "Členovia priestoru" + "Priestory momentálne nie sú podporované" + "Budete potrebovať adresu, aby sa zobrazovala vo verejnom adresári." + "Adresa" + "Umožniť vyhľadanie tejto miestnosti v adresári verejných miestností %1$s" + "Umožniť nájdenie vyhľadávaním vo verejnom adresári." + "Viditeľné vo verejnom adresári" + "Ktokoľvek" + "Kto môže čítať históriu" + "Len pre členov, odkedy boli pozvaní" + "Len členovia od zvolenia tejto možnosti" + "Adresy miestností predstavujú spôsoby, ako nájsť a získať prístup k miestnostiam. To tiež zaisťuje, že môžete jednoducho zdieľať svoju miestnosť s ostatnými. +Môžete sa rozhodnúť zverejniť svoju miestnosť v adresári verejných miestností vášho domovského servera." + "Zverejnenie miestnosti" + "Adresy sú spôsob, ako nájsť a získať prístup do miestností a priestorov. To tiež zabezpečuje, že ich môžete jednoducho zdieľať s ostatnými." + "Viditeľnosť" + "Bezpečnosť a súkromie" + diff --git a/features/securityandprivacy/impl/src/main/res/values-sv/translations.xml b/features/securityandprivacy/impl/src/main/res/values-sv/translations.xml new file mode 100644 index 0000000000..c77201aa5b --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-sv/translations.xml @@ -0,0 +1,33 @@ + + + "Du behöver en rumsadress för att göra den synlig i katalogen." + "Rumsadress" + "Lägg till rumsadress" + "Vem som helst kan be om att gå med i rummet men en administratör eller moderator måste acceptera begäran." + "Be om att gå med" + "Ja, aktivera kryptering" + "När det är aktiverat kan kryptering för ett rum inte inaktiveras, meddelandehistoriken visas bara för rumsmedlemmar sedan de blev inbjudna eller sedan de gick med i rummet. +Ingen förutom rumsmedlemmarna kommer att kunna läsa meddelanden. Detta kan förhindra att bots och bridges fungerar korrekt. +Vi rekommenderar inte att aktivera kryptering för rum som vem som helst kan hitta och gå med i." + "Aktivera kryptering?" + "Efter aktivering kan kryptering inte inaktiveras." + "Kryptering" + "Aktivera totalsträckskryptering" + "Vem som helst kan hitta och gå med" + "Vem som helst" + "Användare kan bara gå med om de är inbjudna" + "Endast inbjudan" + "Tillgång till rum" + "Utrymmesmedlemmar" + "Utrymmen stöds för närvarande inte" + "Du behöver en rumsadress för att göra den synlig i katalogen." + "Tillåt att detta rum hittas genom att söka i den offentliga rumskatalogen på %1$s" + "Synlig i katalogen för offentliga rum" + "Vem kan läsa historik" + "Endast medlemmar sedan de bjöds in" + "Endast medlemmar sedan det här alternativet har valts" + "Rumsadresser är sätt att hitta och komma åt rum. Detta säkerställer också att du enkelt kan dela ditt rum med andra. +Du kan välja att publicera ditt rum i din hemservers offentliga rumskatalog." + "Rumspublicering" + "Säkerhet och sekretess" + diff --git a/features/securityandprivacy/impl/src/main/res/values-tr/translations.xml b/features/securityandprivacy/impl/src/main/res/values-tr/translations.xml new file mode 100644 index 0000000000..940fdc9a91 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-tr/translations.xml @@ -0,0 +1,36 @@ + + + "Dizinde görünür hale getirmek için bir oda adresine ihtiyacınız olacak." + "Oda adresi" + "Oda adresi ekle" + "Herkes odaya katılma isteğinde bulunabilir ancak bir yönetici veya moderatörün isteği kabul etmesi gerekir." + "Katılmak için sor" + "Evet, şifrelemeyi etkinleştir" + "Etkinleştirildikten sonra, bir oda için şifreleme devre dışı bırakılamaz, Mesaj geçmişi yalnızca davet edildiklerinden veya odaya katıldıklarından beri oda üyeleri için görünür olacaktır. +Oda üyeleri dışında hiç kimse mesajları okuyamayacaktır. Bu, botların ve köprülerin düzgün çalışmasını engelleyebilir. +Herkesin bulabileceği ve katılabileceği odalar için şifrelemenin etkinleştirilmesini önermiyoruz." + "Şifrelemeyi etkinleştir?" + "Açıldıktan donra şifreleme kapatılamaz." + "Şifreleme" + "Uçtan uca şifrelemeyi etkinleştir" + "Herkes bulabilir ve katılabilir" + "Herkes" + "İnsanlar yalnızca davet edildiklerinde katılabilirler" + "Yalnızca davet" + "Oda Erişimi" + "Alan üyeleri" + "Alanlar şu anda desteklenmiyor" + "Dizinde görünür hale getirmek için bir oda adresine ihtiyacınız olacak." + "Oda adresi" + "Bu odanın %1$s genel oda dizininde arama yapılarak bulunmasına izin verin" + "Genel oda dizininde görünür" + "Herkes" + "Geçmişi kimler okuyabilir ?" + "Sadece üyeler (davet edildiklerinden beri)" + "Bu seçeneği seçtiğinden beri yalnızca üyeler" + "Oda adresleri, odaları bulmanın ve odalara erişmenin yoludur. Bu aynı zamanda odanızı başkalarıyla kolayca paylaşabilmenizi sağlar. +Odanızı ana sunucunuzun genel oda dizininde yayınlamayı seçebilirsiniz." + "Oda yayınlama" + "Oda görünürlüğü" + "Güvenlik ve gizlilik" + diff --git a/features/securityandprivacy/impl/src/main/res/values-uk/translations.xml b/features/securityandprivacy/impl/src/main/res/values-uk/translations.xml new file mode 100644 index 0000000000..511ebbd980 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-uk/translations.xml @@ -0,0 +1,36 @@ + + + "Вам знадобиться адреса кімнати, щоб зробити її видимою в каталозі." + "Адреса кімнати" + "Додати адресу кімнати" + "Будь-хто може надіслати запит приєднатися до кімнати, але адміністратор або модератор повинні прийняти запит." + "Запросити приєднатися" + "Так, увімкнути шифрування" + "Після ввімкнення шифрування кімнати, його неможливо вимкнути, історію повідомлень бачитимуть лише учасники кімнати, яких було запрошено або які приєдналися до кімнати. +Ніхто, крім учасників кімнати, не зможе прочитати повідомлення. Це може перешкоджати коректній роботі ботів і мостів. +Ми не радимо вмикати шифрування для кімнат, які будь-хто може знайти та до яких може приєднатися всі." + "Увімкнути шифрування?" + "Після ввімкнення шифрування неможливо вимкнути." + "Шифрування" + "Увімкнути наскрізне шифрування" + "Будь-хто може знайти та приєднатися." + "Кожний" + "Люди можуть приєднатися, лише якщо їх запросили" + "Лише запрошені" + "Доступ до кімнати" + "Учасники простору" + "Простори наразі не підтримуються" + "Вам знадобиться адреса кімнати, щоб зробити її видимою в каталозі." + "Адреса кімнати" + "Дозвольте, щоб цю кімнату можна було знайти за допомогою пошуку в каталозі загальнодоступних кімнат %1$s " + "Видима в каталозі загальнодоступних кімнат" + "Будь-хто" + "Хто може читати історію" + "Лише учасники з моменту запрошення" + "Лише учасники після вибору цього параметра" + "Адреси кімнат — це спосіб знайти кімнату та отримати до неї доступ. Це також гарантує, що ви можете легко поділитися своєю кімнатою з іншими. +Ви можете опублікувати свою кімнату в каталозі загальнодоступних кімнат вашого домашнього сервера." + "Публікація в кімнаті" + "Видимість кімнати" + "Безпека й приватність" + diff --git a/features/securityandprivacy/impl/src/main/res/values-uz/translations.xml b/features/securityandprivacy/impl/src/main/res/values-uz/translations.xml new file mode 100644 index 0000000000..671308c8a1 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-uz/translations.xml @@ -0,0 +1,32 @@ + + + "Katalogda ko‘rinadigan qilish uchun xona manzili kerak bo‘ladi." + "Xona manzili" + "Xona manzilini kiritish" + "Xonaga qo‘shilishni istalgan kishi so‘rashi mumkin, lekin administrator yoki moderator so‘rovni qabul qilishi kerak" + "Qo‘shilishni so‘rang" + "Ha, shifrlashni yoqish" + "Yoqilgandan so‘ng, xona uchun shifrlashni o‘chirib bo‘lmaydi. Xabarlar tarixi faqat xona a’zolari taklif qilinganidan yoki xonaga qo‘shilganidan keyingi davrdan boshlab ko‘rinadi. Xona a’zolaridan tashqari hech kim xabarlarni o‘qiy olmaydi. Bu botlar va ko‘priklarning to‘g‘ri ishlashiga to‘sqinlik qilishi mumkin. +Shu sababli, har kim topishi va qo‘shilishi mumkin bo‘lgan xonalar uchun shifrlashni yoqishni tavsiya etmaymiz." + "Shifrlash yoqilsinmi?" + "Yoqilgandan keyin shifrlashni faolsizlantirish imkonsiz." + "Shifrlash" + "End-to-end shifrlashni yoqish" + "Istalgan kishi topishi va qo‘shilishi mumkin" + "Har kim" + "Odamlar faqat taklif qilingan taqdirdagina qo‘shilishi mumkin" + "Faqat taklif qilish" + "Xonaga kirish huquqi" + "Maydon a’zolari" + "Hozirda maydonlar qo‘llab-quvvatlanmaydi" + "Katalogda ko‘rinadigan qilish uchun xona manzili kerak bo‘ladi." + "Bu xonani %1$s umumiy xonalar ro‘yxatidan qidirib topish imkoniyatini berish" + "Umumiy xona ro‘yxatida ko‘rinadi" + "Tarixni kim o‘qiy oladi" + "Taklif qilinganidan buyon faqat a’zolar" + "A’zolar faqat bu parametr tanlanganidan keyin" + "Xona manzillari xonalarni topish va ularga kirish usullaridir. Bu shuningdek xonangizni boshqalar bilan oson ulashish imkonini beradi. +Xonangizni o‘z homeserveringizning ommaviy xonalar ro‘yxatida e’lon qilishni tanlashingiz mumkin." + "xona nashriyoti" + "Xavfsizlik va maxfiylik" + diff --git a/features/securityandprivacy/impl/src/main/res/values-zh-rTW/translations.xml b/features/securityandprivacy/impl/src/main/res/values-zh-rTW/translations.xml new file mode 100644 index 0000000000..93e5880ae1 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-zh-rTW/translations.xml @@ -0,0 +1,37 @@ + + + "您需要地址才能在公開目錄中顯示。" + "編輯地址" + "新增地址" + "所有人都必須申請存取權。" + "要求加入" + "是的,啟用加密" + "啟用後就無法停用聊天室的加密,只有受邀的聊天室成員或加入聊天室後才能看到訊息歷史紀錄。 +除了聊天室成員以外,任何人都不能讀取訊息。這可能會讓機器人與橋接無法正常運作。 +我們不建議對任何人都可以找到並加入的聊天室啟用加密。" + "啟用加密?" + "一旦啟用就無法停用加密。" + "加密" + "啟用端到端加密" + "任何人都可以加入。" + "任何人" + "僅受邀者才能加入。" + "僅限邀請" + "存取權" + "空間成員" + "目前不支援空間" + "您需要地址才能在公開目錄中顯示。" + "地址" + "允許透過搜尋 %1$s 公開聊天室目錄找到此聊天室" + "允許其他人透過公開目錄找到。" + "在公開目錄中可見" + "任何人" + "誰可以讀取歷史紀錄" + "僅在成員被邀請後" + "選取此選項後僅限成員" + "聊天室地址是尋找與存取聊天室的方法。也確保您可以輕鬆與其他人分享聊天室。 +您可以選擇在家伺服器公開聊天室目錄中發佈您的聊天室。" + "聊天室發佈" + "能見度" + "安全與隱私" + diff --git a/features/securityandprivacy/impl/src/main/res/values-zh/translations.xml b/features/securityandprivacy/impl/src/main/res/values-zh/translations.xml new file mode 100644 index 0000000000..ef9051ab97 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values-zh/translations.xml @@ -0,0 +1,36 @@ + + + "你需要房间地址才能使其在目录中可见。" + "房间地址" + "添加房间地址" + "任何人都可以请求加入房间,但必须由管理员或版主接受请求。" + "请求加入" + "是的,启用加密" + "一旦启用,就不能再禁用房间的加密功能。消息历史记录只能在房间成员被邀请或加入房间后才可见。 +除房间成员外,任何人都无法阅读信息。这可能会妨碍机器人和网桥正常工作。 +我们不建议对任何人都能找到并加入的房间启用加密。" + "启用加密?" + "加密一旦启用,就无法禁用。" + "加密" + "启用端到端加密" + "任何人都可以找到并加入" + "任何人" + "只有受邀者才能加入" + "仅限邀请" + "房间访问权限" + "空间成员" + "目前不支持空间" + "你需要房间地址才能使其在目录中可见。" + "房间地址" + "允许通过搜索 %1$s 的公共房间目录来发现此房间" + "在公共房间目录中可见" + "任何人" + "谁可以读取历史记录" + "仅限被邀请的成员" + "仅自选择此选项以来的成员" + "房间地址是查找和访问房间的方式。这也确保你可以轻松地向他人分享房间。 +你可以选择在你服务器的公共房间目录中发布你的房间。" + "房间发布" + "房间可见性" + "安全与隐私" + diff --git a/features/securityandprivacy/impl/src/main/res/values/localazy.xml b/features/securityandprivacy/impl/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..acf4b04e71 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/res/values/localazy.xml @@ -0,0 +1,45 @@ + + + "You’ll need an address in order to make it visible in the public directory." + "Edit address" + "Add address" + "Anyone in authorised spaces can join, but everyone else must request access." + "Everyone must request access." + "Ask to join" + "Anyone in %1$s can join, but everyone else must request access." + "Yes, enable encryption" + "Once enabled, encryption for a room cannot be disabled, Message history will only be visible for room members since they were invited or since they joined the room. +No one besides the room members will be able to read messages. This may prevent bots and bridges to work correctly. +We do not recommend enabling encryption for rooms that anyone can find and join." + "Enable encryption?" + "Once enabled, encryption cannot be disabled." + "Encryption" + "Enable end-to-end encryption" + "Anyone can join." + "Anyone" + "Choose which spaces’ members can join this room without an invitation. %1$s" + "Manage spaces" + "Only invited people can join." + "Invite only" + "Access" + "Anyone in authorised spaces can join." + "Anyone in %1$s can join." + "Space members" + "Spaces are not currently supported" + "You’ll need an address in order to make it visible in the public directory." + "Address" + "Allow for this room to be found by searching %1$s public room directory" + "Allow to be found by searching the public directory." + "Visible in public directory" + "Anyone (history is public)" + "Changes won\'t affect past messages, only new ones. %1$s" + "Who can read history" + "Members since invited" + "Members (full history)" + "Room addresses are ways to find and access rooms. This also ensures you can easily share your room with others. +You can choose to publish your room in your homeserver public room directory." + "Room publishing" + "Addresses are a way to find and access rooms and spaces. This also ensures you can easily share them with others." + "Visibility" + "Security & privacy" + diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/FakeSecurityAndPrivacyNavigator.kt b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/FakeSecurityAndPrivacyNavigator.kt similarity index 72% rename from features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/FakeSecurityAndPrivacyNavigator.kt rename to features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/FakeSecurityAndPrivacyNavigator.kt index 0d68afae1c..f90040d3fb 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/FakeSecurityAndPrivacyNavigator.kt +++ b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/FakeSecurityAndPrivacyNavigator.kt @@ -1,18 +1,24 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.securityandprivacy +package io.element.android.features.securityandprivacy.impl import io.element.android.tests.testutils.lambda.lambdaError class FakeSecurityAndPrivacyNavigator( + private val onDoneLambda: () -> Unit = { lambdaError() }, private val openEditRoomAddressLambda: () -> Unit = { lambdaError() }, private val closeEditRoomAddressLambda: () -> Unit = { lambdaError() }, ) : SecurityAndPrivacyNavigator { + override fun onDone() { + onDoneLambda() + } + override fun openEditRoomAddress() { openEditRoomAddressLambda() } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenterTest.kt b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyPresenterTest.kt similarity index 66% rename from features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenterTest.kt rename to features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyPresenterTest.kt index bc2a3f62f7..c035b5510d 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenterTest.kt +++ b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyPresenterTest.kt @@ -1,24 +1,36 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.securityandprivacy +package io.element.android.features.securityandprivacy.impl import com.google.common.truth.Truth.assertThat +import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyEvent +import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyHistoryVisibility +import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyPresenter +import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyRoomAccess import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService +import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility import io.element.android.libraries.matrix.api.room.join.JoinRule +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility import io.element.android.libraries.matrix.test.A_ROOM_ALIAS import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.aRoomInfo +import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.tests.testutils.lambda.assert +import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.test import kotlinx.coroutines.test.runTest @@ -38,6 +50,7 @@ class SecurityAndPrivacyPresenterTest { assertThat(showRoomVisibilitySections).isFalse() assertThat(showHistoryVisibilitySection).isFalse() assertThat(showEncryptionSection).isFalse() + assertThat(isKnockEnabled).isFalse() } with(awaitItem()) { assertThat(editedSettings).isEqualTo(savedSettings) @@ -48,6 +61,7 @@ class SecurityAndPrivacyPresenterTest { assertThat(showRoomVisibilitySections).isFalse() assertThat(showHistoryVisibilitySection).isTrue() assertThat(showEncryptionSection).isTrue() + assertThat(isKnockEnabled).isFalse() } } } @@ -56,21 +70,21 @@ class SecurityAndPrivacyPresenterTest { fun `present - room info change updates saved and edited settings`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canSendStateResult = { _, _ -> Result.success(true) }, - initialRoomInfo = aRoomInfo( - joinRule = JoinRule.Public, - historyVisibility = RoomHistoryVisibility.WorldReadable, - canonicalAlias = A_ROOM_ALIAS, + roomPermissions = roomPermissions(), + initialRoomInfo = aRoomInfo( + joinRule = JoinRule.Public, + historyVisibility = RoomHistoryVisibility.WorldReadable, + canonicalAlias = A_ROOM_ALIAS, + ) ) ) - ) val presenter = createSecurityAndPrivacyPresenter(room = room) presenter.test { skipItems(1) with(awaitItem()) { assertThat(editedSettings).isEqualTo(savedSettings) assertThat(editedSettings.roomAccess).isEqualTo(SecurityAndPrivacyRoomAccess.Anyone) - assertThat(editedSettings.historyVisibility).isEqualTo(SecurityAndPrivacyHistoryVisibility.Anyone) + assertThat(editedSettings.historyVisibility).isEqualTo(SecurityAndPrivacyHistoryVisibility.WorldReadable) assertThat(editedSettings.address).isEqualTo(A_ROOM_ALIAS.value) assertThat(canBeSaved).isFalse() } @@ -86,13 +100,13 @@ class SecurityAndPrivacyPresenterTest { with(awaitItem()) { assertThat(editedSettings.roomAccess).isEqualTo(SecurityAndPrivacyRoomAccess.InviteOnly) assertThat(showRoomVisibilitySections).isFalse() - eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.Anyone)) + eventSink(SecurityAndPrivacyEvent.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.Anyone)) } with(awaitItem()) { assertThat(editedSettings.roomAccess).isEqualTo(SecurityAndPrivacyRoomAccess.Anyone) assertThat(showRoomVisibilitySections).isTrue() assertThat(canBeSaved).isTrue() - eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.InviteOnly)) + eventSink(SecurityAndPrivacyEvent.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.InviteOnly)) } with(awaitItem()) { assertThat(editedSettings.roomAccess).isEqualTo(SecurityAndPrivacyRoomAccess.InviteOnly) @@ -108,16 +122,16 @@ class SecurityAndPrivacyPresenterTest { presenter.test { skipItems(1) with(awaitItem()) { - assertThat(editedSettings.historyVisibility).isEqualTo(SecurityAndPrivacyHistoryVisibility.SinceSelection) - eventSink(SecurityAndPrivacyEvents.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.SinceInvite)) + assertThat(editedSettings.historyVisibility).isEqualTo(SecurityAndPrivacyHistoryVisibility.Shared) + eventSink(SecurityAndPrivacyEvent.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.Invited)) } with(awaitItem()) { - assertThat(editedSettings.historyVisibility).isEqualTo(SecurityAndPrivacyHistoryVisibility.SinceInvite) + assertThat(editedSettings.historyVisibility).isEqualTo(SecurityAndPrivacyHistoryVisibility.Invited) assertThat(canBeSaved).isTrue() - eventSink(SecurityAndPrivacyEvents.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.SinceSelection)) + eventSink(SecurityAndPrivacyEvent.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.Shared)) } with(awaitItem()) { - assertThat(editedSettings.historyVisibility).isEqualTo(SecurityAndPrivacyHistoryVisibility.SinceSelection) + assertThat(editedSettings.historyVisibility).isEqualTo(SecurityAndPrivacyHistoryVisibility.Shared) assertThat(canBeSaved).isFalse() } } @@ -130,26 +144,26 @@ class SecurityAndPrivacyPresenterTest { skipItems(1) with(awaitItem()) { assertThat(editedSettings.isEncrypted).isFalse() - eventSink(SecurityAndPrivacyEvents.ToggleEncryptionState) + eventSink(SecurityAndPrivacyEvent.ToggleEncryptionState) } with(awaitItem()) { assertThat(showEnableEncryptionConfirmation).isTrue() - eventSink(SecurityAndPrivacyEvents.CancelEnableEncryption) + eventSink(SecurityAndPrivacyEvent.CancelEnableEncryption) } with(awaitItem()) { assertThat(showEnableEncryptionConfirmation).isFalse() - eventSink(SecurityAndPrivacyEvents.ToggleEncryptionState) + eventSink(SecurityAndPrivacyEvent.ToggleEncryptionState) } with(awaitItem()) { assertThat(showEnableEncryptionConfirmation).isTrue() - eventSink(SecurityAndPrivacyEvents.ConfirmEnableEncryption) + eventSink(SecurityAndPrivacyEvent.ConfirmEnableEncryption) } skipItems(1) with(awaitItem()) { assertThat(editedSettings.isEncrypted).isTrue() assertThat(showEnableEncryptionConfirmation).isFalse() assertThat(canBeSaved).isTrue() - eventSink(SecurityAndPrivacyEvents.ToggleEncryptionState) + eventSink(SecurityAndPrivacyEvent.ToggleEncryptionState) } skipItems(1) with(awaitItem()) { @@ -163,10 +177,10 @@ class SecurityAndPrivacyPresenterTest { fun `present - room visibility loading and change`() = runTest { val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canSendStateResult = { _, _ -> Result.success(true) }, - getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, - initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared) - ) + roomPermissions = roomPermissions(), + getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, + initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared) + ) ) val presenter = createSecurityAndPrivacyPresenter(room = room) presenter.test { @@ -176,12 +190,12 @@ class SecurityAndPrivacyPresenterTest { } with(awaitItem()) { assertThat(editedSettings.isVisibleInRoomDirectory).isEqualTo(AsyncData.Success(false)) - eventSink(SecurityAndPrivacyEvents.ToggleRoomVisibility) + eventSink(SecurityAndPrivacyEvent.ToggleRoomVisibility) } with(awaitItem()) { assertThat(editedSettings.isVisibleInRoomDirectory).isEqualTo(AsyncData.Success(true)) assertThat(canBeSaved).isTrue() - eventSink(SecurityAndPrivacyEvents.ToggleRoomVisibility) + eventSink(SecurityAndPrivacyEvent.ToggleRoomVisibility) } with(awaitItem()) { assertThat(editedSettings.isVisibleInRoomDirectory).isEqualTo(AsyncData.Success(false)) @@ -193,12 +207,12 @@ class SecurityAndPrivacyPresenterTest { @Test fun `present - edit room address`() = runTest { val openEditRoomAddressLambda = lambdaRecorder { } - val navigator = FakeSecurityAndPrivacyNavigator(openEditRoomAddressLambda) + val navigator = FakeSecurityAndPrivacyNavigator(openEditRoomAddressLambda = openEditRoomAddressLambda) val presenter = createSecurityAndPrivacyPresenter(navigator = navigator) presenter.test { skipItems(1) with(awaitItem()) { - eventSink(SecurityAndPrivacyEvents.EditRoomAddress) + eventSink(SecurityAndPrivacyEvent.EditRoomAddress) } assert(openEditRoomAddressLambda).isCalledOnce() } @@ -212,37 +226,44 @@ class SecurityAndPrivacyPresenterTest { val updateRoomHistoryVisibilityLambda = lambdaRecorder> { Result.success(Unit) } val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canSendStateResult = { _, _ -> Result.success(true) }, - getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, - initialRoomInfo = aRoomInfo(joinRule = JoinRule.Invite, historyVisibility = RoomHistoryVisibility.Shared) - ), + roomPermissions = roomPermissions(), + getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, + initialRoomInfo = aRoomInfo(joinRule = JoinRule.Invite, historyVisibility = RoomHistoryVisibility.Shared) + ), enableEncryptionResult = enableEncryptionLambda, updateJoinRuleResult = updateJoinRuleLambda, updateRoomVisibilityResult = updateRoomVisibilityLambda, updateRoomHistoryVisibilityResult = updateRoomHistoryVisibilityLambda, ) - val presenter = createSecurityAndPrivacyPresenter(room = room) + val onDoneLambda = lambdaRecorder { } + val navigator = FakeSecurityAndPrivacyNavigator( + onDoneLambda = onDoneLambda, + ) + val presenter = createSecurityAndPrivacyPresenter( + room = room, + navigator = navigator, + ) presenter.test { - skipItems(2) + skipItems(1) with(awaitItem()) { assertThat(editedSettings.roomAccess).isEqualTo(SecurityAndPrivacyRoomAccess.InviteOnly) - eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.Anyone)) + eventSink(SecurityAndPrivacyEvent.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.Anyone)) } with(awaitItem()) { - eventSink(SecurityAndPrivacyEvents.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.Anyone)) + eventSink(SecurityAndPrivacyEvent.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.WorldReadable)) } with(awaitItem()) { - assertThat(editedSettings.historyVisibility).isEqualTo(SecurityAndPrivacyHistoryVisibility.Anyone) - eventSink(SecurityAndPrivacyEvents.ConfirmEnableEncryption) + assertThat(editedSettings.historyVisibility).isEqualTo(SecurityAndPrivacyHistoryVisibility.WorldReadable) + eventSink(SecurityAndPrivacyEvent.ConfirmEnableEncryption) } skipItems(1) with(awaitItem()) { assertThat(editedSettings.isEncrypted).isTrue() - eventSink(SecurityAndPrivacyEvents.ToggleRoomVisibility) + eventSink(SecurityAndPrivacyEvent.ToggleRoomVisibility) } with(awaitItem()) { assertThat(editedSettings.isVisibleInRoomDirectory).isEqualTo(AsyncData.Success(true)) - eventSink(SecurityAndPrivacyEvents.Save) + eventSink(SecurityAndPrivacyEvent.Save) } with(awaitItem()) { assertThat(saveAction).isEqualTo(AsyncAction.Loading) @@ -255,8 +276,8 @@ class SecurityAndPrivacyPresenterTest { isEncrypted = true, ) ) - // Saved settings are updated 3 times to match the edited settings - skipItems(3) + // Saved settings are updated 2 times to match the edited settings + skipItems(2) with(awaitItem()) { assertThat(saveAction).isEqualTo(AsyncAction.Success(Unit)) assertThat(savedSettings).isEqualTo(editedSettings) @@ -266,6 +287,7 @@ class SecurityAndPrivacyPresenterTest { assert(updateJoinRuleLambda).isCalledOnce() assert(updateRoomVisibilityLambda).isCalledOnce() assert(updateRoomHistoryVisibilityLambda).isCalledOnce() + onDoneLambda.assertions().isCalledOnce() } } @@ -279,10 +301,10 @@ class SecurityAndPrivacyPresenterTest { val updateRoomHistoryVisibilityLambda = lambdaRecorder> { Result.success(Unit) } val room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canSendStateResult = { _, _ -> Result.success(true) }, - getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, - initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, joinRule = JoinRule.Private) - ), + roomPermissions = roomPermissions(), + getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, + initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, joinRule = JoinRule.Private) + ), enableEncryptionResult = enableEncryptionLambda, updateJoinRuleResult = updateJoinRuleLambda, updateRoomVisibilityResult = updateRoomVisibilityLambda, @@ -290,26 +312,26 @@ class SecurityAndPrivacyPresenterTest { ) val presenter = createSecurityAndPrivacyPresenter(room = room) presenter.test { - skipItems(2) + skipItems(1) with(awaitItem()) { assertThat(editedSettings.roomAccess).isEqualTo(SecurityAndPrivacyRoomAccess.InviteOnly) - eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.Anyone)) + eventSink(SecurityAndPrivacyEvent.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.Anyone)) } with(awaitItem()) { - eventSink(SecurityAndPrivacyEvents.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.Anyone)) + eventSink(SecurityAndPrivacyEvent.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.WorldReadable)) } with(awaitItem()) { - assertThat(editedSettings.historyVisibility).isEqualTo(SecurityAndPrivacyHistoryVisibility.Anyone) - eventSink(SecurityAndPrivacyEvents.ConfirmEnableEncryption) + assertThat(editedSettings.historyVisibility).isEqualTo(SecurityAndPrivacyHistoryVisibility.WorldReadable) + eventSink(SecurityAndPrivacyEvent.ConfirmEnableEncryption) } skipItems(1) with(awaitItem()) { assertThat(editedSettings.isEncrypted).isTrue() - eventSink(SecurityAndPrivacyEvents.ToggleRoomVisibility) + eventSink(SecurityAndPrivacyEvent.ToggleRoomVisibility) } with(awaitItem()) { assertThat(editedSettings.isVisibleInRoomDirectory).isEqualTo(AsyncData.Success(true)) - eventSink(SecurityAndPrivacyEvents.Save) + eventSink(SecurityAndPrivacyEvent.Save) } with(awaitItem()) { assertThat(saveAction).isEqualTo(AsyncAction.Loading) @@ -322,8 +344,9 @@ class SecurityAndPrivacyPresenterTest { ) ) // Saved settings are updated 2 times to match the edited settings - skipItems(3) - with(awaitItem()) { + skipItems(2) + val state = awaitItem() + with(state) { assertThat(saveAction).isInstanceOf(AsyncAction.Failure::class.java) assertThat(savedSettings.isVisibleInRoomDirectory).isNotEqualTo(editedSettings.isVisibleInRoomDirectory) assertThat(canBeSaved).isTrue() @@ -332,26 +355,67 @@ class SecurityAndPrivacyPresenterTest { assert(updateJoinRuleLambda).isCalledOnce() assert(updateRoomVisibilityLambda).isCalledOnce() assert(updateRoomHistoryVisibilityLambda).isCalledOnce() + // Clear error + state.eventSink(SecurityAndPrivacyEvent.DismissSaveError) + with(awaitItem()) { + assertThat(saveAction).isEqualTo(AsyncAction.Uninitialized) + } } } + @Test + fun `present - isKnockEnabled is true if the Knock feature flag is enabled`() = runTest { + val presenter = createSecurityAndPrivacyPresenter( + featureFlagService = FakeFeatureFlagService( + initialState = mapOf( + FeatureFlags.Knock.key to true, + ) + ) + ) + presenter.test { + assertThat(awaitItem().isKnockEnabled).isFalse() + assertThat(awaitItem().isKnockEnabled).isTrue() + } + } + + private fun roomPermissions( + canChangeRoomAccess: Boolean = true, + canChangeHistoryVisibility: Boolean = true, + canChangeEncryption: Boolean = true, + canChangeRoomVisibility: Boolean = true, + ): RoomPermissions { + return FakeRoomPermissions( + canSendState = { eventType -> + when (eventType) { + StateEventType.RoomJoinRules -> canChangeRoomAccess + StateEventType.RoomHistoryVisibility -> canChangeHistoryVisibility + StateEventType.RoomEncryption -> canChangeEncryption + StateEventType.RoomCanonicalAlias -> canChangeRoomVisibility + else -> lambdaError() + } + } + ) + } + private fun createSecurityAndPrivacyPresenter( serverName: String = "matrix.org", room: FakeJoinedRoom = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canSendStateResult = { _, _ -> Result.success(true) }, + roomPermissions = roomPermissions(), getRoomVisibilityResult = { Result.success(RoomVisibility.Private) }, initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, joinRule = JoinRule.Private) ), ), navigator: SecurityAndPrivacyNavigator = FakeSecurityAndPrivacyNavigator(), + featureFlagService: FeatureFlagService = FakeFeatureFlagService(), ): SecurityAndPrivacyPresenter { return SecurityAndPrivacyPresenter( room = room, matrixClient = FakeMatrixClient( userIdServerNameLambda = { serverName }, ), - navigator = navigator + navigator = navigator, + featureFlagService = featureFlagService, ) } } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyViewTest.kt b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyViewTest.kt similarity index 56% rename from features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyViewTest.kt rename to features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyViewTest.kt index 9bbd770334..b15bc2fe37 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyViewTest.kt +++ b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyViewTest.kt @@ -1,11 +1,12 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.securityandprivacy +package io.element.android.features.securityandprivacy.impl import androidx.activity.ComponentActivity import androidx.compose.ui.test.junit4.AndroidComposeTestRule @@ -13,13 +14,19 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.element.android.features.roomdetails.impl.R +import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyEvent +import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyHistoryVisibility +import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyRoomAccess +import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyState +import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyView +import io.element.android.features.securityandprivacy.impl.root.aSecurityAndPrivacySettings +import io.element.android.features.securityandprivacy.impl.root.aSecurityAndPrivacyState +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.ui.strings.CommonStrings -import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn -import io.element.android.tests.testutils.ensureCalledOnce import io.element.android.tests.testutils.pressBack import org.junit.Rule import org.junit.Test @@ -32,29 +39,54 @@ class SecurityAndPrivacyViewTest { @get:Rule val rule = createAndroidComposeRule() @Test - fun `click on back invokes expected callback`() { - ensureCalledOnce { callback -> - rule.setSecurityAndPrivacyView( - onBackClick = callback, - ) - rule.pressBack() - } + fun `click on back invokes emits the expected event`() { + val recorder = EventsRecorder() + val state = aSecurityAndPrivacyState( + eventSink = recorder, + ) + rule.setSecurityAndPrivacyView(state) + rule.pressBack() + recorder.assertSingle(SecurityAndPrivacyEvent.Exit) + } + + @Test + fun `discard cancellation emits the expected event`() { + val recorder = EventsRecorder() + val state = aSecurityAndPrivacyState( + saveAction = AsyncAction.ConfirmingCancellation, + eventSink = recorder, + ) + rule.setSecurityAndPrivacyView(state) + rule.clickOn(CommonStrings.action_discard) + recorder.assertSingle(SecurityAndPrivacyEvent.Exit) + } + + @Test + fun `save cancellation confirmation emits the expected event`() { + val recorder = EventsRecorder() + val state = aSecurityAndPrivacyState( + saveAction = AsyncAction.ConfirmingCancellation, + eventSink = recorder, + ) + rule.setSecurityAndPrivacyView(state) + rule.clickOn(CommonStrings.action_save, inDialog = true) + recorder.assertSingle(SecurityAndPrivacyEvent.Save) } @Test fun `click on room access item emits the expected event`() { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, ) rule.setSecurityAndPrivacyView(state) rule.clickOn(R.string.screen_security_and_privacy_room_access_invite_only_option_title) - recorder.assertSingle(SecurityAndPrivacyEvents.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.InviteOnly)) + recorder.assertSingle(SecurityAndPrivacyEvent.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.InviteOnly)) } @Test fun `click on disabled save doesn't emit event`() { - val recorder = EventsRecorder(expectEvents = false) + val recorder = EventsRecorder(expectEvents = false) val state = aSecurityAndPrivacyState(eventSink = recorder) rule.setSecurityAndPrivacyView(state) rule.clickOn(CommonStrings.action_save) @@ -63,7 +95,7 @@ class SecurityAndPrivacyViewTest { @Test fun `click on enabled save emits the expected event`() { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, editedSettings = aSecurityAndPrivacySettings( @@ -72,14 +104,14 @@ class SecurityAndPrivacyViewTest { ) rule.setSecurityAndPrivacyView(state) rule.clickOn(CommonStrings.action_save) - recorder.assertSingle(SecurityAndPrivacyEvents.Save) + recorder.assertSingle(SecurityAndPrivacyEvent.Save) } @Test @Config(qualifiers = "h640dp") fun `click on room address item emits the expected event`() { val address = "@alias:matrix.org" - val recorder = EventsRecorder() + val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, editedSettings = aSecurityAndPrivacySettings( @@ -89,13 +121,13 @@ class SecurityAndPrivacyViewTest { ) rule.setSecurityAndPrivacyView(state) rule.onNodeWithText(address).performClick() - recorder.assertSingle(SecurityAndPrivacyEvents.EditRoomAddress) + recorder.assertSingle(SecurityAndPrivacyEvent.EditRoomAddress) } @Test @Config(qualifiers = "h1024dp") fun `click on room visibility item emits the expected event`() { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, editedSettings = aSecurityAndPrivacySettings( @@ -105,47 +137,47 @@ class SecurityAndPrivacyViewTest { ) rule.setSecurityAndPrivacyView(state) rule.clickOn(R.string.screen_security_and_privacy_room_directory_visibility_toggle_title) - recorder.assertSingle(SecurityAndPrivacyEvents.ToggleRoomVisibility) + recorder.assertSingle(SecurityAndPrivacyEvent.ToggleRoomVisibility) } @Test - @Config(qualifiers = "h640dp") + @Config(qualifiers = "h1024dp") fun `click on history visibility item emits the expected event`() { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, editedSettings = aSecurityAndPrivacySettings( - historyVisibility = SecurityAndPrivacyHistoryVisibility.SinceSelection, + historyVisibility = SecurityAndPrivacyHistoryVisibility.Invited, ), ) rule.setSecurityAndPrivacyView(state) - rule.clickOn(R.string.screen_security_and_privacy_room_history_since_selecting_option_title) - recorder.assertSingle(SecurityAndPrivacyEvents.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.SinceSelection)) + rule.clickOn(R.string.screen_security_and_privacy_room_history_since_invite_option_title) + recorder.assertSingle(SecurityAndPrivacyEvent.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.Invited)) } @Test - @Config(qualifiers = "h640dp") + @Config(qualifiers = "h1024dp") fun `click on encryption item emits the expected event`() { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, savedSettings = aSecurityAndPrivacySettings(isEncrypted = false), ) rule.setSecurityAndPrivacyView(state) rule.clickOn(R.string.screen_security_and_privacy_encryption_toggle_title) - recorder.assertSingle(SecurityAndPrivacyEvents.ToggleEncryptionState) + recorder.assertSingle(SecurityAndPrivacyEvent.ToggleEncryptionState) } @Test fun `click on encryption confirm emits the expected event`() { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, showEncryptionConfirmation = true, ) rule.setSecurityAndPrivacyView(state) rule.clickOn(R.string.screen_security_and_privacy_enable_encryption_alert_confirm_button_title) - recorder.assertSingle(SecurityAndPrivacyEvents.ConfirmEnableEncryption) + recorder.assertSingle(SecurityAndPrivacyEvent.ConfirmEnableEncryption) } } @@ -153,12 +185,12 @@ private fun AndroidComposeTestRule.setSecur state: SecurityAndPrivacyState = aSecurityAndPrivacyState( eventSink = EventsRecorder(expectEvents = false), ), - onBackClick: () -> Unit = EnsureNeverCalled(), + onLinkClick: (String) -> Unit = EnsureNeverCalledWithParam(), ) { setContent { SecurityAndPrivacyView( state = state, - onBackClick = onBackClick, + onLinkClick = onLinkClick, ) } } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditBaseRoomAddressPresenterTest.kt b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressPresenterTest.kt similarity index 92% rename from features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditBaseRoomAddressPresenterTest.kt rename to features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressPresenterTest.kt index 9b9cb4120c..aac25ec018 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditBaseRoomAddressPresenterTest.kt +++ b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressPresenterTest.kt @@ -1,16 +1,16 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress +package io.element.android.features.securityandprivacy.impl.editroomaddress import com.google.common.truth.Truth.assertThat -import io.element.android.features.roomdetails.impl.aJoinedRoom -import io.element.android.features.roomdetails.impl.securityandprivacy.FakeSecurityAndPrivacyNavigator -import io.element.android.features.roomdetails.impl.securityandprivacy.SecurityAndPrivacyNavigator +import io.element.android.features.securityandprivacy.impl.FakeSecurityAndPrivacyNavigator +import io.element.android.features.securityandprivacy.impl.SecurityAndPrivacyNavigator import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.room.JoinedRoom @@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.FakeJoinedRoom +import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.alias.FakeRoomAliasHelper import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity import io.element.android.tests.testutils.lambda.assert @@ -30,11 +31,13 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import java.util.Optional -class EditBaseRoomAddressPresenterTest { +class EditRoomAddressPresenterTest { @Test fun `present - initial state no address`() = runTest { val presenter = createEditRoomAddressPresenter( - room = aJoinedRoom(displayName = "") + room = FakeJoinedRoom().apply { + givenRoomInfo(aRoomInfo(name = "")) + } ) presenter.test { with(awaitItem()) { @@ -49,9 +52,9 @@ class EditBaseRoomAddressPresenterTest { @Test fun `present - initial state address matching own homeserver`() = runTest { - val room = aJoinedRoom( - canonicalAlias = RoomAlias("#canonical:matrix.org"), - ) + val room = FakeJoinedRoom().apply { + givenRoomInfo(aRoomInfo(canonicalAlias = RoomAlias("#canonical:matrix.org"))) + } val presenter = createEditRoomAddressPresenter(room = room) presenter.test { with(awaitItem()) { @@ -66,10 +69,14 @@ class EditBaseRoomAddressPresenterTest { @Test fun `present - initial state address not matching own homeserver`() = runTest { - val room = aJoinedRoom( - displayName = "", - canonicalAlias = RoomAlias("#canonical:notmatrix.org"), - ) + val room = FakeJoinedRoom().apply { + givenRoomInfo( + aRoomInfo( + name = "", + canonicalAlias = RoomAlias("#canonical:notmatrix.org") + ) + ) + } val presenter = createEditRoomAddressPresenter(room = room) presenter.test { with(awaitItem()) { @@ -195,12 +202,13 @@ class EditBaseRoomAddressPresenterTest { val navigator = FakeSecurityAndPrivacyNavigator(closeEditRoomAddressLambda = closeEditAddressLambda) val canonicalAlias = RoomAlias("#canonical:matrix.org") - val room = aJoinedRoom( - canonicalAlias = canonicalAlias, + val room = FakeJoinedRoom( updateCanonicalAliasResult = updateCanonicalAliasResult, publishRoomAliasInRoomDirectoryResult = publishAliasInRoomDirectoryResult, removeRoomAliasFromRoomDirectoryResult = removeAliasFromRoomDirectoryResult - ) + ).apply { + givenRoomInfo(aRoomInfo(canonicalAlias = canonicalAlias)) + } val presenter = createEditRoomAddressPresenter(room = room, navigator = navigator) presenter.test { with(awaitItem()) { @@ -245,12 +253,13 @@ class EditBaseRoomAddressPresenterTest { val navigator = FakeSecurityAndPrivacyNavigator(closeEditRoomAddressLambda = closeEditAddressLambda) val canonicalAlias = RoomAlias("#canonical:notmatrix.org") - val room = aJoinedRoom( - canonicalAlias = canonicalAlias, + val room = FakeJoinedRoom( updateCanonicalAliasResult = updateCanonicalAliasResult, publishRoomAliasInRoomDirectoryResult = publishAliasInRoomDirectoryResult, removeRoomAliasFromRoomDirectoryResult = removeAliasFromRoomDirectoryResult - ) + ).apply { + givenRoomInfo(aRoomInfo(canonicalAlias = canonicalAlias)) + } val presenter = createEditRoomAddressPresenter(room = room, navigator = navigator) presenter.test { with(awaitItem()) { diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditBaseRoomAddressViewTest.kt b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressViewTest.kt similarity index 96% rename from features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditBaseRoomAddressViewTest.kt rename to features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressViewTest.kt index 1d2c716710..17d6f3a88d 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditBaseRoomAddressViewTest.kt +++ b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/editroomaddress/EditRoomAddressViewTest.kt @@ -1,11 +1,12 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress +package io.element.android.features.securityandprivacy.impl.editroomaddress import androidx.activity.ComponentActivity import androidx.compose.ui.test.junit4.AndroidComposeTestRule @@ -28,7 +29,7 @@ import org.junit.rules.TestRule import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -class EditBaseRoomAddressViewTest { +class EditRoomAddressViewTest { @get:Rule val rule = createAndroidComposeRule() @Test diff --git a/features/securityandprivacy/test/build.gradle.kts b/features/securityandprivacy/test/build.gradle.kts new file mode 100644 index 0000000000..903ef6d194 --- /dev/null +++ b/features/securityandprivacy/test/build.gradle.kts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.securityandprivacy.test" +} + +dependencies { + implementation(projects.features.securityandprivacy.api) + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) + implementation(projects.tests.testutils) +} diff --git a/features/securityandprivacy/test/src/main/kotlin/io/element/android/features/securityandprivacy/test/FakeSecurityAndPrivacyEntryPoint.kt b/features/securityandprivacy/test/src/main/kotlin/io/element/android/features/securityandprivacy/test/FakeSecurityAndPrivacyEntryPoint.kt new file mode 100644 index 0000000000..a66fe55bff --- /dev/null +++ b/features/securityandprivacy/test/src/main/kotlin/io/element/android/features/securityandprivacy/test/FakeSecurityAndPrivacyEntryPoint.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.securityandprivacy.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeSecurityAndPrivacyEntryPoint : SecurityAndPrivacyEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: SecurityAndPrivacyEntryPoint.Callback, + ): Node { + lambdaError() + } +} diff --git a/features/share/api/build.gradle.kts b/features/share/api/build.gradle.kts index e315ab4e8a..06e2b9469a 100644 --- a/features/share/api/build.gradle.kts +++ b/features/share/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/share/api/src/main/kotlin/io/element/android/features/share/api/ShareEntryPoint.kt b/features/share/api/src/main/kotlin/io/element/android/features/share/api/ShareEntryPoint.kt index dd2889e10a..4d8eef9116 100644 --- a/features/share/api/src/main/kotlin/io/element/android/features/share/api/ShareEntryPoint.kt +++ b/features/share/api/src/main/kotlin/io/element/android/features/share/api/ShareEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,21 +13,19 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint -import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.matrix.api.core.RoomId interface ShareEntryPoint : FeatureEntryPoint { - data class Params(val intent: Intent) : NodeInputs + data class Params(val intent: Intent) - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder + fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: Params, + callback: Callback, + ): Node interface Callback : Plugin { fun onDone(roomIds: List) } - - interface NodeBuilder { - fun params(params: Params): NodeBuilder - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } } diff --git a/features/share/impl/build.gradle.kts b/features/share/impl/build.gradle.kts index 4c62a06352..73748095a9 100644 --- a/features/share/impl/build.gradle.kts +++ b/features/share/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -46,4 +47,6 @@ dependencies { testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.mediaupload.test) testImplementation(projects.libraries.preferences.test) + testImplementation(projects.libraries.roomselect.test) + testImplementation(projects.services.appnavstate.impl) } diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultShareEntryPoint.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultShareEntryPoint.kt index fe65c60b73..a8ae4d71c6 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultShareEntryPoint.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/DefaultShareEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,33 +10,25 @@ package io.element.android.features.share.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.share.api.ShareEntryPoint import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope @ContributesBinding(SessionScope::class) -@Inject class DefaultShareEntryPoint : ShareEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): ShareEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : ShareEntryPoint.NodeBuilder { - override fun params(params: ShareEntryPoint.Params): ShareEntryPoint.NodeBuilder { - plugins += ShareNode.Inputs(intent = params.intent) - return this - } - - override fun callback(callback: ShareEntryPoint.Callback): ShareEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: ShareEntryPoint.Params, + callback: ShareEntryPoint.Callback, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf( + ShareNode.Inputs(intent = params.intent), + callback, + ) + ) } } diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareEvents.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareEvents.kt index cb43e579c2..d0e246fe44 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareEvents.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareIntentHandler.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareIntentHandler.kt index b3db111820..9342ef60d4 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareIntentHandler.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareIntentHandler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,7 +18,6 @@ import android.os.Build import androidx.core.content.IntentCompat import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.androidutils.compat.queryIntentActivitiesCompat import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeAny @@ -49,7 +49,6 @@ interface ShareIntentHandler { } @ContributesBinding(AppScope::class) -@Inject class DefaultShareIntentHandler( @ApplicationContext private val context: Context, ) : ShareIntentHandler { @@ -59,8 +58,9 @@ class DefaultShareIntentHandler( onPlainText: suspend (String) -> Boolean, ): Boolean { val type = intent.resolveType(context) ?: return false + val uris = getIncomingUris(intent, type) return when { - type == MimeTypes.PlainText -> handlePlainText(intent, onPlainText) + uris.isEmpty() && type == MimeTypes.PlainText -> handlePlainText(intent, onPlainText) type.isMimeTypeImage() || type.isMimeTypeVideo() || type.isMimeTypeAudio() || @@ -68,7 +68,6 @@ class DefaultShareIntentHandler( type.isMimeTypeFile() || type.isMimeTypeText() || type.isMimeTypeAny() -> { - val uris = getIncomingUris(intent, type) val result = onUris(uris) revokeUriPermissions(uris.map { it.uri }) result diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareNode.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareNode.kt index e268419920..b91c484ef3 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareNode.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -23,6 +24,7 @@ import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.share.api.ShareEntryPoint import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.RoomId @@ -52,7 +54,7 @@ class ShareNode( private val inputs = inputs() private val presenter = presenterFactory.create(inputs.intent) - private val callbacks = plugins.filterIsInstance() + private val callback: ShareEntryPoint.Callback = callback() override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { val callback = object : RoomSelectEntryPoint.Callback { @@ -61,14 +63,16 @@ class ShareNode( } override fun onCancel() { - navigateUp() + callback.onDone(emptyList()) } } - return roomSelectEntryPoint.nodeBuilder(this, buildContext) - .callback(callback) - .params(RoomSelectEntryPoint.Params(mode = RoomSelectMode.Share)) - .build() + return roomSelectEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = RoomSelectEntryPoint.Params(mode = RoomSelectMode.Share), + callback = callback, + ) } @Composable @@ -82,12 +86,8 @@ class ShareNode( val state = presenter.present() ShareView( state = state, - onShareSuccess = ::onShareSuccess, + onShareSuccess = callback::onDone, ) } } - - private fun onShareSuccess(roomIds: List) { - callbacks.forEach { it.onDone(roomIds) } - } } diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt index d3222edf5e..4a4086ed87 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -22,10 +23,8 @@ import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.JoinedRoom -import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider -import io.element.android.libraries.mediaupload.api.MediaPreProcessor -import io.element.android.libraries.mediaupload.api.MediaSender +import io.element.android.libraries.mediaupload.api.MediaSenderRoomFactory import io.element.android.services.appnavstate.api.ActiveRoomsHolder import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -38,7 +37,7 @@ class SharePresenter( private val sessionCoroutineScope: CoroutineScope, private val shareIntentHandler: ShareIntentHandler, private val matrixClient: MatrixClient, - private val mediaPreProcessor: MediaPreProcessor, + private val mediaSenderRoomFactory: MediaSenderRoomFactory, private val activeRoomsHolder: ActiveRoomsHolder, private val mediaOptimizationConfigProvider: MediaOptimizationConfigProvider, ) : Presenter { @@ -55,7 +54,7 @@ class SharePresenter( @Composable override fun present(): ShareState { - fun handleEvents(event: ShareEvents) { + fun handleEvent(event: ShareEvents) { when (event) { ShareEvents.ClearError -> shareActionState.value = AsyncAction.Uninitialized } @@ -63,7 +62,7 @@ class SharePresenter( return ShareState( shareAction = shareActionState.value, - eventSink = { handleEvents(it) } + eventSink = ::handleEvent, ) } @@ -87,12 +86,7 @@ class SharePresenter( roomIds .map { roomId -> val room = getJoinedRoom(roomId) ?: return@map false - val mediaSender = MediaSender( - preProcessor = mediaPreProcessor, - room = room, - timelineMode = Timeline.Mode.Live, - mediaOptimizationConfigProvider = mediaOptimizationConfigProvider, - ) + val mediaSender = mediaSenderRoomFactory.create(room = room) filesToShare .map { fileToShare -> val result = mediaSender.sendMedia( diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareState.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareState.kt index 948801d341..fd98d58ba3 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareState.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareStateProvider.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareStateProvider.kt index 5afc622fb9..88f9d42f40 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareStateProvider.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareView.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareView.kt index 936add8041..ec5897bcad 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareView.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/DefaultShareEntryPointTest.kt b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/DefaultShareEntryPointTest.kt index 66ee853d26..83a32929ff 100644 --- a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/DefaultShareEntryPointTest.kt +++ b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/DefaultShareEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,12 +11,11 @@ package io.element.android.features.share.impl import android.content.Intent import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node import com.bumble.appyx.testing.junit4.util.MainDispatcherRule import com.google.common.truth.Truth.assertThat import io.element.android.features.share.api.ShareEntryPoint import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.roomselect.api.RoomSelectEntryPoint +import io.element.android.libraries.roomselect.test.FakeRoomSelectEntryPoint import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.node.TestParentNode import kotlinx.coroutines.test.runTest @@ -37,11 +37,7 @@ class DefaultShareEntryPointTest { buildContext = buildContext, plugins = plugins, presenterFactory = { createSharePresenter() }, - roomSelectEntryPoint = object : RoomSelectEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): RoomSelectEntryPoint.NodeBuilder { - lambdaError() - } - }, + roomSelectEntryPoint = FakeRoomSelectEntryPoint(), ) } val callback = object : ShareEntryPoint.Callback { @@ -50,10 +46,12 @@ class DefaultShareEntryPointTest { val params = ShareEntryPoint.Params( intent = Intent(), ) - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .params(params) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + params = params, + callback = callback, + ) assertThat(result).isInstanceOf(ShareNode::class.java) assertThat(result.plugins).contains(ShareNode.Inputs(params.intent)) assertThat(result.plugins).contains(callback) diff --git a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/FakeShareIntentHandler.kt b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/FakeShareIntentHandler.kt index 138fb57747..dbb9d1c704 100644 --- a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/FakeShareIntentHandler.kt +++ b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/FakeShareIntentHandler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt index b1b39b2e80..0df1ed7bab 100644 --- a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt +++ b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,18 +17,17 @@ import com.google.common.truth.Truth.assertThat import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.matrix.api.MatrixClient -import io.element.android.libraries.matrix.api.core.EventId -import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.test.A_MESSAGE import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.FakeMatrixClient -import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.timeline.FakeTimeline -import io.element.android.libraries.mediaupload.api.MediaPreProcessor +import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider +import io.element.android.libraries.mediaupload.api.MediaSenderRoomFactory import io.element.android.libraries.mediaupload.test.FakeMediaOptimizationConfigProvider -import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor +import io.element.android.libraries.mediaupload.test.FakeMediaSender import io.element.android.services.appnavstate.api.ActiveRoomsHolder +import io.element.android.services.appnavstate.impl.DefaultActiveRoomsHolder import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.lambda.lambdaRecorder import kotlinx.coroutines.test.TestScope @@ -36,7 +36,6 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner -import java.io.File @RunWith(RobolectricTestRunner::class) class SharePresenterTest { @@ -120,18 +119,16 @@ class SharePresenterTest { @Test fun `present - send media ok`() = runTest { - val sendFileResult = - lambdaRecorder> { _, _, _, _, _ -> - Result.success(FakeMediaUploadHandler()) - } + val sendMediaResult = lambdaRecorder> { Result.success(Unit) } val joinedRoom = FakeJoinedRoom( - liveTimeline = FakeTimeline().apply { - sendFileLambda = sendFileResult - }, + liveTimeline = FakeTimeline(), ) val matrixClient = FakeMatrixClient().apply { givenGetRoomResult(A_ROOM_ID, joinedRoom) } + val mediaSender = FakeMediaSender( + sendMediaResult = sendMediaResult, + ) val presenter = createSharePresenter( matrixClient = matrixClient, shareIntentHandler = FakeShareIntentHandler { _, onFile, _ -> @@ -143,7 +140,8 @@ class SharePresenterTest { ) ) ) - } + }, + mediaSenderRoomFactory = MediaSenderRoomFactory { mediaSender }, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -155,7 +153,7 @@ class SharePresenterTest { val success = awaitItem() assertThat(success.shareAction.isSuccess()).isTrue() assertThat(success.shareAction).isEqualTo(AsyncAction.Success(listOf(A_ROOM_ID))) - sendFileResult.assertions().isCalledOnce() + sendMediaResult.assertions().isCalledOnce() } } } @@ -164,17 +162,17 @@ internal fun TestScope.createSharePresenter( intent: Intent = Intent(), shareIntentHandler: ShareIntentHandler = FakeShareIntentHandler(), matrixClient: MatrixClient = FakeMatrixClient(), - mediaPreProcessor: MediaPreProcessor = FakeMediaPreProcessor(), - activeRoomsHolder: ActiveRoomsHolder = ActiveRoomsHolder(), - mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(), + activeRoomsHolder: ActiveRoomsHolder = DefaultActiveRoomsHolder(), + mediaSenderRoomFactory: MediaSenderRoomFactory = MediaSenderRoomFactory { FakeMediaSender() }, + mediaOptimizationConfigProvider: MediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(), ): SharePresenter { return SharePresenter( intent = intent, sessionCoroutineScope = this, shareIntentHandler = shareIntentHandler, matrixClient = matrixClient, - mediaPreProcessor = mediaPreProcessor, activeRoomsHolder = activeRoomsHolder, + mediaSenderRoomFactory = mediaSenderRoomFactory, mediaOptimizationConfigProvider = mediaOptimizationConfigProvider, ) } diff --git a/features/signedout/api/build.gradle.kts b/features/signedout/api/build.gradle.kts index 0cc1bcf092..55031045f9 100644 --- a/features/signedout/api/build.gradle.kts +++ b/features/signedout/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/signedout/api/src/main/kotlin/io/element/android/features/signedout/api/SignedOutEntryPoint.kt b/features/signedout/api/src/main/kotlin/io/element/android/features/signedout/api/SignedOutEntryPoint.kt index 9651327dc5..b5e490d4ee 100644 --- a/features/signedout/api/src/main/kotlin/io/element/android/features/signedout/api/SignedOutEntryPoint.kt +++ b/features/signedout/api/src/main/kotlin/io/element/android/features/signedout/api/SignedOutEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,10 +18,9 @@ interface SignedOutEntryPoint : FeatureEntryPoint { val sessionId: SessionId, ) - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - - interface NodeBuilder { - fun params(params: Params): NodeBuilder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: Params, + ): Node } diff --git a/features/signedout/impl/build.gradle.kts b/features/signedout/impl/build.gradle.kts index f314cb8283..3c8aac5e25 100644 --- a/features/signedout/impl/build.gradle.kts +++ b/features/signedout/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/DefaultSignedOutEntryPoint.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/DefaultSignedOutEntryPoint.kt index 91def6db8d..1a73c066f5 100644 --- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/DefaultSignedOutEntryPoint.kt +++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/DefaultSignedOutEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,28 +10,21 @@ package io.element.android.features.signedout.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.signedout.api.SignedOutEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultSignedOutEntryPoint : SignedOutEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): SignedOutEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : SignedOutEntryPoint.NodeBuilder { - override fun params(params: SignedOutEntryPoint.Params): SignedOutEntryPoint.NodeBuilder { - plugins += SignedOutNode.Inputs(params.sessionId) - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: SignedOutEntryPoint.Params, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf(SignedOutNode.Inputs(params.sessionId)) + ) } } diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutEvents.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutEvents.kt index c44b8ec36e..3b78af9918 100644 --- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutEvents.kt +++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutNode.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutNode.kt index 1bacc78932..deebde305c 100644 --- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutNode.kt +++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt index 7c9d1e6d83..1aa13d9b8d 100644 --- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt +++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -42,7 +43,7 @@ class SignedOutPresenter( }.collectAsState(initial = null) val coroutineScope = rememberCoroutineScope() - fun handleEvents(event: SignedOutEvents) { + fun handleEvent(event: SignedOutEvents) { when (event) { SignedOutEvents.SignInAgain -> coroutineScope.launch { sessionStore.removeSession(sessionId.value) @@ -53,7 +54,7 @@ class SignedOutPresenter( return SignedOutState( appName = buildMeta.applicationName, signedOutSession = signedOutSession, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } } diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutState.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutState.kt index 725654ca57..bc1ee18f5a 100644 --- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutState.kt +++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.features.signedout.impl import io.element.android.libraries.sessionstorage.api.SessionData -// Do not use default value, so no member get forgotten in the presenters. data class SignedOutState( val appName: String, val signedOutSession: SessionData?, diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutStateProvider.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutStateProvider.kt index a7b95a8537..396339adbb 100644 --- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutStateProvider.kt +++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -36,7 +37,6 @@ private fun aSessionData( refreshToken = "aRefreshToken", homeserverUrl = "aHomeserverUrl", oidcData = null, - slidingSyncProxy = null, loginTimestamp = null, isTokenValid = isTokenValid, loginType = LoginType.UNKNOWN, diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutView.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutView.kt index 969da07f40..f89b36e4d4 100644 --- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutView.kt +++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/signedout/impl/src/main/res/values-hr/translations.xml b/features/signedout/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..17af7626b1 --- /dev/null +++ b/features/signedout/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,8 @@ + + + "Promijenili ste zaporku u drugoj sesiji" + "Izbrisali ste sesiju iz druge sesije" + "Administrator vašeg poslužitelja poništio je vaš pristup" + "Možda ste odjavljeni iz jednog od razloga navedenih u nastavku. Ponovno se prijavite kako biste nastavili koristiti %s." + "Odjavljeni ste" + diff --git a/features/signedout/impl/src/test/kotlin/io/element/android/features/signedout/impl/DefaultSignedOutEntryPointTest.kt b/features/signedout/impl/src/test/kotlin/io/element/android/features/signedout/impl/DefaultSignedOutEntryPointTest.kt index 6366ba5ea1..93925632a3 100644 --- a/features/signedout/impl/src/test/kotlin/io/element/android/features/signedout/impl/DefaultSignedOutEntryPointTest.kt +++ b/features/signedout/impl/src/test/kotlin/io/element/android/features/signedout/impl/DefaultSignedOutEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -34,9 +35,11 @@ class DefaultSignedOutEntryPointTest { ) } val params = SignedOutEntryPoint.Params(A_SESSION_ID) - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .params(params) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + params = params, + ) assertThat(result).isInstanceOf(SignedOutNode::class.java) assertThat(result.plugins).contains(SignedOutNode.Inputs(params.sessionId)) } diff --git a/features/signedout/impl/src/test/kotlin/io/element/android/features/signedout/impl/SignedOutPresenterTest.kt b/features/signedout/impl/src/test/kotlin/io/element/android/features/signedout/impl/SignedOutPresenterTest.kt index e53c0af112..519016b1e0 100644 --- a/features/signedout/impl/src/test/kotlin/io/element/android/features/signedout/impl/SignedOutPresenterTest.kt +++ b/features/signedout/impl/src/test/kotlin/io/element/android/features/signedout/impl/SignedOutPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -58,9 +59,11 @@ class SignedOutPresenterTest { val initialState = awaitItem() assertThat(initialState.signedOutSession).isEqualTo(aSessionData) assertThat(sessionStore.getAllSessions()).isNotEmpty() + assertThat(sessionStore.numberOfSessions()).isEqualTo(1) initialState.eventSink(SignedOutEvents.SignInAgain) assertThat(awaitItem().signedOutSession).isNull() assertThat(sessionStore.getAllSessions()).isEmpty() + assertThat(sessionStore.numberOfSessions()).isEqualTo(0) } } } diff --git a/features/space/api/build.gradle.kts b/features/space/api/build.gradle.kts index dd19efefec..e2b7bb9f97 100644 --- a/features/space/api/build.gradle.kts +++ b/features/space/api/build.gradle.kts @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/features/space/api/src/main/kotlin/io/element/android/features/space/api/SpaceEntryPoint.kt b/features/space/api/src/main/kotlin/io/element/android/features/space/api/SpaceEntryPoint.kt index bdea93f3ef..48a0a55864 100644 --- a/features/space/api/src/main/kotlin/io/element/android/features/space/api/SpaceEntryPoint.kt +++ b/features/space/api/src/main/kotlin/io/element/android/features/space/api/SpaceEntryPoint.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,22 +16,19 @@ import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.matrix.api.core.RoomId interface SpaceEntryPoint : FeatureEntryPoint { - fun nodeBuilder( + fun createNode( parentNode: Node, buildContext: BuildContext, - ): NodeBuilder - - interface NodeBuilder { - fun inputs(inputs: Inputs): NodeBuilder - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } + inputs: Inputs, + callback: Callback + ): Node data class Inputs( val roomId: RoomId ) : NodeInputs interface Callback : Plugin { - fun onOpenRoom(roomId: RoomId, viaParameters: List) + fun navigateToRoom(roomId: RoomId, viaParameters: List) + fun navigateToRoomMemberList() } } diff --git a/features/space/impl/build.gradle.kts b/features/space/impl/build.gradle.kts index b6afbdcb05..03c8cda4ac 100644 --- a/features/space/impl/build.gradle.kts +++ b/features/space/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -39,6 +40,9 @@ dependencies { implementation(projects.libraries.featureflag.api) implementation(projects.features.invite.api) implementation(projects.libraries.previewutils) + implementation(projects.features.securityandprivacy.api) + implementation(projects.features.rolesandpermissions.api) + implementation(projects.features.roomdetailsedit.api) api(projects.features.space.api) testCommonDependencies(libs, true) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPoint.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPoint.kt index 8591978417..2dc32eb9ea 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPoint.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPoint.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,32 +10,22 @@ package io.element.android.features.space.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope @ContributesBinding(SessionScope::class) -@Inject class DefaultSpaceEntryPoint : SpaceEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): SpaceEntryPoint.NodeBuilder { - val plugins = mutableSetOf() - return object : SpaceEntryPoint.NodeBuilder { - override fun inputs(inputs: SpaceEntryPoint.Inputs): SpaceEntryPoint.NodeBuilder { - plugins.add(inputs) - return this - } - - override fun callback(callback: SpaceEntryPoint.Callback): SpaceEntryPoint.NodeBuilder { - plugins.add(callback) - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins = plugins.toList()) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + inputs: SpaceEntryPoint.Inputs, + callback: SpaceEntryPoint.Callback, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf(inputs, callback), + ) } } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt index 78472c7b31..4c91da0301 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,6 +19,7 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject @@ -26,21 +28,24 @@ import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.features.space.impl.di.SpaceFlowGraph import io.element.android.features.space.impl.leave.LeaveSpaceNode import io.element.android.features.space.impl.root.SpaceNode +import io.element.android.features.space.impl.settings.SpaceSettingsFlowNode import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode -import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.DependencyInjectionGraphOwner -import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.spaces.SpaceService import kotlinx.parcelize.Parcelize -@ContributesNode(SessionScope::class) +@ContributesNode(RoomScope::class) @AssistedInject class SpaceFlowNode( @Assisted val buildContext: BuildContext, @Assisted plugins: List, + room: JoinedRoom, spaceService: SpaceService, graphFactory: SpaceFlowGraph.Factory, ) : BaseFlowNode( @@ -51,15 +56,17 @@ class SpaceFlowNode( buildContext = buildContext, plugins = plugins, ), DependencyInjectionGraphOwner { - private val inputs: SpaceEntryPoint.Inputs = inputs() - private val callback = plugins.filterIsInstance().single() - private val spaceRoomList = spaceService.spaceRoomList(inputs.roomId) + private val callback: SpaceEntryPoint.Callback = callback() + private val spaceRoomList = spaceService.spaceRoomList(room.roomId) override val graph = graphFactory.create(spaceRoomList) sealed interface NavTarget : Parcelable { @Parcelize data object Root : NavTarget + @Parcelize + data class Settings(val initialTarget: SpaceSettingsFlowNode.NavTarget = SpaceSettingsFlowNode.NavTarget.Root) : NavTarget + @Parcelize data object Leave : NavTarget } @@ -76,19 +83,54 @@ class SpaceFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Leave -> { - createNode(buildContext, listOf(inputs)) + val callback = object : LeaveSpaceNode.Callback { + override fun closeLeaveSpaceFlow() { + backstack.pop() + } + + override fun navigateToRolesAndPermissions() { + backstack.push(NavTarget.Settings(SpaceSettingsFlowNode.NavTarget.RolesAndPermissions)) + } + } + createNode(buildContext, listOf(callback)) } NavTarget.Root -> { val callback = object : SpaceNode.Callback { - override fun onOpenRoom(roomId: RoomId, viaParameters: List) { - callback.onOpenRoom(roomId, viaParameters) + override fun navigateToRoom(roomId: RoomId, viaParameters: List) { + callback.navigateToRoom(roomId, viaParameters) } - override fun onLeaveSpace() { + override fun navigateToSpaceSettings() { + backstack.push(NavTarget.Settings()) + } + + override fun navigateToRoomMemberList() { + callback.navigateToRoomMemberList() + } + + override fun startLeaveSpaceFlow() { backstack.push(NavTarget.Leave) } } - createNode(buildContext, listOf(inputs, callback)) + createNode(buildContext, listOf(callback)) + } + is NavTarget.Settings -> { + val callback = object : SpaceSettingsFlowNode.Callback { + override fun initialTarget() = navTarget.initialTarget + + override fun navigateToSpaceMembers() { + callback.navigateToRoomMemberList() + } + + override fun startLeaveSpaceFlow() { + backstack.push(NavTarget.Leave) + } + + override fun closeSettings() { + backstack.pop() + } + } + createNode(buildContext, listOf(callback)) } } } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/di/SpaceFlowGraph.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/di/SpaceFlowGraph.kt index b1dac522b4..449c1eb158 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/di/SpaceFlowGraph.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/di/SpaceFlowGraph.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,12 +12,12 @@ import dev.zacsweers.metro.ContributesTo import dev.zacsweers.metro.GraphExtension import dev.zacsweers.metro.Provides import io.element.android.libraries.architecture.NodeFactoriesBindings -import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.spaces.SpaceRoomList @GraphExtension(SpaceFlowScope::class) interface SpaceFlowGraph : NodeFactoriesBindings { - @ContributesTo(SessionScope::class) + @ContributesTo(RoomScope::class) @GraphExtension.Factory interface Factory { fun create(@Provides spaceRoomList: SpaceRoomList): SpaceFlowGraph diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/di/SpaceFlowScope.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/di/SpaceFlowScope.kt index 77fb07f871..e3dce49c2e 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/di/SpaceFlowScope.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/di/SpaceFlowScope.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceEvents.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceEvents.kt index 558ae8454d..8bc96c428c 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceEvents.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt index c60bddea1d..c41acc0c05 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceNode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,10 +17,10 @@ import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode -import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.features.space.impl.di.SpaceFlowScope -import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.room.JoinedRoom @ContributesNode(SpaceFlowScope::class) @AssistedInject @@ -27,12 +28,19 @@ class LeaveSpaceNode( @Assisted buildContext: BuildContext, @Assisted plugins: List, matrixClient: MatrixClient, + room: JoinedRoom, presenterFactory: LeaveSpacePresenter.Factory, ) : Node(buildContext, plugins = plugins) { - private val inputs: SpaceEntryPoint.Inputs = inputs() - private val leaveSpaceHandle = matrixClient.spaceService.getLeaveSpaceHandle(inputs.roomId) + interface Callback : Plugin { + fun closeLeaveSpaceFlow() + fun navigateToRolesAndPermissions() + } + + private val leaveSpaceHandle = matrixClient.spaceService.getLeaveSpaceHandle(room.roomId) private val presenter: LeaveSpacePresenter = presenterFactory.create(leaveSpaceHandle) + private val callback: Callback = callback() + override fun onBuilt() { super.onBuilt() lifecycle.subscribe( @@ -47,7 +55,8 @@ class LeaveSpaceNode( val state = presenter.present() LeaveSpaceView( state = state, - onCancel = ::navigateUp, + onCancel = callback::closeLeaveSpaceFlow, + onRolesAndPermissionsClick = callback::navigateToRolesAndPermissions, modifier = modifier ) } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt index 19e593bf7c..a331aefccd 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -87,7 +88,7 @@ class LeaveSpacePresenter( } LaunchedEffect(selectedRoomIds, leaveSpaceRooms) { selectableSpaceRooms = leaveSpaceRooms.map { - it?.others.orEmpty().map { room -> + it.others.map { room -> SelectableSpaceRoom( spaceRoom = room.spaceRoom, isLastAdmin = room.isLastAdmin, @@ -97,7 +98,7 @@ class LeaveSpacePresenter( } } - fun handleEvents(event: LeaveSpaceEvents) { + fun handleEvent(event: LeaveSpaceEvents) { when (event) { LeaveSpaceEvents.Retry -> { leaveSpaceRooms = AsyncData.Loading() @@ -134,7 +135,7 @@ class LeaveSpacePresenter( isLastAdmin = leaveSpaceRooms.dataOrNull()?.current?.isLastAdmin == true, selectableSpaceRooms = selectableSpaceRooms, leaveSpaceAction = leaveSpaceAction.value, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceState.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceState.kt index 0f2a0f93f6..1e85014c84 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceState.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,6 +11,7 @@ package io.element.android.features.space.impl.leave import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList data class LeaveSpaceState( val spaceName: String?, @@ -18,10 +20,15 @@ data class LeaveSpaceState( val leaveSpaceAction: AsyncAction, val eventSink: (LeaveSpaceEvents) -> Unit, ) { - private val rooms = selectableSpaceRooms.dataOrNull().orEmpty() - private val partition = rooms.partition { it.isLastAdmin } - private val lastAdminRooms = partition.first - private val selectableRooms = partition.second + private val rooms = selectableSpaceRooms.dataOrNull().orEmpty().toImmutableList() + private val lastAdminRooms: ImmutableList + private val selectableRooms: ImmutableList + + init { + val partition = rooms.partition { it.isLastAdmin } + lastAdminRooms = partition.first.toImmutableList() + selectableRooms = partition.second.toImmutableList() + } /** * True if we should show the quick action to select/deselect all rooms. diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceStateProvider.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceStateProvider.kt index fec625ca13..46d3e53b2b 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceStateProvider.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt index 7432301f91..d405162b88 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -69,6 +70,7 @@ import io.element.android.libraries.ui.strings.CommonStrings fun LeaveSpaceView( state: LeaveSpaceState, onCancel: () -> Unit, + onRolesAndPermissionsClick: () -> Unit, modifier: Modifier = Modifier, ) { Scaffold( @@ -130,6 +132,8 @@ fun LeaveSpaceView( state.eventSink(LeaveSpaceEvents.LeaveSpace) }, onCancel = onCancel, + showRolesAndPermissionsButton = state.isLastAdmin, + onRolesAndPermissionsClick = onRolesAndPermissionsClick, ) } } @@ -210,6 +214,8 @@ private fun LeaveSpaceButtons( showLeaveButton: Boolean, selectedRoomsCount: Int, onLeaveSpace: () -> Unit, + showRolesAndPermissionsButton: Boolean, + onRolesAndPermissionsClick: () -> Unit, onCancel: () -> Unit, ) { ButtonColumnMolecule( @@ -229,8 +235,14 @@ private fun LeaveSpaceButtons( destructive = true, ) } - // TODO For least admin space, add a button to open the settings. - // See https://www.figma.com/design/kcnHxunG1LDWXsJhaNuiHz/ER-145--Spaces-on-Element-X?node-id=4622-59600 + if (showRolesAndPermissionsButton) { + Button( + text = stringResource(CommonStrings.action_go_to_roles_and_permissions), + onClick = onRolesAndPermissionsClick, + modifier = Modifier.fillMaxWidth(), + leadingIcon = IconSource.Vector(CompoundIcons.Settings()), + ) + } TextButton( modifier = Modifier.fillMaxWidth(), text = stringResource(CommonStrings.action_cancel), @@ -345,5 +357,6 @@ internal fun LeaveSpaceViewPreview( LeaveSpaceView( state = state, onCancel = {}, + onRolesAndPermissionsClick = {}, ) } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/SelectableSpaceRoom.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/SelectableSpaceRoom.kt index 6247a9e48f..d7ed243af6 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/SelectableSpaceRoom.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/SelectableSpaceRoom.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceEvents.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceEvents.kt index 8c8eb8dbb7..16a6ad1c3f 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceEvents.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceNode.kt index 52c3472182..240bc89990 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceNode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -22,6 +23,7 @@ import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteV import io.element.android.features.space.impl.di.SpaceFlowScope import io.element.android.libraries.androidutils.R import io.element.android.libraries.androidutils.system.startSharePlainTextIntent +import io.element.android.libraries.architecture.callback import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.spaces.SpaceRoomList @@ -40,11 +42,13 @@ class SpaceNode( private val acceptDeclineInviteView: AcceptDeclineInviteView, ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { - fun onOpenRoom(roomId: RoomId, viaParameters: List) - fun onLeaveSpace() + fun navigateToRoom(roomId: RoomId, viaParameters: List) + fun navigateToSpaceSettings() + fun navigateToRoomMemberList() + fun startLeaveSpaceFlow() } - private val callback = plugins.filterIsInstance().single() + private val callback: Callback = callback() private fun onShareRoom(context: Context) = lifecycleScope.launch { matrixClient.getRoom(spaceRoomList.roomId)?.use { room -> @@ -71,19 +75,25 @@ class SpaceNode( state = state, onBackClick = ::navigateUp, onLeaveSpaceClick = { - callback.onLeaveSpace() + callback.startLeaveSpaceFlow() }, onRoomClick = { spaceRoom -> - callback.onOpenRoom(spaceRoom.roomId, spaceRoom.via) + callback.navigateToRoom(spaceRoom.roomId, spaceRoom.via) + }, + onSettingsClick = { + callback.navigateToSpaceSettings() }, onShareSpace = { onShareRoom(context) }, + onViewMembersClick = { + callback.navigateToRoomMemberList() + }, acceptDeclineInviteView = { acceptDeclineInviteView.Render( state = state.acceptDeclineInviteState, onAcceptInviteSuccess = { roomId -> - callback.onOpenRoom(roomId, emptyList()) + callback.navigateToRoom(roomId, emptyList()) }, onDeclineInviteSuccess = { roomId -> // No action needed diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt index 58f5c8f5fb..309747d2c9 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,26 +11,33 @@ package io.element.android.features.space.impl.root import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import dev.zacsweers.metro.Inject -import im.vector.app.features.analytics.plan.JoinedRoom +import im.vector.app.features.analytics.plan.JoinedRoom.Trigger import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState import io.element.android.features.invite.api.toInviteData +import io.element.android.features.space.impl.settings.SpaceSettingsPermissions +import io.element.android.features.space.impl.settings.spaceSettingsPermissions import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.mapState import io.element.android.libraries.di.annotations.SessionCoroutineScope +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias +import io.element.android.libraries.matrix.api.room.BaseRoom import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.join.JoinRoom +import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState import io.element.android.libraries.matrix.api.spaces.SpaceRoom import io.element.android.libraries.matrix.api.spaces.SpaceRoomList import io.element.android.libraries.matrix.ui.safety.rememberHideInvitesAvatar @@ -47,11 +55,13 @@ import kotlin.jvm.optionals.getOrNull @Inject class SpacePresenter( private val spaceRoomList: SpaceRoomList, + private val room: BaseRoom, private val client: MatrixClient, private val seenInvitesStore: SeenInvitesStore, private val joinRoom: JoinRoom, private val acceptDeclineInvitePresenter: Presenter, @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, + private val featureFlagService: FeatureFlagService, ) : Presenter { private var children by mutableStateOf>(persistentListOf()) @@ -78,6 +88,17 @@ class SpacePresenter( } }.collectAsState() + val permissions by room.permissionsAsState(SpaceSettingsPermissions.DEFAULT) { perms -> + perms.spaceSettingsPermissions() + } + val isSpaceSettingsEnabled by remember { + featureFlagService.isFeatureEnabledFlow(FeatureFlags.SpaceSettings) + }.collectAsState(false) + + val roomInfo by room.roomInfoFlow.collectAsState() + val canAccessSpaceSettings by remember { + derivedStateOf { isSpaceSettingsEnabled && permissions.hasAny(roomInfo.joinRule) } + } val currentSpace by spaceRoomList.currentSpaceFlow.collectAsState() val (joinActions, setJoinActions) = remember { mutableStateOf(emptyMap>()) } @@ -93,7 +114,7 @@ class SpacePresenter( val acceptDeclineInviteState = acceptDeclineInvitePresenter.present() - fun handleEvents(event: SpaceEvents) { + fun handleEvent(event: SpaceEvents) { when (event) { SpaceEvents.LoadMore -> localCoroutineScope.paginate() is SpaceEvents.Join -> { @@ -128,7 +149,8 @@ class SpacePresenter( joinActions = joinActions.toImmutableMap(), acceptDeclineInviteState = acceptDeclineInviteState, topicViewerState = topicViewerState, - eventSink = ::handleEvents, + canAccessSpaceSettings = canAccessSpaceSettings, + eventSink = ::handleEvent, ) } @@ -141,7 +163,7 @@ class SpacePresenter( joinRoom.invoke( roomIdOrAlias = spaceRoom.roomId.toRoomIdOrAlias(), serverNames = spaceRoom.via, - trigger = JoinedRoom.Trigger.SpaceHierarchy, + trigger = Trigger.SpaceHierarchy, ).onFailure { setJoinActions(joinActions + mapOf(spaceRoom.roomId to AsyncAction.Failure(it))) } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceState.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceState.kt index 2499e4c046..cceda62806 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceState.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -25,6 +26,7 @@ data class SpaceState( val joinActions: ImmutableMap>, val acceptDeclineInviteState: AcceptDeclineInviteState, val topicViewerState: TopicViewerState, + val canAccessSpaceSettings: Boolean, val eventSink: (SpaceEvents) -> Unit ) { fun isJoining(spaceId: RoomId): Boolean = joinActions[spaceId] == AsyncAction.Loading diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceStateProvider.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceStateProvider.kt index 98a7363abb..52894ad599 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceStateProvider.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -52,6 +53,7 @@ fun aSpaceState( hasMoreToLoad: Boolean = true, acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(), topicViewerState: TopicViewerState = TopicViewerState.Hidden, + canAccessSpaceSettings: Boolean = true, eventSink: (SpaceEvents) -> Unit = { }, ) = SpaceState( currentSpace = parentSpace, @@ -62,6 +64,7 @@ fun aSpaceState( joinActions = joinActions.toImmutableMap(), acceptDeclineInviteState = acceptDeclineInviteState, topicViewerState = topicViewerState, + canAccessSpaceSettings = canAccessSpaceSettings, eventSink = eventSink, ) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt index 46c6a10e57..769b608e8e 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpaceView.kt @@ -1,12 +1,15 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.space.impl.root +import androidx.annotation.StringRes +import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row @@ -14,6 +17,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -24,6 +29,8 @@ import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.heading import androidx.compose.ui.semantics.semantics @@ -49,6 +56,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.DropdownMenu import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem +import io.element.android.libraries.designsystem.theme.components.HorizontalDivider import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.Scaffold @@ -72,6 +80,8 @@ fun SpaceView( onRoomClick: (spaceRoom: SpaceRoom) -> Unit, onShareSpace: () -> Unit, onLeaveSpaceClick: () -> Unit, + onSettingsClick: () -> Unit, + onViewMembersClick: () -> Unit, modifier: Modifier = Modifier, acceptDeclineInviteView: @Composable () -> Unit, ) { @@ -80,9 +90,12 @@ fun SpaceView( topBar = { SpaceViewTopBar( currentSpace = state.currentSpace, + canAccessSpaceSettings = state.canAccessSpaceSettings, onBackClick = onBackClick, onLeaveSpaceClick = onLeaveSpaceClick, onShareSpace = onShareSpace, + onSettingsClick = onSettingsClick, + onViewMembersClick = onViewMembersClick, ) }, content = { padding -> @@ -177,33 +190,40 @@ private fun SpaceViewContent( onTopicClick = onTopicClick ) } - } - state.children.forEach { spaceRoom -> item { - val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED - val isCurrentlyJoining = state.isJoining(spaceRoom.roomId) - SpaceRoomItemView( - spaceRoom = spaceRoom, - showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites, - hideAvatars = isInvitation && state.hideInvitesAvatar, - onClick = { - onRoomClick(spaceRoom) + HorizontalDivider() + } + } + itemsIndexed( + items = state.children, + key = { _, spaceRoom -> spaceRoom.roomId } + ) { index, spaceRoom -> + val isInvitation = spaceRoom.state == CurrentUserMembership.INVITED + val isCurrentlyJoining = state.isJoining(spaceRoom.roomId) + SpaceRoomItemView( + spaceRoom = spaceRoom, + showUnreadIndicator = isInvitation && spaceRoom.roomId !in state.seenSpaceInvites, + hideAvatars = isInvitation && state.hideInvitesAvatar, + onClick = { + onRoomClick(spaceRoom) + }, + onLongClick = { + // TODO + }, + trailingAction = spaceRoom.trailingAction(isCurrentlyJoining = isCurrentlyJoining) { + state.eventSink(SpaceEvents.Join(spaceRoom)) + }, + bottomAction = spaceRoom.inviteButtons( + onAcceptClick = { + state.eventSink(SpaceEvents.AcceptInvite(spaceRoom)) }, - onLongClick = { - // TODO - }, - trailingAction = spaceRoom.trailingAction(isCurrentlyJoining = isCurrentlyJoining) { - state.eventSink(SpaceEvents.Join(spaceRoom)) - }, - bottomAction = spaceRoom.inviteButtons( - onAcceptClick = { - state.eventSink(SpaceEvents.AcceptInvite(spaceRoom)) - }, - onDeclineClick = { - state.eventSink(SpaceEvents.DeclineInvite(spaceRoom)) - } - ) + onDeclineClick = { + state.eventSink(SpaceEvents.DeclineInvite(spaceRoom)) + } ) + ) + if (index != state.children.lastIndex) { + HorizontalDivider() } } if (state.hasMoreToLoad) { @@ -238,9 +258,12 @@ private fun LoadingMoreIndicator( @Composable private fun SpaceViewTopBar( currentSpace: SpaceRoom?, + canAccessSpaceSettings: Boolean, onBackClick: () -> Unit, onLeaveSpaceClick: () -> Unit, + onSettingsClick: () -> Unit, onShareSpace: () -> Unit, + onViewMembersClick: () -> Unit, modifier: Modifier = Modifier, ) { TopAppBar( @@ -250,9 +273,13 @@ private fun SpaceViewTopBar( }, title = { if (currentSpace != null) { + val roundedCornerShape = RoundedCornerShape(8.dp) SpaceAvatarAndNameRow( name = currentSpace.displayName, avatarData = currentSpace.getAvatarData(AvatarSize.TimelineRoom), + modifier = Modifier + .clip(roundedCornerShape) + .clickable(enabled = canAccessSpaceSettings, onClick = onSettingsClick) ) } }, @@ -270,37 +297,39 @@ private fun SpaceViewTopBar( expanded = showMenu, onDismissRequest = { showMenu = false } ) { - DropdownMenuItem( + SpaceMenuItem( + titleRes = CommonStrings.screen_space_menu_action_members, + icon = CompoundIcons.User(), + onClick = { + showMenu = false + onViewMembersClick() + } + ) + SpaceMenuItem( + titleRes = CommonStrings.action_share, + icon = CompoundIcons.ShareAndroid(), onClick = { showMenu = false onShareSpace() - }, - text = { Text(stringResource(id = CommonStrings.action_share)) }, - leadingIcon = { - Icon( - imageVector = CompoundIcons.ShareAndroid(), - tint = ElementTheme.colors.iconSecondary, - contentDescription = null, - ) } ) - DropdownMenuItem( + if (canAccessSpaceSettings) { + SpaceMenuItem( + titleRes = CommonStrings.common_settings, + icon = CompoundIcons.Settings(), + onClick = { + showMenu = false + onSettingsClick() + } + ) + } + SpaceMenuItem( + titleRes = CommonStrings.action_leave_space, + icon = CompoundIcons.Leave(), + isCritical = true, onClick = { showMenu = false onLeaveSpaceClick() - }, - text = { - Text( - text = stringResource(id = CommonStrings.action_leave), - color = ElementTheme.colors.textCriticalPrimary, - ) - }, - leadingIcon = { - Icon( - imageVector = CompoundIcons.Leave(), - tint = ElementTheme.colors.iconCriticalPrimary, - contentDescription = null, - ) } ) } @@ -308,6 +337,31 @@ private fun SpaceViewTopBar( ) } +@Composable +private fun SpaceMenuItem( + @StringRes titleRes: Int, + icon: ImageVector, + onClick: () -> Unit, + isCritical: Boolean = false, +) { + DropdownMenuItem( + onClick = onClick, + text = { + Text( + text = stringResource(titleRes), + color = if (isCritical) ElementTheme.colors.textCriticalPrimary else ElementTheme.colors.textPrimary, + ) + }, + leadingIcon = { + Icon( + imageVector = icon, + tint = if (isCritical) ElementTheme.colors.iconCriticalPrimary else ElementTheme.colors.iconSecondary, + contentDescription = null, + ) + } + ) +} + @Composable private fun SpaceAvatarAndNameRow( name: String?, @@ -382,6 +436,8 @@ internal fun SpaceViewPreview( onShareSpace = {}, onLeaveSpaceClick = {}, acceptDeclineInviteView = {}, + onSettingsClick = {}, + onViewMembersClick = {}, onBackClick = {}, ) } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsEvents.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsEvents.kt new file mode 100644 index 0000000000..3c2b1e6609 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsEvents.kt @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +sealed interface SpaceSettingsEvents diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsFlowNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsFlowNode.kt new file mode 100644 index 0000000000..8fc7f51aa7 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsFlowNode.kt @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +import android.os.Parcelable +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.pop +import com.bumble.appyx.navmodel.backstack.operation.push +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject +import io.element.android.annotations.ContributesNode +import io.element.android.features.rolesandpermissions.api.RolesAndPermissionsEntryPoint +import io.element.android.features.roomdetailsedit.api.RoomDetailsEditEntryPoint +import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyEntryPoint +import io.element.android.features.space.impl.di.SpaceFlowScope +import io.element.android.libraries.architecture.BackstackView +import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.callback +import io.element.android.libraries.architecture.createNode +import kotlinx.parcelize.Parcelize + +@ContributesNode(SpaceFlowScope::class) +@AssistedInject +class SpaceSettingsFlowNode( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val securityAndPrivacyEntryPoint: SecurityAndPrivacyEntryPoint, + private val rolesAndPermissionsEntryPoint: RolesAndPermissionsEntryPoint, + private val roomDetailsEditEntryPoint: RoomDetailsEditEntryPoint +) : BaseFlowNode( + backstack = BackStack( + initialElement = initialElement(plugins), + savedStateMap = buildContext.savedStateMap, + ), + buildContext = buildContext, + plugins = plugins, +) { + interface Callback : Plugin { + fun initialTarget(): NavTarget = NavTarget.Root + fun navigateToSpaceMembers() + fun startLeaveSpaceFlow() + fun closeSettings() + } + + sealed interface NavTarget : Parcelable { + @Parcelize + data object Root : NavTarget + + @Parcelize + data object EditDetails : NavTarget + + @Parcelize + data object SecurityAndPrivacy : NavTarget + + @Parcelize + data object RolesAndPermissions : NavTarget + } + + private val callback: Callback = callback() + + override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { + return when (navTarget) { + is NavTarget.Root -> { + val callback = object : SpaceSettingsNode.Callback { + override fun closeSettings() { + callback.closeSettings() + } + + override fun navigateToEditDetails() { + backstack.push(NavTarget.EditDetails) + } + + override fun navigateToSpaceMembers() { + callback.navigateToSpaceMembers() + } + + override fun navigateToRolesAndPermissions() { + backstack.push(NavTarget.RolesAndPermissions) + } + + override fun navigateToSecurityAndPrivacy() { + backstack.push(NavTarget.SecurityAndPrivacy) + } + + override fun startLeaveSpaceFlow() { + callback.startLeaveSpaceFlow() + } + } + createNode( + buildContext = buildContext, + plugins = listOf(callback), + ) + } + is NavTarget.SecurityAndPrivacy -> { + val callback = object : SecurityAndPrivacyEntryPoint.Callback { + override fun onDone() { + backstack.pop() + } + } + securityAndPrivacyEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + callback = callback, + ) + } + is NavTarget.RolesAndPermissions -> { + val callback = object : RolesAndPermissionsEntryPoint.Callback { + override fun onDone() { + backstack.pop() + } + } + rolesAndPermissionsEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + callback = callback, + ) + } + NavTarget.EditDetails -> { + roomDetailsEditEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + ) + } + } + } + + @Composable + override fun View(modifier: Modifier) { + BackstackView(modifier) + } +} + +fun initialElement(plugins: List): SpaceSettingsFlowNode.NavTarget { + return plugins.callback().initialTarget() +} diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsNode.kt new file mode 100644 index 0000000000..b1e64fbba1 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsNode.kt @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject +import io.element.android.annotations.ContributesNode +import io.element.android.features.space.impl.di.SpaceFlowScope +import io.element.android.libraries.architecture.appyx.launchMolecule +import io.element.android.libraries.architecture.callback + +@ContributesNode(SpaceFlowScope::class) +@AssistedInject +class SpaceSettingsNode( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: SpaceSettingsPresenter, +) : Node(buildContext, plugins = plugins) { + interface Callback : Plugin { + fun closeSettings() + + fun navigateToEditDetails() + fun navigateToSpaceMembers() + fun navigateToRolesAndPermissions() + fun navigateToSecurityAndPrivacy() + fun startLeaveSpaceFlow() + } + + private val callback: Callback = callback() + private val stateFlow = launchMolecule { presenter.present() } + + @Composable + override fun View(modifier: Modifier) { + val state by stateFlow.collectAsState() + SpaceSettingsView( + state = state, + modifier = modifier, + onSpaceInfoClick = callback::navigateToEditDetails, + onBackClick = callback::closeSettings, + onMembersClick = callback::navigateToSpaceMembers, + onRolesAndPermissionsClick = callback::navigateToRolesAndPermissions, + onSecurityAndPrivacyClick = callback::navigateToSecurityAndPrivacy, + onLeaveSpaceClick = callback::startLeaveSpaceFlow, + ) + } +} diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPermissions.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPermissions.kt new file mode 100644 index 0000000000..e3ec70a51d --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPermissions.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +import io.element.android.features.roomdetailsedit.api.RoomDetailsEditPermissions +import io.element.android.features.roomdetailsedit.api.roomDetailsEditPermissions +import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions +import io.element.android.features.securityandprivacy.api.securityAndPrivacyPermissions +import io.element.android.libraries.matrix.api.room.join.JoinRule +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions +import io.element.android.libraries.matrix.api.room.powerlevels.canEditRolesAndPermissions + +data class SpaceSettingsPermissions( + val editDetailsPermissions: RoomDetailsEditPermissions, + val canEditRolesAndPermissions: Boolean, + val securityAndPrivacyPermissions: SecurityAndPrivacyPermissions, +) { + fun hasAny(joinRule: JoinRule?): Boolean { + return editDetailsPermissions.hasAny || + canEditRolesAndPermissions || + securityAndPrivacyPermissions.hasAny(isSpace = true, joinRule = joinRule) + } + + companion object { + val DEFAULT = SpaceSettingsPermissions( + editDetailsPermissions = RoomDetailsEditPermissions.DEFAULT, + canEditRolesAndPermissions = false, + securityAndPrivacyPermissions = SecurityAndPrivacyPermissions.DEFAULT, + ) + } +} + +fun RoomPermissions.spaceSettingsPermissions(): SpaceSettingsPermissions { + return SpaceSettingsPermissions( + editDetailsPermissions = roomDetailsEditPermissions(), + canEditRolesAndPermissions = canEditRolesAndPermissions(), + securityAndPrivacyPermissions = securityAndPrivacyPermissions(), + ) +} diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt new file mode 100644 index 0000000000..565008a778 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import dev.zacsweers.metro.Inject +import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.matrix.api.room.JoinedRoom +import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState + +@Inject +class SpaceSettingsPresenter( + private val room: JoinedRoom, +) : Presenter { + @Composable + override fun present(): SpaceSettingsState { + val roomInfo by room.roomInfoFlow.collectAsState() + val permissions by room.permissionsAsState(SpaceSettingsPermissions.DEFAULT) { perms -> + perms.spaceSettingsPermissions() + } + val showSecurityAndPrivacy by remember { + derivedStateOf { permissions.securityAndPrivacyPermissions.hasAny(isSpace = false, joinRule = roomInfo.joinRule) } + } + + return SpaceSettingsState( + roomId = room.roomId, + name = roomInfo.name.orEmpty(), + canonicalAlias = roomInfo.canonicalAlias, + avatarUrl = roomInfo.avatarUrl, + memberCount = roomInfo.activeMembersCount, + canEditDetails = permissions.editDetailsPermissions.hasAny, + showRolesAndPermissions = permissions.canEditRolesAndPermissions, + showSecurityAndPrivacy = showSecurityAndPrivacy, + eventSink = {}, + ) + } +} diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsState.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsState.kt new file mode 100644 index 0000000000..5c7e9b5c6e --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsState.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +import io.element.android.libraries.matrix.api.core.RoomAlias +import io.element.android.libraries.matrix.api.core.RoomId + +data class SpaceSettingsState( + val roomId: RoomId, + val name: String, + val canonicalAlias: RoomAlias?, + val avatarUrl: String?, + val memberCount: Long, + val canEditDetails: Boolean, + val showRolesAndPermissions: Boolean, + val showSecurityAndPrivacy: Boolean, + val eventSink: (SpaceSettingsEvents) -> Unit +) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStateProvider.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStateProvider.kt new file mode 100644 index 0000000000..2030b6885a --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsStateProvider.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.matrix.api.core.RoomAlias +import io.element.android.libraries.matrix.api.core.RoomId + +open class SpaceSettingsStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aSpaceSettingsState(), + aSpaceSettingsState(alias = null), + aSpaceSettingsState(showSecurityAndPrivacy = true), + aSpaceSettingsState(showRolesAndPermissions = true), + ) +} + +fun aSpaceSettingsState( + roomId: RoomId = RoomId("!aRoomId:element.io"), + name: String = "Space name", + alias: RoomAlias? = RoomAlias("#spacename:element.io"), + avatarUrl: String? = null, + memberCount: Long = 100, + showRolesAndPermissions: Boolean = false, + showSecurityAndPrivacy: Boolean = false, + canEditDetails: Boolean = false, + eventSink: (SpaceSettingsEvents) -> Unit = {}, +) = SpaceSettingsState( + roomId = roomId, + name = name, + canonicalAlias = alias, + avatarUrl = avatarUrl, + memberCount = memberCount, + canEditDetails = canEditDetails, + showRolesAndPermissions = showRolesAndPermissions, + showSecurityAndPrivacy = showSecurityAndPrivacy, + eventSink = eventSink, +) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt new file mode 100644 index 0000000000..5a8aac4a30 --- /dev/null +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsView.kt @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.space.impl.settings + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.space.impl.R +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.avatar.AvatarType +import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.components.list.ListItemContent +import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.IconSource +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.ListItemStyle +import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun SpaceSettingsView( + state: SpaceSettingsState, + onBackClick: () -> Unit, + onSpaceInfoClick: () -> Unit, + onMembersClick: () -> Unit, + onRolesAndPermissionsClick: () -> Unit, + onSecurityAndPrivacyClick: () -> Unit, + onLeaveSpaceClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Scaffold( + modifier = modifier, + topBar = { + SpaceSettingsTopBar(onBackClick = onBackClick) + }, + ) { padding -> + Column( + modifier = Modifier + .padding(padding) + .verticalScroll(rememberScrollState()) + ) { + SpaceInfoSection( + roomId = state.roomId, + name = state.name, + avatarUrl = state.avatarUrl, + canonicalAlias = state.canonicalAlias?.value, + canEditDetails = state.canEditDetails, + onSpaceInfoClick = onSpaceInfoClick, + ) + Section(isVisible = state.showSecurityAndPrivacy, content = { + SecurityAndPrivacyItem( + onClick = onSecurityAndPrivacyClick + ) + }) + Section(content = { + MembersItem(state.memberCount, onClick = onMembersClick) + if (state.showRolesAndPermissions) { + RolesAndPermissionsItem(onClick = onRolesAndPermissionsClick) + } + }) + Section(content = { + LeaveSpaceItem( + onClick = onLeaveSpaceClick + ) + }) + } + } +} + +@Composable +private fun SpaceInfoSection( + roomId: RoomId, + name: String, + avatarUrl: String?, + canonicalAlias: String?, + canEditDetails: Boolean, + onSpaceInfoClick: () -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(enabled = canEditDetails, onClick = onSpaceInfoClick) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Avatar( + avatarData = AvatarData(roomId.value, name, avatarUrl, AvatarSize.SpaceListItem), + avatarType = AvatarType.Space(), + contentDescription = avatarUrl?.let { stringResource(CommonStrings.a11y_avatar) }, + ) + Spacer(Modifier.width(16.dp)) + Column { + Text( + text = name, + style = ElementTheme.typography.fontHeadingMdRegular, + color = ElementTheme.colors.textPrimary, + ) + if (canonicalAlias != null) { + Text( + text = canonicalAlias, + style = ElementTheme.typography.fontBodyMdRegular, + color = ElementTheme.colors.textSecondary, + ) + } + } + } +} + +@Composable +private fun Section( + modifier: Modifier = Modifier, + isVisible: Boolean = true, + content: @Composable ColumnScope.() -> Unit, +) { + if (isVisible) { + PreferenceCategory(content = content, modifier = modifier) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun SpaceSettingsTopBar( + onBackClick: () -> Unit, + modifier: Modifier = Modifier, +) { + TopAppBar( + titleStr = stringResource(CommonStrings.common_settings), + navigationIcon = { BackButton(onClick = onBackClick) }, + modifier = modifier, + ) +} + +@Composable +private fun SecurityAndPrivacyItem( + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + ListItem( + headlineContent = { Text(stringResource(R.string.screen_space_settings_security_and_privacy)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Lock())), + onClick = onClick, + modifier = modifier, + ) +} + +@Composable +private fun MembersItem( + memberCount: Long, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + ListItem( + headlineContent = { Text(stringResource(CommonStrings.common_people)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.User())), + trailingContent = ListItemContent.Text(memberCount.toString()), + onClick = onClick, + modifier = modifier, + ) +} + +@Composable +private fun RolesAndPermissionsItem( + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + ListItem( + headlineContent = { Text(stringResource(R.string.screen_space_settings_roles_and_permissions)) }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Admin())), + onClick = onClick, + modifier = modifier, + ) +} + +@Composable +private fun LeaveSpaceItem( + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + ListItem( + headlineContent = { + Text(stringResource(CommonStrings.action_leave_space)) + }, + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Leave())), + style = ListItemStyle.Destructive, + onClick = onClick, + modifier = modifier, + ) +} + +@PreviewsDayNight +@Composable +internal fun SpaceSettingsViewPreview( + @PreviewParameter(SpaceSettingsStateProvider::class) state: SpaceSettingsState +) = ElementPreview { + SpaceSettingsView( + state = state, + onBackClick = {}, + onSpaceInfoClick = {}, + onMembersClick = {}, + onRolesAndPermissionsClick = {}, + onSecurityAndPrivacyClick = {}, + onLeaveSpaceClick = {}, + modifier = Modifier, + ) +} diff --git a/features/space/impl/src/main/res/values-be/translations.xml b/features/space/impl/src/main/res/values-be/translations.xml new file mode 100644 index 0000000000..dbd39abf09 --- /dev/null +++ b/features/space/impl/src/main/res/values-be/translations.xml @@ -0,0 +1,4 @@ + + + "Ролі і дазволы" + diff --git a/features/space/impl/src/main/res/values-bg/translations.xml b/features/space/impl/src/main/res/values-bg/translations.xml new file mode 100644 index 0000000000..0759934bbd --- /dev/null +++ b/features/space/impl/src/main/res/values-bg/translations.xml @@ -0,0 +1,6 @@ + + + "Напускане на пространството" + "Роли и разрешения" + "Защита и поверителност" + diff --git a/features/space/impl/src/main/res/values-cs/translations.xml b/features/space/impl/src/main/res/values-cs/translations.xml index 0c645f0650..d4730fc62d 100644 --- a/features/space/impl/src/main/res/values-cs/translations.xml +++ b/features/space/impl/src/main/res/values-cs/translations.xml @@ -7,5 +7,11 @@ "Opustit %1$d místností a prostor" "Tím budete také odstraněni ze všech místností v tomto prostoru." + "Než budete moci odejít, musíte pro tento prostor přiřadit jiného správce." + "Z následujících místností nebudete odstraněni, protože jste jediným administrátorem:" "Opustit %1$s?" + "Jste jediným administrátorem pro %1$s" + "Opustit prostor" + "Role a oprávnění" + "Zabezpečení a soukromí" diff --git a/features/space/impl/src/main/res/values-cy/translations.xml b/features/space/impl/src/main/res/values-cy/translations.xml index d9d6c02ebe..987a41acbb 100644 --- a/features/space/impl/src/main/res/values-cy/translations.xml +++ b/features/space/impl/src/main/res/values-cy/translations.xml @@ -11,4 +11,7 @@ "Dewiswch yr ystafelloedd yr hoffech chi eu gadael nad chi yw\'r unig weinyddwr ar eu cyfer:" "Gadael %1$s ?" + "Gadael y gofod" + "Rolau a chaniatâd" + "Diogelwch a phreifatrwydd" diff --git a/features/space/impl/src/main/res/values-da/translations.xml b/features/space/impl/src/main/res/values-da/translations.xml index 7953f32e99..6422b9635d 100644 --- a/features/space/impl/src/main/res/values-da/translations.xml +++ b/features/space/impl/src/main/res/values-da/translations.xml @@ -2,12 +2,15 @@ "%1$s (Admin)" - "Forlad %1$d rum og klynge" - "Forlad %1$d rum og klynger" + "Forlad %1$d rum og gruppe" + "Forlad %1$d rum og Grupper" "Vælg de rum, du vil forlade, som du ikke er den eneste administrator for:" - "Du skal tildele en anden administrator til denne klynge, før du kan forlade den." + "Du skal tildele en anden administrator til denne gruppe, før du kan forlade den." "Du vil ikke blive fjernet fra følgende rum, fordi du er den eneste administrator:" "Forlad %1$s?" "Du er den eneste administrator for %1$s" + "Forlad gruppe" + "Roller og tilladelser" + "Sikkerhed og privatliv" diff --git a/features/space/impl/src/main/res/values-de/translations.xml b/features/space/impl/src/main/res/values-de/translations.xml index 6fd5d7c76b..a001756c6c 100644 --- a/features/space/impl/src/main/res/values-de/translations.xml +++ b/features/space/impl/src/main/res/values-de/translations.xml @@ -10,4 +10,7 @@ "Du wirst aus den folgenden Chats nicht entfernt, weil du der einzige Admin bist:" "%1$s verlassen?" "Du bist der einzige Administrator für %1$s" + "Space verlassen" + "Rollen und Berechtigungen" + "Sicherheit & Datenschutz" diff --git a/features/space/impl/src/main/res/values-el/translations.xml b/features/space/impl/src/main/res/values-el/translations.xml new file mode 100644 index 0000000000..144aae7278 --- /dev/null +++ b/features/space/impl/src/main/res/values-el/translations.xml @@ -0,0 +1,5 @@ + + + "Ρόλοι και δικαιώματα" + "Ασφάλεια & απόρρητο" + diff --git a/features/space/impl/src/main/res/values-es/translations.xml b/features/space/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..41887088b6 --- /dev/null +++ b/features/space/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,5 @@ + + + "Roles y permisos" + "Seguridad y privacidad" + diff --git a/features/space/impl/src/main/res/values-et/translations.xml b/features/space/impl/src/main/res/values-et/translations.xml index 3ab5d65284..43eaade351 100644 --- a/features/space/impl/src/main/res/values-et/translations.xml +++ b/features/space/impl/src/main/res/values-et/translations.xml @@ -6,6 +6,11 @@ "Lahku %1$d-st jututoast ja kogukonnast" "Sellega eemaldad end ka kõikidest antud kogukonna jututubadest." + "Enne lahkumist pead sa selle kogukonna jaoks lisama vähemalt ühe täiendava peakasutaja." "Sind ei saa järgnevatest jututubadest eemaldada, kuna oled seal/neis ainus peakasutaja:" "Kas lahkud %1$s kogukonnast?" + "Sa oled siin ainus peakasutaja: %1$s" + "Lahku kogukonnast" + "Rollid ja õigused" + "Turvalisus ja privaatsus" diff --git a/features/space/impl/src/main/res/values-eu/translations.xml b/features/space/impl/src/main/res/values-eu/translations.xml new file mode 100644 index 0000000000..6124d56327 --- /dev/null +++ b/features/space/impl/src/main/res/values-eu/translations.xml @@ -0,0 +1,5 @@ + + + "Rolak eta baimenak" + "Segurtasuna eta pribatutasuna" + diff --git a/features/space/impl/src/main/res/values-fa/translations.xml b/features/space/impl/src/main/res/values-fa/translations.xml new file mode 100644 index 0000000000..bda53d0947 --- /dev/null +++ b/features/space/impl/src/main/res/values-fa/translations.xml @@ -0,0 +1,11 @@ + + + "‏%1$s (مدیر)" + "پیش از ترک باید مدیری دیگر به این فضا تخصیص دهید." + "از اتاق(های) زیر برداشته نخواهید شد؛ چرا که تنها مدیر هستید:" + "ترک %1$s؟" + "تنها مدیر %1$s هستید" + "ترک فضا" + "نقش‌ها و اجازه‌ها" + "امنیت و محرمانگی" + diff --git a/features/space/impl/src/main/res/values-fi/translations.xml b/features/space/impl/src/main/res/values-fi/translations.xml index 501ff2f97d..e43a4ae7f9 100644 --- a/features/space/impl/src/main/res/values-fi/translations.xml +++ b/features/space/impl/src/main/res/values-fi/translations.xml @@ -1,5 +1,6 @@ + "%1$s (Ylläpitäjä)" "Poistu %1$d huoneesta ja tilasta" "Poistu %1$d huoneesta ja tilasta" @@ -9,4 +10,7 @@ "Sinua ei poisteta seuraavista huoneista, koska olet ainoa ylläpitäjä:" "Haluatko poistua tilasta %1$s?" "Olet ainoa ylläpitäjä tilassa %1$s" + "Poistu tilasta" + "Roolit ja oikeudet" + "Turvallisuus ja yksityisyys" diff --git a/features/space/impl/src/main/res/values-fr/translations.xml b/features/space/impl/src/main/res/values-fr/translations.xml index cf37795848..befd4a7c92 100644 --- a/features/space/impl/src/main/res/values-fr/translations.xml +++ b/features/space/impl/src/main/res/values-fr/translations.xml @@ -10,4 +10,7 @@ "Vous ne quitterez pas le ou les salons suivants car vous y êtes le seul administrateur:" "Quitter %1$s?" "Vous êtes le seul administrateur de %1$s" + "Quitter l’espace" + "Rôles & autorisations" + "Sécurité & confidentialité" diff --git a/features/space/impl/src/main/res/values-hr/translations.xml b/features/space/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..9babbb3d69 --- /dev/null +++ b/features/space/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,17 @@ + + + "%1$s (administrator)" + + "Napusti %1$d sobu i prostor" + "Napusti %1$d sobe i prostor" + "Napusti %1$d soba i prostor" + + "Odaberite sobe koje želite napustiti, a za koje niste jedini administrator:" + "Morate dodijeliti drugog administratora za ovaj prostor prije nego što ga napustite." + "Nećete biti uklonjeni iz sljedećih soba jer ste jedini administrator:" + "Želite li napustiti %1$s?" + "Vi ste jedini administrator za %1$s" + "Napusti prostor" + "Uloge i dopuštenja" + "Sigurnost i privatnost" + diff --git a/features/space/impl/src/main/res/values-hu/translations.xml b/features/space/impl/src/main/res/values-hu/translations.xml index 79a8733df2..3ddbe6c822 100644 --- a/features/space/impl/src/main/res/values-hu/translations.xml +++ b/features/space/impl/src/main/res/values-hu/translations.xml @@ -6,5 +6,11 @@ "%1$d szoba és tér elhagyása" "Ez a tér összes szobájából is eltávolítja." + "Mielőtt elhagyhatná ezt a teret, ki kell jelölnie egy másik adminisztrátort." + "Nem lesz eltávolítva a következő szobá(k)ból, mert ön az egyetlen adminisztrátor:" "Kilép innen: %1$s?" + "Ön az egyetlen adminisztrátor itt: %1$s" + "Tér elhagyása" + "Szerepkörök és jogosultságok" + "Biztonság és adatvédelem" diff --git a/features/space/impl/src/main/res/values-in/translations.xml b/features/space/impl/src/main/res/values-in/translations.xml new file mode 100644 index 0000000000..d505c16f38 --- /dev/null +++ b/features/space/impl/src/main/res/values-in/translations.xml @@ -0,0 +1,5 @@ + + + "Peran dan perizinan" + "Keamanan & privasi" + diff --git a/features/space/impl/src/main/res/values-it/translations.xml b/features/space/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..e483f98513 --- /dev/null +++ b/features/space/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,16 @@ + + + "%1$s (Amministratore)" + + "Lascia %1$d stanza e spazio" + "Lascia %1$d stanze e spazi" + + "Seleziona le stanze che desideri abbandonare e di cui non sei l\'unico amministratore:" + "Prima di poter uscire, devi assegnare un altro amministratore a questo spazio." + "Non verrai rimosso dalle seguenti stanze perché sei l\'unico amministratore:" + "Uscire da %1$s?" + "Sei l\'unico amministratore di %1$s" + "Esci dallo spazio" + "Ruoli e autorizzazioni" + "Sicurezza e privacy" + diff --git a/features/space/impl/src/main/res/values-ka/translations.xml b/features/space/impl/src/main/res/values-ka/translations.xml new file mode 100644 index 0000000000..6674642954 --- /dev/null +++ b/features/space/impl/src/main/res/values-ka/translations.xml @@ -0,0 +1,4 @@ + + + "როლები და ნებართვები" + diff --git a/features/space/impl/src/main/res/values-ko/translations.xml b/features/space/impl/src/main/res/values-ko/translations.xml new file mode 100644 index 0000000000..7855147bb7 --- /dev/null +++ b/features/space/impl/src/main/res/values-ko/translations.xml @@ -0,0 +1,5 @@ + + + "역할 및 권한" + "보안 및 개인정보 보호" + diff --git a/features/space/impl/src/main/res/values-nb/translations.xml b/features/space/impl/src/main/res/values-nb/translations.xml index aafe1d756f..0e0709f80e 100644 --- a/features/space/impl/src/main/res/values-nb/translations.xml +++ b/features/space/impl/src/main/res/values-nb/translations.xml @@ -1,9 +1,16 @@ "%1$s (Admin)" + + "Forlat %1$d rom og område" + "Forlat %1$d rom og område" + "Velg rommene du vil forlate, som du ikke er den eneste administratoren for:" "Du må tildele en annen administrator for dette området før du kan forlate det." "Du vil ikke bli fjernet fra følgende rom fordi du er den eneste administratoren:" "Forlat %1$s?" "Du er den eneste administratoren for %1$s" + "Forlat område" + "Roller og tillatelser" + "Sikkerhet og personvern" diff --git a/features/space/impl/src/main/res/values-nl/translations.xml b/features/space/impl/src/main/res/values-nl/translations.xml new file mode 100644 index 0000000000..e5b9cf7cbe --- /dev/null +++ b/features/space/impl/src/main/res/values-nl/translations.xml @@ -0,0 +1,4 @@ + + + "Rollen en rechten" + diff --git a/features/space/impl/src/main/res/values-pl/translations.xml b/features/space/impl/src/main/res/values-pl/translations.xml new file mode 100644 index 0000000000..0c34e56377 --- /dev/null +++ b/features/space/impl/src/main/res/values-pl/translations.xml @@ -0,0 +1,17 @@ + + + "%1$s (Administrator)" + + "Opuść %1$d pokój i przestrzeń" + "Opuść %1$d pokoje i przestrzeń" + "Opuść %1$d pokojów i przestrzeń" + + "Wybierz pokoje, które chcesz opuścić, a których nie jesteś jedynym administratorem:" + "Aby opuścić tę przestrzeń, musisz przypisać do niej innego administratora." + "Nie zostaniesz usunięty z następujących pokoi, ponieważ jesteś ich jedynym administratorem:" + "Opuścić %1$s?" + "Jesteś jedynym administratorem %1$s" + "Opuść przestrzeń" + "Role i uprawnienia" + "Bezpieczeństwo i prywatność" + diff --git a/features/space/impl/src/main/res/values-pt-rBR/translations.xml b/features/space/impl/src/main/res/values-pt-rBR/translations.xml new file mode 100644 index 0000000000..3329be1097 --- /dev/null +++ b/features/space/impl/src/main/res/values-pt-rBR/translations.xml @@ -0,0 +1,16 @@ + + + "%1$s (Administrador)" + + "Sair de %1$d sala e espaço" + "Sair de %1$d salas e do espaço" + + "Selecione as salas que gostaria de sair nas quais você não é o único administrador:" + "Você precisa atribuir outro administrador para este espaço antes de sair." + "Você não será removido das seguintes salas porque você é o único administrador:" + "Sair de %1$s?" + "Você é o único administrador de %1$s" + "Sair do espaço" + "Cargos e permissões" + "Segurança e privacidade" + diff --git a/features/space/impl/src/main/res/values-pt/translations.xml b/features/space/impl/src/main/res/values-pt/translations.xml index 75e1f5431e..71d4836588 100644 --- a/features/space/impl/src/main/res/values-pt/translations.xml +++ b/features/space/impl/src/main/res/values-pt/translations.xml @@ -1,9 +1,13 @@ + "%1$s (admin)" "Sair do espaço e de %1$d sala" "Sair do espaço e de %1$d salas" "Também irás sair de todas as salas deste espaço." "Sair de %1$s?" + "Sair do espaço" + "Cargos e permissões" + "Segurança e privacidade" diff --git a/features/space/impl/src/main/res/values-ro/translations.xml b/features/space/impl/src/main/res/values-ro/translations.xml index 6ff2a5dfa8..588518a249 100644 --- a/features/space/impl/src/main/res/values-ro/translations.xml +++ b/features/space/impl/src/main/res/values-ro/translations.xml @@ -11,4 +11,7 @@ "Nu veți părăsi următoarele camere deoarece sunteți singurul administrator:" "Părăsiți %1$s?" "Sunteți singurul administrator pentru %1$s" + "Părăsiți spațiul" + "Roluri și permisiuni" + "Securitate & confidențialitate" diff --git a/features/space/impl/src/main/res/values-ru/translations.xml b/features/space/impl/src/main/res/values-ru/translations.xml index 0df1b9224a..47cd467725 100644 --- a/features/space/impl/src/main/res/values-ru/translations.xml +++ b/features/space/impl/src/main/res/values-ru/translations.xml @@ -11,4 +11,7 @@ "Вы не будете удалены из следующих комнат, поскольку вы являетесь единственным администратором:" "Выйти из %1$s?" "Вы единственный администратор для %1$s" + "Покинуть пространство" + "Роли и разрешения" + "Безопасность и конфиденциальность" diff --git a/features/space/impl/src/main/res/values-sk/translations.xml b/features/space/impl/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..2fd11ba58b --- /dev/null +++ b/features/space/impl/src/main/res/values-sk/translations.xml @@ -0,0 +1,17 @@ + + + "%1$s (Správca)" + + "Opustiť %1$d miestnosť a priestor" + "Opustiť %1$d miestnosti a priestory" + "Opustiť %1$d miestností a priestorov" + + "Vyberte miestnosti, ktoré chcete opustiť a pre ktoré nie ste jediným správcom:" + "Pred odchodom musíte pre tento priestor určiť iného správcu." + "Z nasledujúcich miestností nebudete odstránený/á, pretože ste jediným správcom:" + "Opustiť %1$s?" + "Ste jediným administrátorom pre %1$s" + "Opustiť priestor" + "Roly a povolenia" + "Bezpečnosť a súkromie" + diff --git a/features/space/impl/src/main/res/values-sv/translations.xml b/features/space/impl/src/main/res/values-sv/translations.xml new file mode 100644 index 0000000000..d2dcf89347 --- /dev/null +++ b/features/space/impl/src/main/res/values-sv/translations.xml @@ -0,0 +1,5 @@ + + + "Roller och behörigheter" + "Säkerhet och sekretess" + diff --git a/features/space/impl/src/main/res/values-tr/translations.xml b/features/space/impl/src/main/res/values-tr/translations.xml new file mode 100644 index 0000000000..99e4a986fa --- /dev/null +++ b/features/space/impl/src/main/res/values-tr/translations.xml @@ -0,0 +1,5 @@ + + + "Roller ve izinler" + "Güvenlik ve gizlilik" + diff --git a/features/space/impl/src/main/res/values-uk/translations.xml b/features/space/impl/src/main/res/values-uk/translations.xml new file mode 100644 index 0000000000..d7652641bb --- /dev/null +++ b/features/space/impl/src/main/res/values-uk/translations.xml @@ -0,0 +1,5 @@ + + + "Ролі та дозволи" + "Безпека й приватність" + diff --git a/features/space/impl/src/main/res/values-ur/translations.xml b/features/space/impl/src/main/res/values-ur/translations.xml new file mode 100644 index 0000000000..827417fb44 --- /dev/null +++ b/features/space/impl/src/main/res/values-ur/translations.xml @@ -0,0 +1,4 @@ + + + "کردارہا اور اجازتیں" + diff --git a/features/space/impl/src/main/res/values-uz/translations.xml b/features/space/impl/src/main/res/values-uz/translations.xml new file mode 100644 index 0000000000..83540091ca --- /dev/null +++ b/features/space/impl/src/main/res/values-uz/translations.xml @@ -0,0 +1,5 @@ + + + "Rollar va ruxsatlar" + "Xavfsizlik va maxfiylik" + diff --git a/features/space/impl/src/main/res/values-zh-rTW/translations.xml b/features/space/impl/src/main/res/values-zh-rTW/translations.xml index 419ea40233..abf495860f 100644 --- a/features/space/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/space/impl/src/main/res/values-zh-rTW/translations.xml @@ -1,5 +1,15 @@ + "%1$s(管理員)" + + "離開 %1$d 個聊天室與空間" + "這也會將您從此空間中的所有聊天室移除。" + "您必須為此空間另外指定一位管理員後才能離開。" + "您不會被從以下聊天室移除,因為您是唯一的管理員:" "離開 %1$s?" + "您是 %1$s 唯一的管理員" + "離開空間" + "角色與權限" + "安全與隱私" diff --git a/features/space/impl/src/main/res/values-zh/translations.xml b/features/space/impl/src/main/res/values-zh/translations.xml index 4e95141bad..f0afff02f4 100644 --- a/features/space/impl/src/main/res/values-zh/translations.xml +++ b/features/space/impl/src/main/res/values-zh/translations.xml @@ -9,4 +9,7 @@ "您不会从以下房间中被移除,因为您是唯一的管理员:" "离开%1$s?" "您是 %1$s 的唯一管理员" + "离开空间" + "角色与权限" + "安全与隐私" diff --git a/features/space/impl/src/main/res/values/localazy.xml b/features/space/impl/src/main/res/values/localazy.xml index c6ced29d41..a4df5e767d 100644 --- a/features/space/impl/src/main/res/values/localazy.xml +++ b/features/space/impl/src/main/res/values/localazy.xml @@ -10,4 +10,7 @@ "You will not be removed from the following room(s) because you\'re the only administrator:" "Leave %1$s?" "You are the only admin for %1$s" + "Leave space" + "Roles & permissions" + "Security & privacy" diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt index 17823ba72b..0e2717c055 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/DefaultSpaceEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,6 +16,7 @@ import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.features.space.impl.di.FakeSpaceFlowGraph import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.spaces.FakeSpaceRoomList import io.element.android.libraries.matrix.test.spaces.FakeSpaceService import io.element.android.tests.testutils.lambda.lambdaError @@ -40,16 +42,20 @@ class DefaultSpaceEntryPointTest { spaceService = FakeSpaceService( spaceRoomListResult = { _: RoomId -> FakeSpaceRoomList(A_ROOM_ID) } ), + room = FakeJoinedRoom(), graphFactory = FakeSpaceFlowGraph.Factory ) } val callback = object : SpaceEntryPoint.Callback { - override fun onOpenRoom(roomId: RoomId, viaParameters: List) = lambdaError() + override fun navigateToRoom(roomId: RoomId, viaParameters: List) = lambdaError() + override fun navigateToRoomMemberList() = lambdaError() } - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .inputs(nodeInputs) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + inputs = nodeInputs, + callback = callback, + ) assertThat(result).isInstanceOf(SpaceFlowNode::class.java) assertThat(result.plugins).contains(nodeInputs) assertThat(result.plugins).contains(callback) diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/di/FakeSpaceFlowGraph.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/di/FakeSpaceFlowGraph.kt index 09263ff52d..436e9ac3b9 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/di/FakeSpaceFlowGraph.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/di/FakeSpaceFlowGraph.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt index 3d123f3f41..495f0b38bd 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -61,7 +62,7 @@ class LeaveSpacePresenterTest { val state = awaitItem() assertThat(state.selectableSpaceRooms.isLoading()).isTrue() assertThat(state.leaveSpaceAction).isEqualTo(AsyncAction.Uninitialized) - skipItems(3) + skipItems(2) val stateError = awaitItem() assertThat(stateError.selectableSpaceRooms.isFailure()).isTrue() // Retry @@ -83,7 +84,7 @@ class LeaveSpacePresenterTest { presenter.test { val state = awaitItem() assertThat(state.spaceName).isNull() - skipItems(3) + skipItems(2) val finalState = awaitItem() assertThat(finalState.spaceName).isEqualTo(A_SPACE_NAME) assertThat(finalState.isLastAdmin).isTrue() @@ -119,7 +120,7 @@ class LeaveSpacePresenterTest { presenter.test { val state = awaitItem() assertThat(state.spaceName).isNull() - skipItems(3) + skipItems(2) val finalState = awaitItem() // The current state is not in the sub room list assertThat(finalState.selectableSpaceRooms.dataOrNull()!!.map { it.spaceRoom.roomId }).containsExactly(A_ROOM_ID, A_ROOM_ID_3) @@ -153,7 +154,7 @@ class LeaveSpacePresenterTest { ) ) presenter.test { - skipItems(4) + skipItems(3) val state = awaitItem() assertThat(state.spaceName).isNull() assertThat(state.isLastAdmin).isFalse() @@ -217,7 +218,7 @@ class LeaveSpacePresenterTest { ) ) presenter.test { - skipItems(4) + skipItems(3) val state = awaitItem() state.eventSink(LeaveSpaceEvents.LeaveSpace) val stateLeaving = awaitItem() diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceStateTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceStateTest.kt index 998868593f..d3b3f44398 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceStateTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/leave/LeaveSpaceStateTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpacePresenterTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpacePresenterTest.kt index fbef7bb3a1..917aceb262 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpacePresenterTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpacePresenterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,9 +19,12 @@ import io.element.android.features.invite.api.toInviteData import io.element.android.features.invite.test.InMemorySeenInvitesStore import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomIdOrAlias import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias +import io.element.android.libraries.matrix.api.room.BaseRoom import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.join.JoinRoom import io.element.android.libraries.matrix.api.spaces.SpaceRoomList @@ -28,7 +32,9 @@ import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_ID_2 import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.join.FakeJoinRoom +import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.libraries.matrix.test.spaces.FakeSpaceRoomList import io.element.android.libraries.previewutils.room.aSpaceRoom import io.element.android.tests.testutils.EventsRecorder @@ -61,11 +67,39 @@ class SpacePresenterTest { assertThat(state.joinActions).isEmpty() assertThat(state.acceptDeclineInviteState).isEqualTo(anAcceptDeclineInviteState()) assertThat(state.topicViewerState).isEqualTo(TopicViewerState.Hidden) + assertThat(state.canAccessSpaceSettings).isFalse() advanceUntilIdle() paginateResult.assertions().isCalledOnce() } } + @Test + fun `present - canAccessSpaceSettings false when space settings ff is enabled but no permissions`() = runTest { + val presenter = createSpacePresenter(spaceSettingsEnabled = true) + presenter.test { + val state = awaitItem() + assertThat(state.canAccessSpaceSettings).isFalse() + } + } + + @Test + fun `present - canAccessSpaceSettings true when space settings ff is enabled and has permissions`() = runTest { + val room = FakeBaseRoom( + roomPermissions = FakeRoomPermissions( + canSendState = { true } + ) + ) + val presenter = createSpacePresenter( + room = room, + spaceSettingsEnabled = true, + ) + presenter.test { + skipItems(1) + val state = awaitItem() + assertThat(state.canAccessSpaceSettings).isTrue() + } + } + @Test fun `present - load more`() = runTest { val paginateResult = lambdaRecorder> { @@ -321,20 +355,30 @@ class SpacePresenterTest { private fun TestScope.createSpacePresenter( client: MatrixClient = FakeMatrixClient(), - spaceRoomList: SpaceRoomList = FakeSpaceRoomList(), + room: BaseRoom = FakeBaseRoom(), + spaceRoomList: SpaceRoomList = FakeSpaceRoomList( + paginateResult = { Result.success(Unit) } + ), seenInvitesStore: SeenInvitesStore = InMemorySeenInvitesStore(), joinRoom: JoinRoom = FakeJoinRoom( lambda = { _, _, _ -> Result.success(Unit) }, ), acceptDeclineInvitePresenter: Presenter = Presenter { anAcceptDeclineInviteState() }, + spaceSettingsEnabled: Boolean = false, ): SpacePresenter { return SpacePresenter( client = client, + room = room, spaceRoomList = spaceRoomList, seenInvitesStore = seenInvitesStore, joinRoom = joinRoom, acceptDeclineInvitePresenter = acceptDeclineInvitePresenter, sessionCoroutineScope = backgroundScope, + featureFlagService = FakeFeatureFlagService( + initialState = mapOf( + FeatureFlags.SpaceSettings.key to spaceSettingsEnabled, + ) + ), ) } } diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceStateTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceStateTest.kt index d036d7023c..440ec1b6a5 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceStateTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceStateTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceViewTest.kt b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceViewTest.kt index 2702133780..406b5d17e8 100644 --- a/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceViewTest.kt +++ b/features/space/impl/src/test/kotlin/io/element/android/features/space/impl/root/SpaceViewTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -139,6 +140,8 @@ private fun AndroidComposeTestRule.setSpace onRoomClick: (SpaceRoom) -> Unit = EnsureNeverCalledWithParam(), onShareSpace: () -> Unit = EnsureNeverCalled(), onLeaveSpaceClick: () -> Unit = EnsureNeverCalled(), + onSettingsClick: () -> Unit = EnsureNeverCalled(), + onViewMembersClick: () -> Unit = EnsureNeverCalled(), acceptDeclineInviteView: @Composable () -> Unit = {}, ) { setContent { @@ -148,6 +151,8 @@ private fun AndroidComposeTestRule.setSpace onRoomClick = onRoomClick, onShareSpace = onShareSpace, onLeaveSpaceClick = onLeaveSpaceClick, + onSettingsClick = onSettingsClick, + onViewMembersClick = onViewMembersClick, acceptDeclineInviteView = acceptDeclineInviteView, ) } diff --git a/features/startchat/api/build.gradle.kts b/features/startchat/api/build.gradle.kts index 77822f1a15..890ae26e81 100644 --- a/features/startchat/api/build.gradle.kts +++ b/features/startchat/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/features/startchat/api/src/main/kotlin/io/element/android/features/startchat/api/ConfirmingStartDmWithMatrixUser.kt b/features/startchat/api/src/main/kotlin/io/element/android/features/startchat/api/ConfirmingStartDmWithMatrixUser.kt index 192c32fee7..5bf015c0f0 100644 --- a/features/startchat/api/src/main/kotlin/io/element/android/features/startchat/api/ConfirmingStartDmWithMatrixUser.kt +++ b/features/startchat/api/src/main/kotlin/io/element/android/features/startchat/api/ConfirmingStartDmWithMatrixUser.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/api/src/main/kotlin/io/element/android/features/startchat/api/StartChatEntryPoint.kt b/features/startchat/api/src/main/kotlin/io/element/android/features/startchat/api/StartChatEntryPoint.kt index 17b9b902e2..b560cda240 100644 --- a/features/startchat/api/src/main/kotlin/io/element/android/features/startchat/api/StartChatEntryPoint.kt +++ b/features/startchat/api/src/main/kotlin/io/element/android/features/startchat/api/StartChatEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,14 +15,14 @@ import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.matrix.api.core.RoomIdOrAlias interface StartChatEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - interface NodeBuilder { - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: Callback, + ): Node interface Callback : Plugin { - fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List) - fun onOpenRoomDirectory() + fun onRoomCreated(roomIdOrAlias: RoomIdOrAlias, serverNames: List) + fun navigateToRoomDirectory() } } diff --git a/features/startchat/api/src/main/kotlin/io/element/android/features/startchat/api/StartDMAction.kt b/features/startchat/api/src/main/kotlin/io/element/android/features/startchat/api/StartDMAction.kt index b49a9fbdfd..6aa2ad5d98 100644 --- a/features/startchat/api/src/main/kotlin/io/element/android/features/startchat/api/StartDMAction.kt +++ b/features/startchat/api/src/main/kotlin/io/element/android/features/startchat/api/StartDMAction.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/build.gradle.kts b/features/startchat/impl/build.gradle.kts index 8ba7593b36..6ab1a361e9 100644 --- a/features/startchat/impl/build.gradle.kts +++ b/features/startchat/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -51,6 +52,7 @@ dependencies { testImplementation(projects.libraries.mediaupload.test) testImplementation(projects.libraries.permissions.test) testImplementation(projects.libraries.usersearch.test) + testImplementation(projects.features.createroom.test) testImplementation(projects.features.startchat.test) testImplementation(projects.libraries.featureflag.test) } diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/StartChatNavigator.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/StartChatNavigator.kt index a45b4dddab..125dc2c2b3 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/StartChatNavigator.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/StartChatNavigator.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,7 +18,7 @@ import io.element.android.libraries.architecture.overlay.operation.show import io.element.android.libraries.matrix.api.core.RoomIdOrAlias interface StartChatNavigator : Plugin { - fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List) + fun onRoomCreated(roomIdOrAlias: RoomIdOrAlias, serverNames: List) fun onCreateNewRoom() fun onShowJoinRoomByAddress() fun onDismissJoinRoomByAddress() @@ -30,7 +31,8 @@ class DefaultStartChatNavigator( private val openRoom: (RoomIdOrAlias, List) -> Unit, private val openRoomDirectory: () -> Unit, ) : StartChatNavigator { - override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List) = openRoom(roomIdOrAlias, serverNames) + override fun onRoomCreated(roomIdOrAlias: RoomIdOrAlias, serverNames: List) = + openRoom(roomIdOrAlias, serverNames) override fun onOpenRoomDirectory() = openRoomDirectory() diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/DefaultStartChatEntryPoint.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/DefaultStartChatEntryPoint.kt index c33ac4356f..6e70153dc1 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/DefaultStartChatEntryPoint.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/DefaultStartChatEntryPoint.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,28 +10,18 @@ package io.element.android.features.startchat.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.startchat.api.StartChatEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultStartChatEntryPoint : StartChatEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): StartChatEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : StartChatEntryPoint.NodeBuilder { - override fun callback(callback: StartChatEntryPoint.Callback): StartChatEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: StartChatEntryPoint.Callback, + ): Node { + return parentNode.createNode(buildContext, listOf(callback)) } } diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/DefaultStartDMAction.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/DefaultStartDMAction.kt index 6847b1859d..a484fe2e72 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/DefaultStartDMAction.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/DefaultStartDMAction.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.features.startchat.impl import androidx.compose.runtime.MutableState import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import im.vector.app.features.analytics.plan.CreatedRoom import io.element.android.features.startchat.api.ConfirmingStartDmWithMatrixUser import io.element.android.features.startchat.api.StartDMAction @@ -23,7 +23,6 @@ import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.services.analytics.api.AnalyticsService @ContributesBinding(SessionScope::class) -@Inject class DefaultStartDMAction( private val matrixClient: MatrixClient, private val analyticsService: AnalyticsService, diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/StartChatFlowNode.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/StartChatFlowNode.kt index 30d1f3a2cf..236d92fd4a 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/StartChatFlowNode.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/StartChatFlowNode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,7 +17,6 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.navigation.transition.JumpToEndTransitionHandler import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject @@ -29,6 +29,7 @@ import io.element.android.features.startchat.impl.root.StartChatNode import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.OverlayView +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.RoomId @@ -60,15 +61,12 @@ class StartChatFlowNode( data object JoinByAddress : NavTarget } + private val callback: StartChatEntryPoint.Callback = callback() private val navigator = DefaultStartChatNavigator( backstack = backstack, overlay = overlay, - openRoom = { roomIdOrAlias, viaServers -> - plugins().forEach { it.onOpenRoom(roomIdOrAlias, viaServers) } - }, - openRoomDirectory = { - plugins().forEach { it.onOpenRoomDirectory() } - } + openRoom = callback::onRoomCreated, + openRoomDirectory = callback::navigateToRoomDirectory, ) override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { @@ -79,12 +77,14 @@ class StartChatFlowNode( NavTarget.NewRoom -> { val callback = object : CreateRoomEntryPoint.Callback { override fun onRoomCreated(roomId: RoomId) { - navigator.onOpenRoom(roomId.toRoomIdOrAlias(), emptyList()) + navigator.onRoomCreated(roomId.toRoomIdOrAlias(), emptyList()) } } - createRoomEntryPoint.nodeBuilder(parentNode = this, buildContext = buildContext) - .callback(callback) - .build() + createRoomEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + callback = callback, + ) } NavTarget.JoinByAddress -> { createNode(buildContext = buildContext, plugins = listOf(navigator)) diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/SearchMultipleUsersResultItem.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/SearchMultipleUsersResultItem.kt index 867ac9d918..622a0ac71f 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/SearchMultipleUsersResultItem.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/SearchMultipleUsersResultItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/SearchSingleUserResultItem.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/SearchSingleUserResultItem.kt index bd94d136af..448ed86203 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/SearchSingleUserResultItem.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/SearchSingleUserResultItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/SearchUserBar.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/SearchUserBar.kt index 3d42c40067..a664ad0912 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/SearchUserBar.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/SearchUserBar.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/UserListView.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/UserListView.kt index 753bebec83..f35c6282ed 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/UserListView.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/components/UserListView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressEvents.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressEvents.kt index 648146fbd7..4a5c2de972 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressEvents.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressNode.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressNode.kt index 101958ddad..c81cb1af66 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressNode.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressNode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressPresenter.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressPresenter.kt index 540c1a4784..bda1e054e4 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressPresenter.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressPresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -48,7 +49,7 @@ class JoinRoomByAddressPresenter( var internalAddressState by remember { mutableStateOf(RoomAddressState.Unknown) } var validateAddress: Boolean by remember { mutableStateOf(false) } - fun handleEvents(event: JoinRoomByAddressEvents) { + fun handleEvent(event: JoinRoomByAddressEvents) { when (event) { JoinRoomByAddressEvents.Continue -> { when (val currentState = internalAddressState) { @@ -88,13 +89,13 @@ class JoinRoomByAddressPresenter( return JoinRoomByAddressState( address = address, addressState = addressState, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } private fun onRoomFound(state: RoomAddressState.RoomFound) { navigator.onDismissJoinRoomByAddress() - navigator.onOpenRoom( + navigator.onRoomCreated( roomIdOrAlias = state.resolved.roomId.toRoomIdOrAlias(), serverNames = state.resolved.servers ) diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressState.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressState.kt index 84b01ac263..4749b14b22 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressState.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressStateProvider.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressStateProvider.kt index 847ca78837..eb1305eb80 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressStateProvider.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressView.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressView.kt index 2d34dcc1cf..0627a78da0 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressView.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinRoomByAddressView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatEvents.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatEvents.kt index 6ea72d8b05..66e135b45c 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatEvents.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatNode.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatNode.kt index 9a9ca85160..60ef9ee00f 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatNode.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatNode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -53,7 +54,7 @@ class StartChatNode( onCloseClick = this::navigateUp, onNewRoomClick = navigator::onCreateNewRoom, onOpenDM = { - navigator.onOpenRoom(roomIdOrAlias = it.toRoomIdOrAlias(), serverNames = emptyList()) + navigator.onRoomCreated(roomIdOrAlias = it.toRoomIdOrAlias(), serverNames = emptyList()) }, onJoinByAddressClick = navigator::onShowJoinRoomByAddress, onInviteFriendsClick = { invitePeople(activity) }, diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenter.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenter.kt index 10a9745f32..e176f202ad 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenter.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -57,7 +58,7 @@ class StartChatPresenter( featureFlagService.isFeatureEnabledFlow(FeatureFlags.RoomDirectorySearch) }.collectAsState(initial = false) - fun handleEvents(event: StartChatEvents) { + fun handleEvent(event: StartChatEvents) { when (event) { is StartChatEvents.StartDM -> localCoroutineScope.launch { startDMAction.execute( @@ -75,7 +76,7 @@ class StartChatPresenter( userListState = userListState, startDmAction = startDmActionState.value, isRoomDirectorySearchEnabled = isRoomDirectorySearchEnabled, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } } diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatState.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatState.kt index 724d6e5e88..65f977d3e3 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatState.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatStateProvider.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatStateProvider.kt index 6b5113cc94..1c82ae373e 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatStateProvider.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatView.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatView.kt index ebde080f75..ca308e4815 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatView.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/root/StartChatView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenter.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenter.kt index 38d15f6de3..4e85dd0353 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenter.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -75,6 +76,15 @@ class DefaultUserListPresenter( }.launchIn(this) } + fun handleEvent(event: UserListEvents) { + when (event) { + is UserListEvents.OnSearchActiveChanged -> isSearchActive = event.active + is UserListEvents.UpdateSearchQuery -> searchQuery = event.query + is UserListEvents.AddToSelection -> userListDataStore.selectUser(event.matrixUser) + is UserListEvents.RemoveFromSelection -> userListDataStore.removeUserFromSelection(event.matrixUser) + } + } + return UserListState( searchQuery = searchQuery, searchResults = searchResults, @@ -83,14 +93,7 @@ class DefaultUserListPresenter( showSearchLoader = showSearchLoader, selectionMode = args.selectionMode, recentDirectRooms = recentDirectRooms.toImmutableList(), - eventSink = { event -> - when (event) { - is UserListEvents.OnSearchActiveChanged -> isSearchActive = event.active - is UserListEvents.UpdateSearchQuery -> searchQuery = event.query - is UserListEvents.AddToSelection -> userListDataStore.selectUser(event.matrixUser) - is UserListEvents.RemoveFromSelection -> userListDataStore.removeUserFromSelection(event.matrixUser) - } - }, + eventSink = ::handleEvent, ) } } diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListDataStore.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListDataStore.kt index 99fc19f6e1..f7ca98a7e6 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListDataStore.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListDataStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListEvents.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListEvents.kt index 794b2c18af..99e910b89f 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListEvents.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListPresenter.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListPresenter.kt index a07b39a1e7..797938b003 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListPresenter.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListPresenterArgs.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListPresenterArgs.kt index d7e6a727f8..d1dbf8a382 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListPresenterArgs.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListPresenterArgs.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListState.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListState.kt index 2186bcb57a..33b74d240c 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListState.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListStateProvider.kt b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListStateProvider.kt index 65b4efdadb..cd43f96688 100644 --- a/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListStateProvider.kt +++ b/features/startchat/impl/src/main/kotlin/io/element/android/features/startchat/impl/userlist/UserListStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/main/res/values-hr/translations.xml b/features/startchat/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..8f0407fcde --- /dev/null +++ b/features/startchat/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,12 @@ + + + "Nova soba" + "Direktorij soba" + "Došlo je do pogreške prilikom pokretanja razgovora" + "Pridruži se sobi putem adrese" + "Adresa nije valjana" + "Unos…" + "Pronađena je odgovarajuća soba" + "Soba nije pronađena" + "npr. #naziv-sobe:matrix.org" + diff --git a/features/startchat/impl/src/main/res/values-ru/translations.xml b/features/startchat/impl/src/main/res/values-ru/translations.xml index 6d676d3c76..6b148a7a4c 100644 --- a/features/startchat/impl/src/main/res/values-ru/translations.xml +++ b/features/startchat/impl/src/main/res/values-ru/translations.xml @@ -2,7 +2,7 @@ "Создать новую комнату" "Каталог комнат" - "Произошла ошибка при запуске чата" + "Произошла ошибка при попытке начать чат" "Присоединиться к комнате по адресу" "Недействительный адрес" "Ввести…" diff --git a/features/startchat/impl/src/main/res/values-uz/translations.xml b/features/startchat/impl/src/main/res/values-uz/translations.xml index a789abc565..b2523ab7bd 100644 --- a/features/startchat/impl/src/main/res/values-uz/translations.xml +++ b/features/startchat/impl/src/main/res/values-uz/translations.xml @@ -3,4 +3,10 @@ "Yangi xona" "Xona katalogi" "Suhbatni boshlashda xatolik yuz berdi" + "Xonaga manzil orqali kirish" + "Yaroqli manzil emas" + "Kirish…" + "Mos xona topildi" + "Xona topilmadi" + "masalan #xona-nomi:matrix.org" diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/DefaultStartChatEntryPointTest.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/DefaultStartChatEntryPointTest.kt index 8f4a41a3fa..b00a595747 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/DefaultStartChatEntryPointTest.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/DefaultStartChatEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,10 +10,9 @@ package io.element.android.features.startchat.impl import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node import com.bumble.appyx.testing.junit4.util.MainDispatcherRule import com.google.common.truth.Truth.assertThat -import io.element.android.features.createroom.api.CreateRoomEntryPoint +import io.element.android.features.createroom.api.FakeCreateRoomEntryPoint import io.element.android.features.startchat.api.StartChatEntryPoint import io.element.android.libraries.matrix.api.core.RoomIdOrAlias import io.element.android.tests.testutils.lambda.lambdaError @@ -34,18 +34,18 @@ class DefaultStartChatEntryPointTest { StartChatFlowNode( buildContext = buildContext, plugins = plugins, - createRoomEntryPoint = object : CreateRoomEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext) = lambdaError() - }, + createRoomEntryPoint = FakeCreateRoomEntryPoint(), ) } val callback = object : StartChatEntryPoint.Callback { - override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List) = lambdaError() - override fun onOpenRoomDirectory() = lambdaError() + override fun onRoomCreated(roomIdOrAlias: RoomIdOrAlias, serverNames: List) = lambdaError() + override fun navigateToRoomDirectory() = lambdaError() } - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + callback = callback, + ) assertThat(result).isInstanceOf(StartChatFlowNode::class.java) assertThat(result.plugins).contains(callback) } diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/DefaultStartDMActionTest.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/DefaultStartDMActionTest.kt index fa6e140c3a..122775f2cc 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/DefaultStartDMActionTest.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/DefaultStartDMActionTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/FakeStartChatNavigator.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/FakeStartChatNavigator.kt index 9de00e0a4c..ba1b6a4e69 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/FakeStartChatNavigator.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/FakeStartChatNavigator.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,7 +18,7 @@ class FakeStartChatNavigator( private val dismissJoinRoomByAddressLambda: () -> Unit = {}, private val openRoomDirectoryLambda: () -> Unit = {}, ) : StartChatNavigator { - override fun onOpenRoom(roomIdOrAlias: RoomIdOrAlias, serverNames: List) { + override fun onRoomCreated(roomIdOrAlias: RoomIdOrAlias, serverNames: List) { openRoomLambda(roomIdOrAlias, serverNames) } diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressPresenterTest.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressPresenterTest.kt index bd40b92d57..74c43d683e 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressPresenterTest.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressPresenterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressViewTest.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressViewTest.kt index 4de9bd1470..eeea85cb63 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressViewTest.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/joinbyaddress/JoinBaseRoomByAddressViewTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenterTest.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenterTest.kt index 7340981053..7b34b8b52b 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenterTest.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatPresenterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatViewTest.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatViewTest.kt index dc213446a0..9237f3433c 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatViewTest.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/root/StartChatViewTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenterTest.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenterTest.kt index 50bbc425c2..c02b59f77a 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenterTest.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/DefaultUserListPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/FakeUserListPresenter.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/FakeUserListPresenter.kt index 84c4fd9d67..960f596b7a 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/FakeUserListPresenter.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/FakeUserListPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/FakeUserListPresenterFactory.kt b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/FakeUserListPresenterFactory.kt index 8e80626e86..d61ae25a9c 100644 --- a/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/FakeUserListPresenterFactory.kt +++ b/features/startchat/impl/src/test/kotlin/io/element/android/features/startchat/impl/userlist/FakeUserListPresenterFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/test/build.gradle.kts b/features/startchat/test/build.gradle.kts index 96b05a80fa..d6d1471832 100644 --- a/features/startchat/test/build.gradle.kts +++ b/features/startchat/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/startchat/test/src/main/kotlin/io/element/android/features/invitepeople/test/FakeStartDMAction.kt b/features/startchat/test/src/main/kotlin/io/element/android/features/invitepeople/test/FakeStartDMAction.kt index d7ab63d0e3..c4969df4d9 100644 --- a/features/startchat/test/src/main/kotlin/io/element/android/features/invitepeople/test/FakeStartDMAction.kt +++ b/features/startchat/test/src/main/kotlin/io/element/android/features/invitepeople/test/FakeStartDMAction.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/userprofile/api/build.gradle.kts b/features/userprofile/api/build.gradle.kts index 8bdaa8d77e..45278693f2 100644 --- a/features/userprofile/api/build.gradle.kts +++ b/features/userprofile/api/build.gradle.kts @@ -1,12 +1,13 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { - id("io.element.android-library") + id("io.element.android-compose-library") id("kotlin-parcelize") } diff --git a/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileEntryPoint.kt b/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileEntryPoint.kt index 70968aad61..6d02280e88 100644 --- a/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileEntryPoint.kt +++ b/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -19,14 +20,13 @@ interface UserProfileEntryPoint : FeatureEntryPoint { data class Params(val userId: UserId) : NodeInputs interface Callback : Plugin { - fun onOpenRoom(roomId: RoomId) + fun navigateToRoom(roomId: RoomId) } - interface NodeBuilder { - fun params(params: Params): NodeBuilder - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } - - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder + fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: Params, + callback: Callback, + ): Node } diff --git a/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileEvents.kt b/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileEvents.kt index 4a5f6bb415..55c9c40f74 100644 --- a/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileEvents.kt +++ b/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfilePresenterFactory.kt b/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfilePresenterFactory.kt index 593fda92f2..4bf68ae324 100644 --- a/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfilePresenterFactory.kt +++ b/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfilePresenterFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileState.kt b/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileState.kt index b9f08a27f3..0e0016ee14 100644 --- a/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileState.kt +++ b/features/userprofile/api/src/main/kotlin/io/element/android/features/userprofile/api/UserProfileState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/userprofile/impl/build.gradle.kts b/features/userprofile/impl/build.gradle.kts index f0c214c22e..0b65441cc3 100644 --- a/features/userprofile/impl/build.gradle.kts +++ b/features/userprofile/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -44,6 +45,9 @@ dependencies { testCommonDependencies(libs, true) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.mediaviewer.test) + testImplementation(projects.features.call.test) + testImplementation(projects.features.verifysession.test) testImplementation(projects.features.startchat.test) testImplementation(projects.features.enterprise.test) } diff --git a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/DefaultUserProfileEntryPoint.kt b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/DefaultUserProfileEntryPoint.kt index bfc8a30df6..e1b15198cf 100644 --- a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/DefaultUserProfileEntryPoint.kt +++ b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/DefaultUserProfileEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,33 +10,22 @@ package io.element.android.features.userprofile.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.userprofile.api.UserProfileEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultUserProfileEntryPoint : UserProfileEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): UserProfileEntryPoint.NodeBuilder { - return object : UserProfileEntryPoint.NodeBuilder { - val plugins = ArrayList() - - override fun params(params: UserProfileEntryPoint.Params): UserProfileEntryPoint.NodeBuilder { - plugins += params - return this - } - - override fun callback(callback: UserProfileEntryPoint.Callback): UserProfileEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: UserProfileEntryPoint.Params, + callback: UserProfileEntryPoint.Callback, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf(params, callback), + ) } } diff --git a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/DefaultUserProfilePresenterFactory.kt b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/DefaultUserProfilePresenterFactory.kt index 775cdf5ff9..03d803fcd2 100644 --- a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/DefaultUserProfilePresenterFactory.kt +++ b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/DefaultUserProfilePresenterFactory.kt @@ -1,14 +1,14 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.userprofile.impl import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.userprofile.api.UserProfilePresenterFactory import io.element.android.features.userprofile.api.UserProfileState import io.element.android.features.userprofile.impl.root.UserProfilePresenter @@ -17,7 +17,6 @@ import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.UserId @ContributesBinding(SessionScope::class) -@Inject class DefaultUserProfilePresenterFactory( private val factory: UserProfilePresenter.Factory, ) : UserProfilePresenterFactory { diff --git a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/UserProfileFlowNode.kt b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/UserProfileFlowNode.kt index 5828d60c25..f95ccce528 100644 --- a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/UserProfileFlowNode.kt +++ b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/UserProfileFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,6 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push @@ -28,6 +28,7 @@ import io.element.android.features.userprofile.shared.UserProfileNodeHelper import io.element.android.features.verifysession.api.OutgoingVerificationEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope @@ -67,25 +68,26 @@ class UserProfileFlowNode( data class VerifyUser(val userId: UserId) : NavTarget } + private val callback: UserProfileEntryPoint.Callback = callback() private val inputs = inputs() override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Root -> { val callback = object : UserProfileNodeHelper.Callback { - override fun openAvatarPreview(username: String, avatarUrl: String) { + override fun navigateToAvatarPreview(username: String, avatarUrl: String) { backstack.push(NavTarget.AvatarPreview(username, avatarUrl)) } - override fun onStartDM(roomId: RoomId) { - plugins().forEach { it.onOpenRoom(roomId) } + override fun navigateToRoom(roomId: RoomId) { + callback.navigateToRoom(roomId) } - override fun onStartCall(dmRoomId: RoomId) { + override fun startCall(dmRoomId: RoomId) { elementCallEntryPoint.startCall(CallType.RoomCall(sessionId = sessionId, roomId = dmRoomId)) } - override fun onVerifyUser(userId: UserId) { + override fun startVerifyUserFlow(userId: UserId) { backstack.push(NavTarget.VerifyUser(userId)) } } @@ -98,26 +100,48 @@ class UserProfileFlowNode( backstack.pop() } - override fun onViewInTimeline(eventId: EventId) { + override fun viewInTimeline(eventId: EventId) { + // Cannot happen + } + + override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) { // Cannot happen } } - mediaViewerEntryPoint.nodeBuilder(this, buildContext) - .avatar( - filename = navTarget.name, - avatarUrl = navTarget.avatarUrl - ) - .callback(callback) - .build() + val params = mediaViewerEntryPoint.createParamsForAvatar( + filename = navTarget.name, + avatarUrl = navTarget.avatarUrl, + ) + mediaViewerEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = params, + callback = callback, + ) } is NavTarget.VerifyUser -> { val params = OutgoingVerificationEntryPoint.Params( showDeviceVerifiedScreen = false, verificationRequest = VerificationRequest.Outgoing.User(userId = navTarget.userId) ) - outgoingVerificationEntryPoint.nodeBuilder(this, buildContext) - .params(params) - .build() + outgoingVerificationEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = params, + callback = object : OutgoingVerificationEntryPoint.Callback { + override fun navigateToLearnMoreAboutEncryption() { + // No op + } + + override fun onBack() { + // No op + } + + override fun onDone() { + // No op + } + } + ) } } } diff --git a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfileNode.kt b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfileNode.kt index 735957946a..71f95a2e84 100644 --- a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfileNode.kt +++ b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfileNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -63,7 +64,7 @@ class UserProfileNode( } fun onStartDM(roomId: RoomId) { - callback.onStartDM(roomId) + callback.navigateToRoom(roomId) } val state = presenter.present() @@ -74,9 +75,9 @@ class UserProfileNode( goBack = this::navigateUp, onShareUser = ::onShareUser, onOpenDm = ::onStartDM, - onStartCall = callback::onStartCall, - openAvatarPreview = callback::openAvatarPreview, - onVerifyClick = callback::onVerifyUser, + onStartCall = callback::startCall, + openAvatarPreview = callback::navigateToAvatarPreview, + onVerifyClick = callback::startVerifyUserFlow, ) } } diff --git a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfilePresenter.kt b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfilePresenter.kt index 3f226d0213..7e09a03ec3 100644 --- a/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfilePresenter.kt +++ b/features/userprofile/impl/src/main/kotlin/io/element/android/features/userprofile/impl/root/UserProfilePresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -33,6 +34,8 @@ import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.room.powerlevels.canCall +import io.element.android.libraries.matrix.api.room.powerlevels.use import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.distinctUntilChanged @@ -55,7 +58,7 @@ class UserProfilePresenter( @Composable private fun getDmRoomId(): State { - return produceState(initialValue = null) { + return produceState(initialValue = null) { value = client.findDM(userId).getOrNull() } } @@ -65,7 +68,6 @@ class UserProfilePresenter( val isElementCallAvailable by produceState(initialValue = false, roomId) { value = sessionEnterpriseService.isElementCallAvailable() } - return produceState(initialValue = false, isElementCallAvailable, roomId) { value = when { isElementCallAvailable.not() -> false @@ -74,7 +76,7 @@ class UserProfilePresenter( roomId ?.let { client.getRoom(it) } ?.use { room -> - room.canUserJoinCall(client.sessionId).getOrNull() + room.roomPermissions().use(false) { perms -> perms.canCall() } } .orFalse() } @@ -99,7 +101,7 @@ class UserProfilePresenter( } val userProfile by produceState(null) { value = client.getProfile(userId).getOrNull() } - fun handleEvents(event: UserProfileEvents) { + fun handleEvent(event: UserProfileEvents) { when (event) { is UserProfileEvents.BlockUser -> { if (event.needsConfirmation) { @@ -151,7 +153,7 @@ class UserProfilePresenter( dmRoomId = dmRoomId, canCall = canCall, snackbarMessage = null, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/features/userprofile/impl/src/test/kotlin/io/element/android/features/userprofile/impl/DefaultUserProfileEntryPointTest.kt b/features/userprofile/impl/src/test/kotlin/io/element/android/features/userprofile/impl/DefaultUserProfileEntryPointTest.kt index 75bc434048..652bbc6d94 100644 --- a/features/userprofile/impl/src/test/kotlin/io/element/android/features/userprofile/impl/DefaultUserProfileEntryPointTest.kt +++ b/features/userprofile/impl/src/test/kotlin/io/element/android/features/userprofile/impl/DefaultUserProfileEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,19 +10,15 @@ package io.element.android.features.userprofile.impl import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node import com.bumble.appyx.testing.junit4.util.MainDispatcherRule import com.google.common.truth.Truth.assertThat -import io.element.android.features.call.api.CallType -import io.element.android.features.call.api.ElementCallEntryPoint +import io.element.android.features.call.test.FakeElementCallEntryPoint import io.element.android.features.userprofile.api.UserProfileEntryPoint -import io.element.android.features.verifysession.api.OutgoingVerificationEntryPoint -import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.features.verifysession.test.FakeOutgoingVerificationEntryPoint import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_USER_ID -import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint +import io.element.android.libraries.mediaviewer.test.FakeMediaViewerEntryPoint import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.node.TestParentNode import org.junit.Rule @@ -43,41 +40,25 @@ class DefaultUserProfileEntryPointTest { buildContext = buildContext, plugins = plugins, sessionId = A_SESSION_ID, - elementCallEntryPoint = object : ElementCallEntryPoint { - override fun startCall(callType: CallType) = lambdaError() - override suspend fun handleIncomingCall( - callType: CallType.RoomCall, - eventId: EventId, - senderId: UserId, - roomName: String?, - senderName: String?, - avatarUrl: String?, - timestamp: Long, - expirationTimestamp: Long, - notificationChannelId: String, - textContent: String? - ) = lambdaError() - }, - mediaViewerEntryPoint = object : MediaViewerEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext) = lambdaError() - }, - outgoingVerificationEntryPoint = object : OutgoingVerificationEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext) = lambdaError() - }, + elementCallEntryPoint = FakeElementCallEntryPoint(), + mediaViewerEntryPoint = FakeMediaViewerEntryPoint(), + outgoingVerificationEntryPoint = FakeOutgoingVerificationEntryPoint(), ) } val callback = object : UserProfileEntryPoint.Callback { - override fun onOpenRoom(roomId: RoomId) { + override fun navigateToRoom(roomId: RoomId) { lambdaError() } } val params = UserProfileEntryPoint.Params( userId = A_USER_ID, ) - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .params(params) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + params = params, + callback = callback, + ) assertThat(result).isInstanceOf(UserProfileFlowNode::class.java) assertThat(result.plugins).contains(params) assertThat(result.plugins).contains(callback) diff --git a/features/userprofile/impl/src/test/kotlin/io/element/android/features/userprofile/impl/UserProfilePresenterTest.kt b/features/userprofile/impl/src/test/kotlin/io/element/android/features/userprofile/impl/UserProfilePresenterTest.kt index dd937b067a..511effe750 100644 --- a/features/userprofile/impl/src/test/kotlin/io/element/android/features/userprofile/impl/UserProfilePresenterTest.kt +++ b/features/userprofile/impl/src/test/kotlin/io/element/android/features/userprofile/impl/UserProfilePresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -27,6 +28,7 @@ import io.element.android.libraries.matrix.api.MatrixClient 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.encryption.identity.IdentityState +import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_ROOM_ID @@ -35,9 +37,11 @@ import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.room.FakeBaseRoom +import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.libraries.matrix.ui.components.aMatrixUser import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.lambda.any +import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.test @@ -88,15 +92,7 @@ class UserProfilePresenterTest { @Test fun `present - canCall is false when canUserJoinCall returns false`() { testCanCall( - canUserJoinCallResult = Result.success(false), - expectedResult = false, - ) - } - - @Test - fun `present - canCall is false when canUserJoinCall fails`() { - testCanCall( - canUserJoinCallResult = Result.failure(AN_EXCEPTION), + canUserJoinCall = false, expectedResult = false, ) } @@ -127,7 +123,7 @@ class UserProfilePresenterTest { private fun testCanCall( isElementCallAvailable: Boolean = true, - canUserJoinCallResult: Result = Result.success(true), + canUserJoinCall: Boolean = true, dmRoom: RoomId? = A_ROOM_ID, canFindRoom: Boolean = true, expectedResult: Boolean, @@ -135,7 +131,14 @@ class UserProfilePresenterTest { checkThatRoomIsDestroyed: Boolean = false, ) = runTest { val room = FakeBaseRoom( - canUserJoinCallResult = { canUserJoinCallResult }, + roomPermissions = FakeRoomPermissions( + canSendState = { type -> + when (type) { + StateEventType.CallMember -> canUserJoinCall + else -> lambdaError() + } + } + ), ) val client = createFakeMatrixClient().apply { if (canFindRoom) { @@ -385,14 +388,12 @@ class UserProfilePresenterTest { } private fun createFakeMatrixClient( - isUserVerified: Boolean = true, userIdentityState: IdentityState? = null, ignoreUserResult: (UserId) -> Result = { Result.success(Unit) }, unIgnoreUserResult: (UserId) -> Result = { Result.success(Unit) }, ignoredUsersFlow: StateFlow> = MutableStateFlow(persistentListOf()) ) = FakeMatrixClient( encryptionService = FakeEncryptionService( - isUserVerifiedResult = { Result.success(isUserVerified) }, getUserIdentityResult = { Result.success(userIdentityState) } ), ignoreUserResult = ignoreUserResult, diff --git a/features/userprofile/shared/build.gradle.kts b/features/userprofile/shared/build.gradle.kts index 95f64154cf..7f4d96c883 100644 --- a/features/userprofile/shared/build.gradle.kts +++ b/features/userprofile/shared/build.gradle.kts @@ -1,9 +1,10 @@ import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileHeaderSection.kt b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileHeaderSection.kt index 415a6e3282..b27d1ad091 100644 --- a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileHeaderSection.kt +++ b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileHeaderSection.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileMainActionsSection.kt b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileMainActionsSection.kt index a06813cc02..0ed6d14aca 100644 --- a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileMainActionsSection.kt +++ b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileMainActionsSection.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileNodeHelper.kt b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileNodeHelper.kt index 61f4669769..6f2266eef2 100644 --- a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileNodeHelper.kt +++ b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileNodeHelper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -21,10 +22,10 @@ class UserProfileNodeHelper( private val userId: UserId, ) { interface Callback : NodeInputs { - fun openAvatarPreview(username: String, avatarUrl: String) - fun onStartDM(roomId: RoomId) - fun onStartCall(dmRoomId: RoomId) - fun onVerifyUser(userId: UserId) + fun navigateToAvatarPreview(username: String, avatarUrl: String) + fun navigateToRoom(roomId: RoomId) + fun startCall(dmRoomId: RoomId) + fun startVerifyUserFlow(userId: UserId) } fun onShareUser( diff --git a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileStateProvider.kt b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileStateProvider.kt index 25cb8a5df6..49a2fee4b5 100644 --- a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileStateProvider.kt +++ b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileView.kt b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileView.kt index f39cbfaf73..48f89390da 100644 --- a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileView.kt +++ b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/UserProfileView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogs.kt b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogs.kt index f6984d4858..2ba17b947b 100644 --- a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogs.kt +++ b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogs.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserSection.kt b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserSection.kt index 6446acdb0f..c3caffa7f3 100644 --- a/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserSection.kt +++ b/features/userprofile/shared/src/main/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserSection.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -69,7 +70,7 @@ private fun PreferenceBlockUser( isLoading: Boolean, eventSink: (UserProfileEvents) -> Unit, ) { - val loadingCurrentValue = @Composable { + val loadingCurrentValue = @Composable { _: Boolean -> CircularProgressIndicator( modifier = Modifier .progressSemantics() diff --git a/features/userprofile/shared/src/main/res/values-fa/translations.xml b/features/userprofile/shared/src/main/res/values-fa/translations.xml index 65664a007a..4eca6a0be9 100644 --- a/features/userprofile/shared/src/main/res/values-fa/translations.xml +++ b/features/userprofile/shared/src/main/res/values-fa/translations.xml @@ -13,5 +13,7 @@ "رفع انسداد" "قادر خواهید بود دوباره همهٔ پیام‌هایش را ببینید." "رفع انسداد کاربر" + "استفاده از کارهٔ وب برای تأیید این کاربر." + "تأیید %1$s" "هنگام تلاش برای شروع چت خطایی روی داد" diff --git a/features/userprofile/shared/src/main/res/values-hr/translations.xml b/features/userprofile/shared/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..5bfaf99a61 --- /dev/null +++ b/features/userprofile/shared/src/main/res/values-hr/translations.xml @@ -0,0 +1,19 @@ + + + "Blokiraj" + "Blokirani korisnici neće vam moći slati poruke i sve njihove poruke bit će skrivene. Možete ih odblokirati u bilo kojem trenutku." + "Blokiraj korisnika" + "Odblokiraj" + "Moći ćete ponovno vidjeti sve njihove poruke." + "Odblokiraj korisnika" + "Blokiraj" + "Blokirani korisnici neće vam moći slati poruke i sve njihove poruke bit će skrivene. Možete ih odblokirati u bilo kojem trenutku." + "Blokiraj korisnika" + "Profil" + "Odblokiraj" + "Moći ćete ponovno vidjeti sve njihove poruke." + "Odblokiraj korisnika" + "Pomoću mrežne aplikacije provjerite ovog korisnika." + "Provjeri korisnika %1$s" + "Došlo je do pogreške prilikom pokretanja razgovora" + diff --git a/features/userprofile/shared/src/main/res/values-ru/translations.xml b/features/userprofile/shared/src/main/res/values-ru/translations.xml index 4138f6471b..fde62b2a3f 100644 --- a/features/userprofile/shared/src/main/res/values-ru/translations.xml +++ b/features/userprofile/shared/src/main/res/values-ru/translations.xml @@ -15,5 +15,5 @@ "Разблокировать пользователя" "Используйте веб-приложение для проверки этого пользователя." "Верифицировать %1$s" - "Произошла ошибка при запуске чата" + "Произошла ошибка при попытке начать чат" diff --git a/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/UserProfileViewTest.kt b/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/UserProfileViewTest.kt index 0e614e53d7..dc41fb31c8 100644 --- a/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/UserProfileViewTest.kt +++ b/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/UserProfileViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogsTest.kt b/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogsTest.kt index b7d300a30e..3219658796 100644 --- a/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogsTest.kt +++ b/features/userprofile/shared/src/test/kotlin/io/element/android/features/userprofile/shared/blockuser/BlockUserDialogsTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/api/build.gradle.kts b/features/verifysession/api/build.gradle.kts index 646f817745..0f174f3e6c 100644 --- a/features/verifysession/api/build.gradle.kts +++ b/features/verifysession/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/IncomingVerificationEntryPoint.kt b/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/IncomingVerificationEntryPoint.kt index 9d90f33e8d..0330bf47fc 100644 --- a/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/IncomingVerificationEntryPoint.kt +++ b/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/IncomingVerificationEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -19,13 +20,12 @@ interface IncomingVerificationEntryPoint : FeatureEntryPoint { val verificationRequest: VerificationRequest.Incoming, ) : NodeInputs - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - - interface NodeBuilder { - fun callback(callback: Callback): NodeBuilder - fun params(params: Params): NodeBuilder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: Params, + callback: Callback, + ): Node interface Callback : Plugin { fun onDone() diff --git a/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/OutgoingVerificationEntryPoint.kt b/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/OutgoingVerificationEntryPoint.kt index 60536504d2..d7608caf98 100644 --- a/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/OutgoingVerificationEntryPoint.kt +++ b/features/verifysession/api/src/main/kotlin/io/element/android/features/verifysession/api/OutgoingVerificationEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -20,16 +21,15 @@ interface OutgoingVerificationEntryPoint : FeatureEntryPoint { val verificationRequest: VerificationRequest.Outgoing, ) : NodeInputs - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - - interface NodeBuilder { - fun callback(callback: Callback): NodeBuilder - fun params(params: Params): NodeBuilder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: Params, + callback: Callback, + ): Node interface Callback : Plugin { - fun onLearnMoreAboutEncryption() + fun navigateToLearnMoreAboutEncryption() fun onBack() fun onDone() } diff --git a/features/verifysession/impl/build.gradle.kts b/features/verifysession/impl/build.gradle.kts index 13e9010e7f..89a87edf19 100644 --- a/features/verifysession/impl/build.gradle.kts +++ b/features/verifysession/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/emoji/EmojiResource.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/emoji/EmojiResource.kt index b562d8dd15..3957107793 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/emoji/EmojiResource.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/emoji/EmojiResource.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.verifysession.impl.emoji diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/emoji/SasEmojisPreview.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/emoji/SasEmojisPreview.kt index a5a80fbaa2..9facf31c8d 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/emoji/SasEmojisPreview.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/emoji/SasEmojisPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/DefaultIncomingVerificationEntryPoint.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/DefaultIncomingVerificationEntryPoint.kt index c1a6418153..6567594c0f 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/DefaultIncomingVerificationEntryPoint.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/DefaultIncomingVerificationEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,33 +10,19 @@ package io.element.android.features.verifysession.impl.incoming import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.verifysession.api.IncomingVerificationEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultIncomingVerificationEntryPoint : IncomingVerificationEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): IncomingVerificationEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : IncomingVerificationEntryPoint.NodeBuilder { - override fun callback(callback: IncomingVerificationEntryPoint.Callback): IncomingVerificationEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun params(params: IncomingVerificationEntryPoint.Params): IncomingVerificationEntryPoint.NodeBuilder { - plugins += params - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: IncomingVerificationEntryPoint.Params, + callback: IncomingVerificationEntryPoint.Callback, + ): Node { + return parentNode.createNode(buildContext, listOf(params, callback)) } } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationNavigator.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationNavigator.kt index 4430c0221f..8572030f97 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationNavigator.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationNavigator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationNode.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationNode.kt index a17054b9e6..3eb8735838 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationNode.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,11 +13,11 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.verifysession.api.IncomingVerificationEntryPoint +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope @@ -28,13 +29,14 @@ class IncomingVerificationNode( presenterFactory: IncomingVerificationPresenter.Factory, ) : Node(buildContext, plugins = plugins), IncomingVerificationNavigator { + private val callback: IncomingVerificationEntryPoint.Callback = callback() private val presenter = presenterFactory.create( verificationRequest = inputs().verificationRequest, navigator = this, ) override fun onFinish() { - plugins().forEach { it.onDone() } + callback.onDone() } @Composable diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenter.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenter.kt index 176176a895..aa991a54bc 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenter.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -107,7 +108,7 @@ class IncomingVerificationPresenter( } } - fun handleEvents(event: IncomingVerificationViewEvents) { + fun handleEvent(event: IncomingVerificationViewEvents) { Timber.d("Verification user action: ${event::class.simpleName}") when (event) { IncomingVerificationViewEvents.StartVerification -> @@ -141,7 +142,7 @@ class IncomingVerificationPresenter( return IncomingVerificationState( step = step, request = verificationRequest, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationState.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationState.kt index 00bffa4ce3..311fc13e74 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationState.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationState.kt @@ -1,19 +1,18 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.verifysession.impl.incoming -import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.verification.SessionVerificationData import io.element.android.libraries.matrix.api.verification.VerificationRequest -@Immutable data class IncomingVerificationState( val step: Step, val request: VerificationRequest.Incoming, diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateMachine.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateMachine.kt index 40fbf4379c..babb12f191 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateMachine.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateMachine.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateProvider.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateProvider.kt index 478e696889..77f5af4a2e 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateProvider.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationView.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationView.kt index 4506dfe0c9..286d826a6a 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationView.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewEvents.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewEvents.kt index 7a75ec0f45..383354860b 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewEvents.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/ui/SessionDetailsView.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/ui/SessionDetailsView.kt index 14cd6733d9..308410f259 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/ui/SessionDetailsView.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/incoming/ui/SessionDetailsView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/DefaultOutgoingVerificationEntryPoint.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/DefaultOutgoingVerificationEntryPoint.kt index 8355e71f75..9667c7781f 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/DefaultOutgoingVerificationEntryPoint.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/DefaultOutgoingVerificationEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,33 +10,19 @@ package io.element.android.features.verifysession.impl.outgoing import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.verifysession.api.OutgoingVerificationEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultOutgoingVerificationEntryPoint : OutgoingVerificationEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): OutgoingVerificationEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : OutgoingVerificationEntryPoint.NodeBuilder { - override fun callback(callback: OutgoingVerificationEntryPoint.Callback): OutgoingVerificationEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun params(params: OutgoingVerificationEntryPoint.Params): OutgoingVerificationEntryPoint.NodeBuilder { - plugins += params - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: OutgoingVerificationEntryPoint.Params, + callback: OutgoingVerificationEntryPoint.Callback, + ): Node { + return parentNode.createNode(buildContext, listOf(params, callback)) } } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationNode.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationNode.kt index 9941ce58fe..f6ccf8b9ef 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationNode.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,11 +13,11 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.verifysession.api.OutgoingVerificationEntryPoint +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope @@ -27,8 +28,7 @@ class OutgoingVerificationNode( @Assisted plugins: List, presenterFactory: OutgoingVerificationPresenter.Factory, ) : Node(buildContext, plugins = plugins) { - private val callback = plugins().first() - + private val callback: OutgoingVerificationEntryPoint.Callback = callback() private val inputs = inputs() private val presenter = presenterFactory.create( @@ -42,7 +42,7 @@ class OutgoingVerificationNode( OutgoingVerificationView( state = state, modifier = modifier, - onLearnMoreClick = callback::onLearnMoreAboutEncryption, + onLearnMoreClick = callback::navigateToLearnMoreAboutEncryption, onFinish = callback::onDone, onBack = callback::onBack, ) diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenter.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenter.kt index c985c15e36..90e8a3e598 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenter.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -92,7 +93,7 @@ class OutgoingVerificationPresenter( observeVerificationService() } - fun handleEvents(event: OutgoingVerificationViewEvents) { + fun handleEvent(event: OutgoingVerificationViewEvents) { Timber.d("Verification user action: ${event::class.simpleName}") when (event) { // Just relay the event to the state machine @@ -109,7 +110,7 @@ class OutgoingVerificationPresenter( return OutgoingVerificationState( step = step, request = verificationRequest, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationState.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationState.kt index 9b2d5f2e40..6ba8a03a7f 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationState.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationState.kt @@ -1,19 +1,18 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.verifysession.impl.outgoing -import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.verification.SessionVerificationData import io.element.android.libraries.matrix.api.verification.VerificationRequest -@Immutable data class OutgoingVerificationState( val step: Step, val request: VerificationRequest.Outgoing, diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationStateMachine.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationStateMachine.kt index b078b6ed99..7932ccc484 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationStateMachine.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationStateMachine.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationStateProvider.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationStateProvider.kt index ab7119d213..2da099df9f 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationStateProvider.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationView.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationView.kt index 2357b39be7..f2d3ccfee2 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationView.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewEvents.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewEvents.kt index 3a59f579c8..9faab7e790 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewEvents.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/Common.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/Common.kt index 8859a4180b..665a5e49f7 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/Common.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/Common.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationBottomMenu.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationBottomMenu.kt index 96aef77a37..123a54e2d9 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationBottomMenu.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationBottomMenu.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationContentVerifying.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationContentVerifying.kt index 7a31a26cbb..6fc593ffb2 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationContentVerifying.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationContentVerifying.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationUserProfileContent.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationUserProfileContent.kt index 2e17b736f2..edaadc583d 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationUserProfileContent.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/ui/VerificationUserProfileContent.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/util/StateMachineUtil.kt b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/util/StateMachineUtil.kt index f07cf5ea8f..3739af6959 100644 --- a/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/util/StateMachineUtil.kt +++ b/features/verifysession/impl/src/main/kotlin/io/element/android/features/verifysession/impl/util/StateMachineUtil.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/main/res/values-bg/translations.xml b/features/verifysession/impl/src/main/res/values-bg/translations.xml index 1a2e194696..357600cf60 100644 --- a/features/verifysession/impl/src/main/res/values-bg/translations.xml +++ b/features/verifysession/impl/src/main/res/values-bg/translations.xml @@ -10,6 +10,9 @@ "Нещо не изглежда наред. Или времето за изчакване на заявката е изтекло, или заявката е отхвърлена." "Потвърдете, че емоджитата по-долу съвпадат с показаните в другото ви устройство." "Сравнете емоджита" + "Уверете се, че емоджитата по-долу съвпадат с показаните на другото устройство." + "Уверете се, че числата по-долу съвпадат с показаните на другата ви сесия." + "Сравнете числа" "Сега можете да четете или изпращате съобщения сигурно на другото си устройство." "Устройството е потвърдено" "Въвеждане на ключ за възстановяване" @@ -20,13 +23,17 @@ "В очакване на съвпадение" "Сравнете уникален набор от емоджита." "Сравнете уникалните емоджита, като се уверите, че се появяват в същия ред." + "Влезли" "Неуспешно потвърждаване" + "Продължете само ако вие сте инициирали това потвърждаване." "Сега можете да четете или изпращате съобщения сигурно на другото си устройство." "Устройството е потвърдено" + "Поискано е потвърждение" "Те не съвпадат" "Те съвпадат" "Уверете се, че приложението е отворено на другото устройство, преди да започнете потвърждението оттук." "Отворете приложението на друго потвърдено устройство" + "Потвърждаване на този потребител?" "Чака се другият потребител" "След като бъдете приети, ще можете да продължите потвърждението." "Приемете заявката, за да започнете процеса на потвърждаване в другата си сесия, за да продължите." diff --git a/features/verifysession/impl/src/main/res/values-eo/translations.xml b/features/verifysession/impl/src/main/res/values-eo/translations.xml deleted file mode 100644 index 71beec704e..0000000000 --- a/features/verifysession/impl/src/main/res/values-eo/translations.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - "Create a new backup password" - "Confirm this device to set up secure messaging." - "Confirm it\'s you" - "Use backup password" - "Device confirmed" - "Confirm that the emojis below match those shown on your other session." - "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted." - "Now you can trust this user when sending or receiving messages." - "Device confirmed" - "Enter backup password" - "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted." - "Device confirmed" - "Open the app on another confirmed device" - "For extra security, another user wants to verify you. You\'ll be shown a set of emojis to compare." - diff --git a/features/verifysession/impl/src/main/res/values-fa/translations.xml b/features/verifysession/impl/src/main/res/values-fa/translations.xml index ace791d1b0..ffc03e1f19 100644 --- a/features/verifysession/impl/src/main/res/values-fa/translations.xml +++ b/features/verifysession/impl/src/main/res/values-fa/translations.xml @@ -11,10 +11,10 @@ "استفاده از افزاره‌ای دیگر" "منتظر افزارهٔ دیگر…" "يه چيزي درست به نظر نمياد یا زمان درخواست به پایان رسید یا درخواست رد شد." - "تأیید کنید که ایموجی های زیر با ایموجی های نشان داده شده در جلسه دیگر شما مطابقت دارند." + "تأیید تطابق شکلک‌های زیر با شکلک‌های نشان داده شده روی افزارهٔ دیگرتان." "مقایسهٔ شکلک‌ها" "مقایسهٔ اعداد" - "اکنون نشست جدیدتان تأیید شده‌. این نشست به پیام‌های رمزنگارش شده‌تان دسترسی داشته و دیگر کاربران مطمئن می‌بینندش." + "اکنون می‌توانید روی افزارهٔ دیگرتان با امنیت پیام فرستاده و بخوانید." "افزاره تأیید شده" "ورود کلید بازیابی" "برای دسترسی به تاریخچه پیام‌های رمزگذاری‌شده‌تان، ثابت کنید که خودتان هستید." @@ -24,8 +24,9 @@ "منتظر تطابق…" "مقایسهٔ مجموعه‌ای یکتا از شکلک‌ها." "شکلک‌ها را مقایسه کنید، از ترتیب نمایش آنان نیز مطمئن شوید." + "وارد شده" "صحت‌سنجی شکست خورد" - "اکنون نشست جدیدتان تأیید شده‌. این نشست به پیام‌های رمزنگارش شده‌تان دسترسی داشته و دیگر کاربران مطمئن می‌بینندش." + "اکنون می‌توانید روی افزارهٔ دیگرتان با امنیت پیام فرستاده و بخوانید." "افزاره تأیید شده" "مطابق نیستند" "مطابقند" diff --git a/features/verifysession/impl/src/main/res/values-hr/translations.xml b/features/verifysession/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..a2ccb7ce0a --- /dev/null +++ b/features/verifysession/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,54 @@ + + + "Ne možete potvrditi?" + "Izradi novi ključ za oporavak" + "Potvrdite ovaj uređaj kako biste postavili sigurnu razmjenu poruka." + "Potvrdite svoj identitet" + "Upotrijebite drugi uređaj" + "Upotrijebi ključ za oporavak" + "Sada možete sigurno čitati ili slati poruke, a svatko s kim razgovarate također može vjerovati ovom uređaju." + "Uređaj je potvrđen" + "Upotrijebite drugi uređaj" + "Čekanje na drugi uređaj…" + "Nešto nije u redu. Zahtjev je istekao ili je odbijen." + "Potvrdite da se emotikoni u nastavku podudaraju s onima prikazanima na vašem drugom uređaju." + "Usporedi emotikone" + "Potvrdite da se emotikoni u nastavku podudaraju s onima prikazanima na uređaju drugog korisnika." + "Potvrdite da se brojevi u nastavku podudaraju s onima prikazanima na vašoj drugoj sesiji." + "Usporedi brojeve" + "Sada možete sigurno čitati ili slati poruke na svom drugom uređaju." + "Sada možete biti sigurni u vjerodostojnost identiteta ovog korisnika prilikom slanja ili primanja poruka." + "Uređaj je potvrđen" + "Unesi ključ za oporavak" + "Zahtjev je istekao, odbijen je ili je došlo do neusklađenosti u provjeri." + "Dokažite da ste to vi kako biste mogli pristupiti povijesti svojih šifriranih poruka." + "Otvori postojeću sesiju" + "Ponovi provjeru" + "Spreman/na sam." + "Čekanje podudaranja…" + "Usporedite jedinstveni skup emotikona." + "Usporedite jedinstvene emotikone, pazeći pritom da se pojavljuju istim redoslijedom." + "Prijavljen/a" + "Zahtjev je istekao, odbijen je ili je došlo do neusklađenosti u provjeri." + "Provjera nije uspjela" + "Nastavite samo ako ste pokrenuli ovu provjeru." + "Potvrdite drugi uređaj kako biste zaštitili povijest poruka." + "Sada možete sigurno čitati ili slati poruke na svom drugom uređaju." + "Uređaj je potvrđen" + "Zatražena je provjera" + "Ne podudaraju se" + "Podudaraju se" + "Prije nego što odavde započnete provjeru, provjerite jeste li otvorili aplikaciju na drugom uređaju." + "Otvorite aplikaciju na drugom potvrđenom uređaju" + "Radi dodatne sigurnosti potvrdite ovog korisnika tako da usporedite skup emotikona na svojim uređajima. Učinite to koristeći se pouzdanim načinom komunikacije." + "Želite li potvrditi ovog korisnika?" + "Radi dodatne sigurnosti drugi korisnik želi potvrditi vaš identitet. Bit će vam prikazan skup emotikona za usporedbu." + "Trebali biste vidjeti skočni prozor na drugom uređaju. Sada započnite provjeru odatle." + "Započni provjeru na drugom uređaju" + "Započni provjeru na drugom uređaju" + "Čeka se drugi korisnik" + "Nakon prihvaćanja moći ćete nastaviti s potvrđivanjem." + "Prihvatite zahtjev za pokretanje postupka provjere u drugoj sesiji kako biste nastavili." + "Čekanje na prihvaćanje zahtjeva" + "Odjavljivanje…" + diff --git a/features/verifysession/impl/src/main/res/values-it/translations.xml b/features/verifysession/impl/src/main/res/values-it/translations.xml index 3be984865e..5b11c27cbe 100644 --- a/features/verifysession/impl/src/main/res/values-it/translations.xml +++ b/features/verifysession/impl/src/main/res/values-it/translations.xml @@ -11,12 +11,12 @@ "Usa un altro dispositivo" "In attesa sull\'altro dispositivo…" "C\'è qualcosa che non va. La richiesta è scaduta o è stata rifiutata." - "Verifica che gli emoji sottostanti corrispondano a quelli mostrati nell\'altra sessione." + "Verifica che le emoji sottostanti corrispondano a quelle mostrate sull\'altro dispositivo." "Confronta le emoji" "Conferma che le emoji qui sotto corrispondano a quelle visualizzate sul dispositivo dell\'altro utente." "Conferma che i numeri seguenti corrispondano a quelli mostrati nell\'altra sessione." "Confronta i numeri" - "La tua nuova sessione è ora verificata. Ha accesso ai tuoi messaggi crittografati e gli altri utenti la vedranno come attendibile." + "Ora puoi leggere o inviare messaggi in modo sicuro sul tuo altro dispositivo." "Ora puoi fidarti dell\'identità di questo utente quando invii o ricevi messaggi." "Dispositivo verificato" "Inserisci la chiave di recupero" @@ -33,7 +33,7 @@ "Verifica fallita" "Continua solo se tu hai avviato questa verifica." "Verifica l\'altro dispositivo per proteggere la cronologia dei messaggi." - "La tua nuova sessione è ora verificata. Ha accesso ai tuoi messaggi crittografati e gli altri utenti la vedranno come attendibile." + "Ora puoi leggere o inviare messaggi in modo sicuro sul tuo altro dispositivo." "Dispositivo verificato" "Richiesta di verifica" "Non corrispondono" diff --git a/features/verifysession/impl/src/main/res/values-pl/translations.xml b/features/verifysession/impl/src/main/res/values-pl/translations.xml index f76f2c31fa..92b0572d08 100644 --- a/features/verifysession/impl/src/main/res/values-pl/translations.xml +++ b/features/verifysession/impl/src/main/res/values-pl/translations.xml @@ -16,7 +16,7 @@ "Upewnij się, że emoji poniżej odpowiadają tym na urządzeniu drugiego użytkownika." "Upewnij się, że liczby poniżej pasują do tych wyświetlanych na innej sesji." "Porównaj liczby" - "Twoja nowa sesja jest teraz zweryfikowana. Ma ona dostęp do Twoich zaszyfrowanych wiadomości, a inni użytkownicy będą widzieć ją jako zaufaną." + "Teraz możesz bezpiecznie czytać i wysyłać wiadomości na swoim drugim urządzeniu." "Teraz możesz zaufać tożsamości tego użytkownika podczas wysyłania lub odbierania wiadomości." "Urządzenie zweryfikowane" "Wprowadź klucz przywracania" @@ -33,7 +33,7 @@ "Weryfikacja nie powiodła się" "Kontynuuj tylko, jeśli to Ty zainicjowałeś tę weryfikację." "Zweryfikuj drugie urządzenie, aby zabezpieczyć historię wiadomości." - "Twoja nowa sesja jest teraz zweryfikowana. Ma ona dostęp do Twoich zaszyfrowanych wiadomości, a inni użytkownicy będą widzieć ją jako zaufaną." + "Teraz możesz bezpiecznie czytać i wysyłać wiadomości na swoim drugim urządzeniu." "Urządzenie zweryfikowane" "Zażądano weryfikacji" "Nie pasują do siebie" diff --git a/features/verifysession/impl/src/main/res/values-pt-rBR/translations.xml b/features/verifysession/impl/src/main/res/values-pt-rBR/translations.xml index 8dd7c9a2f7..591c482ca6 100644 --- a/features/verifysession/impl/src/main/res/values-pt-rBR/translations.xml +++ b/features/verifysession/impl/src/main/res/values-pt-rBR/translations.xml @@ -11,12 +11,12 @@ "Usar outro dispositivo" "Aguardando o outro dispositivo…" "Algo não parece certo. Ou a solicitação atingiu o tempo limite ou a solicitação foi negada." - "Confirme se os emojis abaixo correspondem aos mostrados na sua outra sessão." + "Confirme que os emojis abaixo correspondem aos mostrados no seu outro dispositivo." "Compare os emojis" "Confirme se os emojis abaixo correspondem aos exibidos no dispositivo do outro usuário." "Confirme se os números abaixo correspondem aos mostrados na sua outra sessão." "Comparar números" - "Sua nova sessão está verificada agora. Ela tem acesso às suas mensagens criptografadas e outros usuários a verão como confiável." + "Agora você pode enviar ou receber mensagens com segurança no seu outro dispositivo." "Agora você pode confiar na identidade desse usuário ao enviar ou receber mensagens." "Dispositivo verificado" "Digitar chave de recuperação" @@ -33,7 +33,7 @@ "A verificação falhou" "Continue somente se você iniciou esta verificação." "Verifique o outro dispositivo para manter seu histórico de mensagens seguro." - "Sua nova sessão está verificada agora. Ela tem acesso às suas mensagens criptografadas e outros usuários a verão como confiável." + "Agora você pode enviar ou receber mensagens com segurança no seu outro dispositivo." "Dispositivo verificado" "Verificação solicitada" "Eles não combinam" diff --git a/features/verifysession/impl/src/main/res/values-sk/translations.xml b/features/verifysession/impl/src/main/res/values-sk/translations.xml index 7c4781db77..932b8555fc 100644 --- a/features/verifysession/impl/src/main/res/values-sk/translations.xml +++ b/features/verifysession/impl/src/main/res/values-sk/translations.xml @@ -11,12 +11,12 @@ "Použite iné zariadenie" "Čaká sa na druhom zariadení…" "Zdá sa, že niečo nie je v poriadku. Časový limit žiadosti vypršal alebo bola žiadosť zamietnutá." - "Skontrolujte, či sa emotikony uvedené nižšie zhodujú s emotikonmi zobrazenými vo vašej druhej relácii." + "Potvrďte, že nižšie uvedené emotikony sa zhodujú s emotikonmi zobrazenými na vašom druhom zariadení." "Porovnajte emotikony" "Potvrďte, že emotikony uvedené nižšie zodpovedajú emotikonom zobrazeným na zariadení druhého používateľa." "Skontrolujte, či sa nižšie uvedené čísla zhodujú s číslami zobrazenými na vašej druhej relácii." "Porovnať čísla" - "Vaša nová relácia je teraz overená. Má prístup k vašim zašifrovaným správam a ostatní používatelia ju budú vidieť ako dôveryhodnú." + "Teraz môžete bezpečne čítať alebo odosielať správy na svojom druhom zariadení." "Teraz môžete dôverovať identite tohto používateľa pri odosielaní alebo prijímaní správ." "Zariadenie overené" "Zadajte kľúč na obnovenie" @@ -33,7 +33,7 @@ "Overenie zlyhalo" "Pokračujte iba vtedy, ak ste toto overenie začali." "Overte druhé zariadenie, aby bola vaša história správ zabezpečená." - "Vaša nová relácia je teraz overená. Má prístup k vašim zašifrovaným správam a ostatní používatelia ju budú vidieť ako dôveryhodnú." + "Teraz môžete bezpečne čítať alebo odosielať správy na svojom druhom zariadení." "Zariadenie overené" "Vyžadované overenie" "Nezhodujú sa" diff --git a/features/verifysession/impl/src/main/res/values-sv/translations.xml b/features/verifysession/impl/src/main/res/values-sv/translations.xml index 3e92ab0189..53b26d75de 100644 --- a/features/verifysession/impl/src/main/res/values-sv/translations.xml +++ b/features/verifysession/impl/src/main/res/values-sv/translations.xml @@ -11,7 +11,7 @@ "Använd en annan enhet" "Väntar på annan enhet …" "Något verkar inte stämma. Antingen gick tidsgränsen för begäran ut eller så avvisades begäran." - "Bekräfta att emojierna nedan matchar de som visas på din andra session." + "Bekräfta att emojierna nedan matchar de som visas på din andra enhet." "Jämför emojis" "Bekräfta att emojierna nedan matchar de som visas på den andra användarens enhet." "Bekräfta att siffrorna nedan matchar de som visas på din andra session." diff --git a/features/verifysession/impl/src/main/res/values-uz/translations.xml b/features/verifysession/impl/src/main/res/values-uz/translations.xml index 5204e759b3..26ba7393df 100644 --- a/features/verifysession/impl/src/main/res/values-uz/translations.xml +++ b/features/verifysession/impl/src/main/res/values-uz/translations.xml @@ -13,9 +13,11 @@ "Nimadir noto‘g‘ri ko‘rinadi. Yoki so‘rov muddati tugadi yoki so‘rov rad etildi." "Quyidagi kulgichlar boshqa seansda ko‘rsatilganlarga mos kelishini tasdiqlang." "Emojilarni solishtiring" + "Quyidagi emojilar narigi foydalanuvchining qurilmasida ko‘rsatilgan emojilarga mos kelishini tasdiqlang." "Quyidagi raqamlarning boshqa sessiyangizda koʻrsatilgan raqamlarga mos kelishini tasdiqlang." "Sonlarni taqqoslash" - "Yangi seansingiz tasdiqlandi. U sizning shifrlangan xabarlaringizga kirish huquqiga ega va boshqa foydalanuvchilar uni ishonchli deb bilishadi." + "Endi xabarlarni boshqa qurilmangizda xavfsiz o‘qish yoki yuborishingiz mumkin." + "Endi xabarlarni yuborish yoki qabul qilishda bu foydalanuvchining shaxsiga ishonishingiz mumkin." "Qurilma tasdiqlandi" "Tiklash kalitini kiriting" "So‘rov vaqti tugab qoldi, so‘rov rad etildi yoki tekshiruv mos kelmadi." @@ -31,11 +33,21 @@ "Tasdiqlanmadi" "Bu tekshiruvni boshlagan bo‘lsangizgina davom eting." "Xabarlaringiz tarixini xavfsiz saqlash uchun narigi qurilmani tasdiqlang." - "Yangi seansingiz tasdiqlandi. U sizning shifrlangan xabarlaringizga kirish huquqiga ega va boshqa foydalanuvchilar uni ishonchli deb bilishadi." + "Endi xabarlarni boshqa qurilmangizda xavfsiz o‘qish yoki yuborishingiz mumkin." "Qurilma tasdiqlandi" "Tasdiqlash talab qilindi" "Ular mos kelmaydi" "Ular mos keladi" + "Bu yerdan tasdiqlashni boshlashdan oldin, boshqa qurilmada ilovaning ochiq ekanligiga ishonch hosil qiling." + "Ilovani boshqa tasdiqlangan qurilmada oching" + "Qo‘shimcha xavfsizlik chorasi sifatida, qurilmalaringizdagi emojilar to‘plamini solishtirish orqali ushbu foydalanuvchini tasdiqlang. Buni ishonchli aloqa usuli yordamida amalga oshiring." + "Bu foydalanuvchi tasdiqlansinmi?" + "Qo‘shimcha xavfsizlik maqsadida, boshqa foydalanuvchi sizning shaxsingizni tasdiqlashni xohlaydi. Taqqoslash uchun sizga bir qator emojilar ko‘rsatiladi." + "Boshqa qurilmangizda qalqib chiquvchi oyna paydo bo‘lishi kerak. Tekshirish jarayonini o‘sha yerdan boshlang." + "Boshqa qurilmada tekshirishni boshlang" + "Boshqa qurilmada tekshirishni boshlang" + "Boshqa foydalanuvchi kutilmoqda" + "Qabul qilinganingizdan so‘ng, tasdiqlash jarayonini davom ettirishingiz mumkin bo‘ladi." "Davom etish uchun boshqa seansda tekshirish jarayonini boshlash soʻrovini qabul qiling." "Soʻrovni qabul qilish kutilmoqda" "Chiqish…" diff --git a/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml b/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml index 7bba014ada..658e5242f6 100644 --- a/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml +++ b/features/verifysession/impl/src/main/res/values-zh-rTW/translations.xml @@ -11,12 +11,12 @@ "使用另一部裝置" "正在等待其他裝置…" "似乎出了一點問題。有可能是因為等候逾時,或是請求被拒絕。" - "確認顯示在其他工作階段上的表情符號是否和下方的相同。" + "請確認以下表情符號是否與您其他裝置上顯示的符號相符。" "比對表情符號" "確認下方的表情符號與其他使用者裝置上顯示的表情符號相符。" "確認以下數字是否與其他作業階段中顯示的數字相符。" "比較數字" - "新的工作階段已完成驗證。它能夠存取您的加密訊息,而其他使用者會將它視為可信任的。" + "現在您可以在其他裝置上安全地閱讀或傳送訊息。" "現在,您可以在傳送或接收訊息時信任此使用者的身份。" "裝置已驗證" "輸入復原金鑰" @@ -33,7 +33,7 @@ "驗證失敗。" "僅當您啟動此驗證時才繼續。" "驗證其他裝置以保護您的訊息歷史紀錄安全。" - "新的工作階段已完成驗證。它能夠存取您的加密訊息,而其他使用者會將它視為可信任的。" + "現在您可以在其他裝置上安全地閱讀或傳送訊息。" "裝置已驗證" "已請求驗證" "不一樣" diff --git a/features/verifysession/impl/src/main/res/values-zh/translations.xml b/features/verifysession/impl/src/main/res/values-zh/translations.xml index 92641d1773..54b6de5d68 100644 --- a/features/verifysession/impl/src/main/res/values-zh/translations.xml +++ b/features/verifysession/impl/src/main/res/values-zh/translations.xml @@ -11,12 +11,12 @@ "使用其他设备" "正在等待其他设备……" "发生了一些错误。网络请求超时,或者被服务器拒绝。" - "确认下方的表情符号与另一设备上显示的相同。" + "确认下面的表情符号与您其他设备上显示的表情符号相匹配。" "比较表情符号" "请验证下方表情是否与对方设备显示一致" "确认以下数字与其他会话中显示的一致。" "比较数字" - "新设备已经成功验证。现在新设备可以访问加密信息,其他用户也会信任这个设备。" + "现在您可以在其他设备上安全地阅读或发送消息。" "现在您可以在发送或接收消息时信任该用户的身份。" "设备已验证" "输入恢复密钥" @@ -33,7 +33,7 @@ "验证失败" "仅在你发起此验证后才继续。" "验证另一台设备以确保您的消息历史记录保密。" - "新设备已经成功验证。现在新设备可以访问加密信息,其他用户也会信任这个设备。" + "现在您可以在其他设备上安全地阅读或发送消息。" "设备已验证" "已请求验证" "不匹配" diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/DefaultIncomingVerificationEntryPointTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/DefaultIncomingVerificationEntryPointTest.kt index ad586fc7fb..9fc7858349 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/DefaultIncomingVerificationEntryPointTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/DefaultIncomingVerificationEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -37,10 +38,12 @@ class DefaultIncomingVerificationEntryPointTest { val params = IncomingVerificationEntryPoint.Params( verificationRequest = anIncomingSessionVerificationRequest() ) - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .params(params) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + params = params, + callback = callback, + ) assertThat(result).isInstanceOf(IncomingVerificationNode::class.java) assertThat(result.plugins).contains(params) assertThat(result.plugins).contains(callback) diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenterTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenterTest.kt index 2bf7116990..c9b9e25b74 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenterTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewTest.kt index 6b61e05689..4aa63f3ab8 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/DefaultOutgoingVerificationEntryPointTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/DefaultOutgoingVerificationEntryPointTest.kt index 52ff36dbd6..33da9cc991 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/DefaultOutgoingVerificationEntryPointTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/DefaultOutgoingVerificationEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -34,7 +35,7 @@ class DefaultOutgoingVerificationEntryPointTest { ) } val callback = object : OutgoingVerificationEntryPoint.Callback { - override fun onLearnMoreAboutEncryption() = lambdaError() + override fun navigateToLearnMoreAboutEncryption() = lambdaError() override fun onBack() = lambdaError() override fun onDone() = lambdaError() } @@ -42,10 +43,12 @@ class DefaultOutgoingVerificationEntryPointTest { showDeviceVerifiedScreen = true, verificationRequest = anOutgoingSessionVerificationRequest(), ) - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .params(params) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + params = params, + callback = callback, + ) assertThat(result).isInstanceOf(OutgoingVerificationNode::class.java) assertThat(result.plugins).contains(params) assertThat(result.plugins).contains(callback) diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenterTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenterTest.kt index 2eac19d018..f02f24c9ef 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenterTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewTest.kt index c1897d4c6f..71b55fac10 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/verifysession/test/build.gradle.kts b/features/verifysession/test/build.gradle.kts new file mode 100644 index 0000000000..01ce23e0cd --- /dev/null +++ b/features/verifysession/test/build.gradle.kts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.verifysession.test" +} + +dependencies { + implementation(projects.features.verifysession.api) + implementation(projects.libraries.architecture) + implementation(projects.tests.testutils) +} diff --git a/features/verifysession/test/src/main/kotlin/io/element/android/features/verifysession/test/FakeIncomingVerificationEntryPoint.kt b/features/verifysession/test/src/main/kotlin/io/element/android/features/verifysession/test/FakeIncomingVerificationEntryPoint.kt new file mode 100644 index 0000000000..0637e13ae8 --- /dev/null +++ b/features/verifysession/test/src/main/kotlin/io/element/android/features/verifysession/test/FakeIncomingVerificationEntryPoint.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.verifysession.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.verifysession.api.IncomingVerificationEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeIncomingVerificationEntryPoint : IncomingVerificationEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: IncomingVerificationEntryPoint.Params, + callback: IncomingVerificationEntryPoint.Callback, + ): Node = lambdaError() +} diff --git a/features/verifysession/test/src/main/kotlin/io/element/android/features/verifysession/test/FakeOutgoingVerificationEntryPoint.kt b/features/verifysession/test/src/main/kotlin/io/element/android/features/verifysession/test/FakeOutgoingVerificationEntryPoint.kt new file mode 100644 index 0000000000..c05aa2acd7 --- /dev/null +++ b/features/verifysession/test/src/main/kotlin/io/element/android/features/verifysession/test/FakeOutgoingVerificationEntryPoint.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.verifysession.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.verifysession.api.OutgoingVerificationEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeOutgoingVerificationEntryPoint : OutgoingVerificationEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: OutgoingVerificationEntryPoint.Params, + callback: OutgoingVerificationEntryPoint.Callback, + ): Node = lambdaError() +} diff --git a/features/viewfolder/api/build.gradle.kts b/features/viewfolder/api/build.gradle.kts index 4df696b2b2..4763c24d79 100644 --- a/features/viewfolder/api/build.gradle.kts +++ b/features/viewfolder/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/features/viewfolder/api/src/main/kotlin/io/element/android/features/viewfolder/api/TextFileViewer.kt b/features/viewfolder/api/src/main/kotlin/io/element/android/features/viewfolder/api/TextFileViewer.kt index fa1d3511e4..29a7c9f9e8 100644 --- a/features/viewfolder/api/src/main/kotlin/io/element/android/features/viewfolder/api/TextFileViewer.kt +++ b/features/viewfolder/api/src/main/kotlin/io/element/android/features/viewfolder/api/TextFileViewer.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/viewfolder/api/src/main/kotlin/io/element/android/features/viewfolder/api/ViewFolderEntryPoint.kt b/features/viewfolder/api/src/main/kotlin/io/element/android/features/viewfolder/api/ViewFolderEntryPoint.kt index e7f6f580a7..6f9fc504cb 100644 --- a/features/viewfolder/api/src/main/kotlin/io/element/android/features/viewfolder/api/ViewFolderEntryPoint.kt +++ b/features/viewfolder/api/src/main/kotlin/io/element/android/features/viewfolder/api/ViewFolderEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,13 +18,12 @@ interface ViewFolderEntryPoint : FeatureEntryPoint { val rootPath: String, ) - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - - interface NodeBuilder { - fun params(params: Params): NodeBuilder - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: Params, + callback: Callback, + ): Node interface Callback : Plugin { fun onDone() diff --git a/features/viewfolder/impl/build.gradle.kts b/features/viewfolder/impl/build.gradle.kts index 0c7ef17b22..4d6ad057e2 100644 --- a/features/viewfolder/impl/build.gradle.kts +++ b/features/viewfolder/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/DefaultTextFileViewer.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/DefaultTextFileViewer.kt index 0e1f1e11b9..ae3e6932ac 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/DefaultTextFileViewer.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/DefaultTextFileViewer.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,14 +12,12 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.viewfolder.api.TextFileViewer import io.element.android.features.viewfolder.impl.file.ColorationMode import io.element.android.features.viewfolder.impl.file.FileContent import kotlinx.collections.immutable.ImmutableList @ContributesBinding(AppScope::class) -@Inject class DefaultTextFileViewer : TextFileViewer { @Composable override fun Render( diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/DefaultViewFolderEntryPoint.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/DefaultViewFolderEntryPoint.kt index 330ca68a8c..3ef83176b9 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/DefaultViewFolderEntryPoint.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/DefaultViewFolderEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,34 +10,26 @@ package io.element.android.features.viewfolder.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.viewfolder.api.ViewFolderEntryPoint import io.element.android.features.viewfolder.impl.root.ViewFolderFlowNode import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultViewFolderEntryPoint : ViewFolderEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): ViewFolderEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : ViewFolderEntryPoint.NodeBuilder { - override fun params(params: ViewFolderEntryPoint.Params): ViewFolderEntryPoint.NodeBuilder { - plugins += ViewFolderFlowNode.Inputs(params.rootPath) - return this - } - - override fun callback(callback: ViewFolderEntryPoint.Callback): ViewFolderEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: ViewFolderEntryPoint.Params, + callback: ViewFolderEntryPoint.Callback, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf( + ViewFolderFlowNode.Inputs(params.rootPath), + callback, + ), + ) } } diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContent.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContent.kt index 8a233d4506..c68d630869 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContent.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContent.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -72,13 +73,14 @@ private fun LineRow( colorationMode: ColorationMode, ) { val context = LocalContext.current + val toastMessage = stringResource(CommonStrings.common_line_copied_to_clipboard) Row( modifier = Modifier .fillMaxWidth() .clickable(onClick = { context.copyToClipboard( text = line, - toastMessage = context.getString(CommonStrings.common_line_copied_to_clipboard), + toastMessage = toastMessage, ) }) ) { diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContentReader.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContentReader.kt index e3635e40f4..08b7b509ab 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContentReader.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContentReader.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.features.viewfolder.impl.file import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.extensions.runCatchingExceptions import kotlinx.coroutines.withContext @@ -20,7 +20,6 @@ interface FileContentReader { } @ContributesBinding(AppScope::class) -@Inject class DefaultFileContentReader( private val dispatchers: CoroutineDispatchers, ) : FileContentReader { diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileSave.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileSave.kt index 9323281471..03f442f36e 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileSave.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileSave.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,7 +16,6 @@ import android.provider.MediaStore import androidx.annotation.RequiresApi import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.androidutils.system.toast import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.extensions.runCatchingExceptions @@ -33,7 +33,6 @@ interface FileSave { } @ContributesBinding(AppScope::class) -@Inject class DefaultFileSave( @ApplicationContext private val context: Context, private val dispatchers: CoroutineDispatchers, diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileShare.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileShare.kt index 3c8aae4f39..9a43933c94 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileShare.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileShare.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,6 @@ import android.net.Uri import androidx.core.content.FileProvider import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.meta.BuildMeta @@ -30,7 +30,6 @@ interface FileShare { } @ContributesBinding(AppScope::class) -@Inject class DefaultFileShare( @ApplicationContext private val context: Context, private val dispatchers: CoroutineDispatchers, diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileEvents.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileEvents.kt index 5b562795d7..2cc4f3a601 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileEvents.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileNode.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileNode.kt index 41369dda07..6c6a024119 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileNode.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,12 +13,12 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs @ContributesNode(AppScope::class) @@ -36,6 +37,7 @@ class ViewFileNode( fun onBackClick() } + private val callback: Callback = callback() private val inputs: Inputs = inputs() private val presenter = presenterFactory.create( @@ -43,17 +45,13 @@ class ViewFileNode( name = inputs.name, ) - private fun onBackClick() { - plugins().forEach { it.onBackClick() } - } - @Composable override fun View(modifier: Modifier) { val state = presenter.present() ViewFileView( state = state, modifier = modifier, - onBackClick = ::onBackClick, + onBackClick = callback::onBackClick, ) } } diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFilePresenter.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFilePresenter.kt index fb330327f3..615b77e10b 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFilePresenter.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFilePresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileState.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileState.kt index 229b743e2c..979e188b65 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileState.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileStateProvider.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileStateProvider.kt index 2c0400363b..1b20b5c947 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileStateProvider.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt index 4f3b84734f..23f92fc9ba 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/ViewFileView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/FolderExplorer.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/FolderExplorer.kt index dec0541622..c1a2f60729 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/FolderExplorer.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/FolderExplorer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.features.viewfolder.impl.folder import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.viewfolder.impl.model.Item import io.element.android.libraries.androidutils.filesize.FileSizeFormatter import io.element.android.libraries.core.coroutine.CoroutineDispatchers @@ -21,7 +21,6 @@ interface FolderExplorer { } @ContributesBinding(AppScope::class) -@Inject class DefaultFolderExplorer( private val fileSizeFormatter: FileSizeFormatter, private val dispatchers: CoroutineDispatchers, diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderNode.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderNode.kt index 4c57ea4135..4eac71fb4b 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderNode.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,13 +13,13 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.viewfolder.impl.model.Item import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs @ContributesNode(AppScope::class) @@ -35,9 +36,10 @@ class ViewFolderNode( interface Callback : Plugin { fun onBackClick() - fun onNavigateTo(item: Item) + fun navigateToItem(item: Item) } + private val callback: Callback = callback() private val inputs: Inputs = inputs() private val presenter = presenterFactory.create( @@ -45,22 +47,14 @@ class ViewFolderNode( path = inputs.path, ) - private fun onBackClick() { - plugins().forEach { it.onBackClick() } - } - - private fun onNavigateTo(item: Item) { - plugins().forEach { it.onNavigateTo(item) } - } - @Composable override fun View(modifier: Modifier) { val state = presenter.present() ViewFolderView( state = state, modifier = modifier, - onNavigateTo = ::onNavigateTo, - onBackClick = ::onBackClick, + onNavigateTo = callback::navigateToItem, + onBackClick = callback::onBackClick, ) } } diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderPresenter.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderPresenter.kt index 0c5ab61705..6c91675860 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderPresenter.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderState.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderState.kt index 3eb48790a4..f6e62be4bb 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderState.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderStateProvider.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderStateProvider.kt index cbaac89cc2..2debe4cbf8 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderStateProvider.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderView.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderView.kt index bf5360f847..2a6b4031ef 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderView.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/folder/ViewFolderView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/model/Item.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/model/Item.kt index 104d0dc35c..571ac20ade 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/model/Item.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/model/Item.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/root/ViewFolderFlowNode.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/root/ViewFolderFlowNode.kt index d57824f2fd..7418d7899e 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/root/ViewFolderFlowNode.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/root/ViewFolderFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,6 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push @@ -28,6 +28,7 @@ import io.element.android.features.viewfolder.impl.model.Item import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.inputs import kotlinx.parcelize.Parcelize @@ -65,6 +66,7 @@ class ViewFolderFlowNode( val rootPath: String, ) : NodeInputs + private val callback: ViewFolderEntryPoint.Callback = callback() private val inputs: Inputs = inputs() override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { @@ -108,10 +110,10 @@ class ViewFolderFlowNode( ): Node { val callback: ViewFolderNode.Callback = object : ViewFolderNode.Callback { override fun onBackClick() { - onDone() + callback.onDone() } - override fun onNavigateTo(item: Item) { + override fun navigateToItem(item: Item) { when (item) { Item.Parent -> { // Should not happen when in Root since parent is not accessible from root (canGoUp set to false) @@ -133,8 +135,4 @@ class ViewFolderFlowNode( override fun View(modifier: Modifier) { BackstackView() } - - private fun onDone() { - plugins().forEach { it.onDone() } - } } diff --git a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/impl/DefaultViewFolderEntryPointTest.kt b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/impl/DefaultViewFolderEntryPointTest.kt index 47d5992d8c..3ac0a664d9 100644 --- a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/impl/DefaultViewFolderEntryPointTest.kt +++ b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/impl/DefaultViewFolderEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -40,10 +41,12 @@ class DefaultViewFolderEntryPointTest { val params = ViewFolderEntryPoint.Params( rootPath = "path", ) - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .params(params) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + params = params, + callback = callback, + ) assertThat(result).isInstanceOf(ViewFolderFlowNode::class.java) assertThat(result.plugins).contains(ViewFolderFlowNode.Inputs(params.rootPath)) assertThat(result.plugins).contains(callback) diff --git a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileContentReader.kt b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileContentReader.kt index 9fed22741d..6dd621ac6a 100644 --- a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileContentReader.kt +++ b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileContentReader.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileSave.kt b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileSave.kt index 2bcbc8648a..3649d30759 100644 --- a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileSave.kt +++ b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileSave.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileShare.kt b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileShare.kt index 0c639e2649..7348e4bed0 100644 --- a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileShare.kt +++ b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/FakeFileShare.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/ViewFilePresenterTest.kt b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/ViewFilePresenterTest.kt index 117d1707f7..1a031ade28 100644 --- a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/ViewFilePresenterTest.kt +++ b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/file/ViewFilePresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/folder/FakeFolderExplorer.kt b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/folder/FakeFolderExplorer.kt index fb72582de8..7761bfb976 100644 --- a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/folder/FakeFolderExplorer.kt +++ b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/folder/FakeFolderExplorer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/folder/ViewFolderPresenterTest.kt b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/folder/ViewFolderPresenterTest.kt index 715dfa711f..31fd91eabe 100644 --- a/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/folder/ViewFolderPresenterTest.kt +++ b/features/viewfolder/impl/src/test/kotlin/io/element/android/features/viewfolder/test/folder/ViewFolderPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/viewfolder/test/build.gradle.kts b/features/viewfolder/test/build.gradle.kts new file mode 100644 index 0000000000..6fe542013d --- /dev/null +++ b/features/viewfolder/test/build.gradle.kts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.viewfolder.test" +} + +dependencies { + implementation(projects.features.viewfolder.api) + implementation(projects.libraries.architecture) + implementation(projects.tests.testutils) +} diff --git a/features/viewfolder/test/src/main/kotlin/io/element/android/features/viewfolder/test/FakeViewFolderEntryPoint.kt b/features/viewfolder/test/src/main/kotlin/io/element/android/features/viewfolder/test/FakeViewFolderEntryPoint.kt new file mode 100644 index 0000000000..11f009f3af --- /dev/null +++ b/features/viewfolder/test/src/main/kotlin/io/element/android/features/viewfolder/test/FakeViewFolderEntryPoint.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.features.viewfolder.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.features.viewfolder.api.ViewFolderEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeViewFolderEntryPoint : ViewFolderEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: ViewFolderEntryPoint.Params, + callback: ViewFolderEntryPoint.Callback, + ): Node = lambdaError() +} diff --git a/gradle.properties b/gradle.properties index a2c7228ed2..ce03899940 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,8 @@ # +# Copyright (c) 2025 Element Creations Ltd. # Copyright 2022 New Vector Ltd. # -# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. # Please see LICENSE files in the repository root for full details. # Project-wide Gradle settings. @@ -58,3 +59,6 @@ detekt.use.worker.api=true # Let test include roborazzi verification # https://github.com/takahirom/roborazzi?tab=readme-ov-file#roborazzitest roborazzi.test.verify=true + +# Needed after enabling proguard on AGP 8.13.1 +android.r8.gradual.support=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0d4cb342dc..ffabfc9446 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,26 +3,26 @@ [versions] # Project -# We cannot use 8.12.+ since it breaks F-Droid build (see https://github.com/element-hq/element-x-android/issues/3420#issuecomment-3199571010) -android_gradle_plugin = "8.11.1" +android_gradle_plugin = "8.13.2" # When updateing this, please also update the version in the file ./idea/kotlinc.xml -kotlin = "2.2.20" +kotlin = "2.3.0" kotlinpoet = "2.2.0" -ksp = "2.2.20-2.0.2" -firebaseAppDistribution = "5.1.1" +ksp = "2.3.4" +firebaseAppDistribution = "5.2.0" # AndroidX core = "1.17.0" -datastore = "1.1.7" +datastore = "1.2.0" constraintlayout = "2.2.1" constraintlayout_compose = "1.1.1" -lifecycle = "2.9.2" -activity = "1.11.0" -media3 = "1.8.0" -camera = "1.5.0" +lifecycle = "2.10.0" +activity = "1.12.2" +media3 = "1.9.0" +camera = "1.5.2" +work = "2.11.0" # Compose -compose_bom = "2025.07.00" +compose_bom = "2025.12.00" # Coroutines coroutines = "1.10.2" @@ -32,34 +32,37 @@ accompanist = "0.37.3" # Test test_core = "1.7.0" -roborazzi = "1.50.0" +roborazzi = "1.52.0" # Jetbrain datetime = "0.7.1" serialization_json = "1.9.0" #other -detekt = "1.23.8" coil = "3.3.0" +# Rollback to 1.0.4, 1.0.5 has this issue: https://github.com/airbnb/Showkase/issues/420 showkase = "1.0.5" appyx = "1.7.1" -sqldelight = "2.1.0" -wysiwyg = "2.39.0" +sqldelight = "2.2.1" +wysiwyg = "2.41.0" telephoto = "0.18.0" -haze = "1.6.10" +haze = "1.7.1" # Dependency analysis -dependencyAnalysis = "3.0.4" +dependencyAnalysis = "3.5.1" # DI -metro = "0.6.8" +metro = "0.9.2" # Auto service autoservice = "1.1.1" # quality +detekt = "1.23.8" +# See https://github.com/pinterest/ktlint/releases/ +ktlint = "1.8.0" androidx-test-ext-junit = "1.3.0" -kover = "0.9.1" +kover = "0.9.4" [libraries] # Project @@ -75,11 +78,11 @@ kotlinpoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin kover_gradle_plugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" } ksp_gradle_plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } # https://firebase.google.com/docs/android/setup#available-libraries -google_firebase_bom = "com.google.firebase:firebase-bom:34.3.0" +google_firebase_bom = "com.google.firebase:firebase-bom:34.7.0" firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" } autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" } ksp_plugin = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } -google_tink = "com.google.crypto.tink:tink-android:1.18.0" +google_tink = "com.google.crypto.tink:tink-android:1.20.0" # AndroidX androidx_core = { module = "androidx.core:core", version.ref = "core" } @@ -87,18 +90,20 @@ androidx_corektx = { module = "androidx.core:core-ktx", version.ref = "core" } androidx_annotationjvm = "androidx.annotation:annotation-jvm:1.9.1" androidx_datastore_preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" } androidx_datastore_datastore = { module = "androidx.datastore:datastore", version.ref = "datastore" } -androidx_exifinterface = "androidx.exifinterface:exifinterface:1.4.1" +androidx_exifinterface = "androidx.exifinterface:exifinterface:1.4.2" androidx_constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" } androidx_constraintlayout_compose = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "constraintlayout_compose" } androidx_camera_lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camera" } androidx_camera_view = { module = "androidx.camera:camera-view", version.ref = "camera" } androidx_camera_camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camera" } +androidx_javascriptengine = "androidx.javascriptengine:javascriptengine:1.0.0" +androidx_workmanager_runtime = { module = "androidx.work:work-runtime-ktx", version.ref = "work" } androidx_recyclerview = "androidx.recyclerview:recyclerview:1.4.0" androidx_browser = "androidx.browser:browser:1.9.0" androidx_lifecycle_runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } androidx_lifecycle_process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle" } -androidx_splash = "androidx.core:core-splashscreen:1.0.1" +androidx_splash = "androidx.core:core-splashscreen:1.2.0" androidx_media3_exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" } androidx_media3_ui = { module = "androidx.media3:media3-ui", version.ref = "media3" } androidx_media3_transformer = { module = "androidx.media3:media3-transformer", version.ref = "media3" } @@ -110,7 +115,7 @@ androidx_activity_activity = { module = "androidx.activity:activity", version.re androidx_activity_compose = { module = "androidx.activity:activity-compose", version.ref = "activity" } androidx_startup = "androidx.startup:startup-runtime:1.2.0" androidx_preference = "androidx.preference:preference:1.2.1" -androidx_webkit = "androidx.webkit:webkit:1.14.0" +androidx_webkit = "androidx.webkit:webkit:1.15.0" androidx_compose_bom = { module = "androidx.compose:compose-bom", version.ref = "compose_bom" } androidx_compose_material3 = { module = "androidx.compose.material3:material3" } @@ -126,6 +131,7 @@ androidx_compose_material_icons = { module = "androidx.compose.material:material # Coroutines coroutines_core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } +coroutines_guava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava", version.ref = "coroutines" } coroutines_test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } # Accompanist @@ -135,7 +141,7 @@ accompanist_permission = { module = "com.google.accompanist:accompanist-permissi squareup_seismic = "com.squareup:seismic:1.0.3" # network -network_okhttp_bom = "com.squareup.okhttp3:okhttp-bom:5.1.0" +network_okhttp_bom = "com.squareup.okhttp3:okhttp-bom:5.3.2" network_okhttp_logging = { module = "com.squareup.okhttp3:logging-interceptor" } network_okhttp_okhttp = { module = "com.squareup.okhttp3:okhttp" } network_okhttp = { module = "com.squareup.okhttp3:okhttp" } @@ -144,20 +150,24 @@ network_retrofit_bom = "com.squareup.retrofit2:retrofit-bom:3.0.0" network_retrofit = { module = "com.squareup.retrofit2:retrofit" } network_retrofit_converter_serialization = { module = "com.squareup.retrofit2:converter-kotlinx-serialization" } +# Quality +# Reference ktlint-cli so that Renovate can check if a new version is available. +ktlint-cli = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" } + # Test test_core = { module = "androidx.test:core", version.ref = "test_core" } test_corektx = { module = "androidx.test:core-ktx", version.ref = "test_core" } test_arch_core = "androidx.arch.core:core-testing:2.2.0" test_junit = "junit:junit:4.13.2" test_runner = "androidx.test:runner:1.7.0" -test_mockk = "io.mockk:mockk:1.14.6" +test_mockk = "io.mockk:mockk:1.14.7" test_konsist = "com.lemonappdev:konsist:0.17.3" test_turbine = "app.cash.turbine:turbine:1.2.1" test_truth = "com.google.truth:truth:1.4.5" -test_parameter_injector = "com.google.testparameterinjector:test-parameter-injector:1.19" -test_robolectric = "org.robolectric:robolectric:4.15.1" +test_parameter_injector = "com.google.testparameterinjector:test-parameter-injector:1.20" +test_robolectric = "org.robolectric:robolectric:4.16" test_appyx_junit = { module = "com.bumble.appyx:testing-junit4", version.ref = "appyx" } -test_composable_preview_scanner = "io.github.sergio-sastre.ComposablePreviewScanner:android:0.7.0" +test_composable_preview_scanner = "io.github.sergio-sastre.ComposablePreviewScanner:android:0.8.1" test_detekt_api = { module = "io.gitlab.arturbosch.detekt:detekt-api", version.ref = "detekt" } test_detekt_test = { module = "io.gitlab.arturbosch.detekt:detekt-test", version.ref = "detekt" } @@ -167,7 +177,7 @@ test_detekt_test = { module = "io.gitlab.arturbosch.detekt:detekt-test", version # https://github.com/matrix-org/matrix-rust-components-kotlin/commits/main/sdk/sdk-android/src/main/kotlin/org/matrix/rustcomponents/sdk/matrix_sdk_ffi.kt # All new features should not be implemented in the pull request that upgrades the version, developers should # only fix API breaks and may add some TODOs. -matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.10.7" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.12.19" # Others coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } @@ -189,36 +199,39 @@ matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-driver-jvm = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" } sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" } -sqlcipher = "net.zetetic:sqlcipher-android:4.10.0" -sqlite = "androidx.sqlite:sqlite-ktx:2.6.1" -unifiedpush = "org.unifiedpush.android:connector:3.0.10" +sqlcipher = "net.zetetic:sqlcipher-android:4.12.0" +sqlite = "androidx.sqlite:sqlite-ktx:2.6.2" +unifiedpush = "org.unifiedpush.android:connector:3.1.2" vanniktech_blurhash = "com.vanniktech:blurhash:0.3.0" telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil", version.ref = "telephoto" } telephoto_flick = { module = "me.saket.telephoto:flick-android", version.ref = "telephoto" } statemachine = "com.freeletics.flowredux:compose:1.2.2" -maplibre = "org.maplibre.gl:android-sdk:12.0.0" +maplibre = "org.maplibre.gl:android-sdk:12.3.1" maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:3.0.2" maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:3.0.2" opusencoder = "io.element.android:opusencoder:1.2.0" zxing_cpp = "io.github.zxing-cpp:android:2.3.0" +google_zxing = "com.google.zxing:core:3.5.4" + haze = { module = "dev.chrisbanes.haze:haze", version.ref = "haze" } haze_materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref = "haze" } +color_picker = "io.mhssn:colorpicker:1.0.0" # Analytics -posthog = "com.posthog:posthog-android:3.23.0" -sentry = "io.sentry:sentry-android:8.23.0" +posthog = "com.posthog:posthog-android:3.28.0" +sentry = "io.sentry:sentry-android:8.29.0" # main branch can be tested replacing the version with main-SNAPSHOT -matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.28.0" +matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.29.2" # Emojibase -matrix_emojibase_bindings = "io.element.android:emojibase-bindings:1.4.3" +matrix_emojibase_bindings = "io.element.android:emojibase-bindings:1.5.0" sigpwned_emoji4j = "com.sigpwned:emoji4j-core:16.0.0" # Di metro_runtime = { module = "dev.zacsweers.metro:runtime", version.ref = "metro" } # Element Call -element_call_embedded = "io.element.android:element-call-embedded:0.16.0" +element_call_embedded = "io.element.android:element-call-embedded:0.16.3" # Auto services google_autoservice = { module = "com.google.auto.service:auto-service", version.ref = "autoservice" } @@ -244,16 +257,16 @@ ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } # Note: used in DependencyInjectionExtensions.kt metro = { id = "dev.zacsweers.metro", version.ref = "metro" } detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } -ktlint = "org.jlleitschuh.gradle.ktlint:13.1.0" +ktlint = "org.jlleitschuh.gradle.ktlint:14.0.1" dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12" -dependencycheck = "org.owasp.dependencycheck:12.1.6" +dependencycheck = "org.owasp.dependencycheck:12.1.9" dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyAnalysis" } paparazzi = "app.cash.paparazzi:2.0.0-alpha02" roborazzi = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } firebaseAppDistribution = { id = "com.google.firebase.appdistribution", version.ref = "firebaseAppDistribution" } knit = { id = "org.jetbrains.kotlinx.knit", version = "0.5.0" } -sonarqube = "org.sonarqube:6.3.1.5724" -licensee = "app.cash.licensee:1.13.0" +sonarqube = "org.sonarqube:7.2.2.6593" +licensee = "app.cash.licensee:1.14.1" compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } -gms_google_services = { id = "com.google.gms.google-services", version = "4.4.3" } +gms_google_services = { id = "com.google.gms.google-services", version = "4.4.4" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1b33c55baa..f8e1ee3125 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 78cb6e16a4..8a848873f3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionSha256Sum=72f44c9f8ebcb1af43838f45ee5c4aa9c5444898b3468ab3f4af7b6076c5bc3f +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 23d15a9367..adff685a03 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/gradlew.bat b/gradlew.bat index 5eed7ee845..e509b2dd8f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/libraries/accountselect/api/build.gradle.kts b/libraries/accountselect/api/build.gradle.kts index 7e0ce303f9..c7f5a97307 100644 --- a/libraries/accountselect/api/build.gradle.kts +++ b/libraries/accountselect/api/build.gradle.kts @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/accountselect/api/src/main/kotlin/io/element/android/libraries/accountselect/api/AccountSelectEntryPoint.kt b/libraries/accountselect/api/src/main/kotlin/io/element/android/libraries/accountselect/api/AccountSelectEntryPoint.kt index 72da3491de..0af756aab5 100644 --- a/libraries/accountselect/api/src/main/kotlin/io/element/android/libraries/accountselect/api/AccountSelectEntryPoint.kt +++ b/libraries/accountselect/api/src/main/kotlin/io/element/android/libraries/accountselect/api/AccountSelectEntryPoint.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,15 +15,14 @@ import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.matrix.api.core.SessionId interface AccountSelectEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - - interface NodeBuilder { - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: Callback, + ): Node interface Callback : Plugin { - fun onSelectAccount(sessionId: SessionId) + fun onAccountSelected(sessionId: SessionId) fun onCancel() } } diff --git a/libraries/accountselect/impl/build.gradle.kts b/libraries/accountselect/impl/build.gradle.kts index ea1fbd52ad..2497299f5b 100644 --- a/libraries/accountselect/impl/build.gradle.kts +++ b/libraries/accountselect/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectNode.kt b/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectNode.kt index 5478d9fe43..1f52143e20 100644 --- a/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectNode.kt +++ b/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectNode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,7 +18,7 @@ import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.libraries.accountselect.api.AccountSelectEntryPoint -import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.architecture.callback @ContributesNode(AppScope::class) @AssistedInject @@ -26,23 +27,15 @@ class AccountSelectNode( @Assisted plugins: List, private val presenter: AccountSelectPresenter, ) : Node(buildContext, plugins = plugins) { - private val callbacks = plugins.filterIsInstance() - - private fun onDismiss() { - callbacks.forEach { it.onCancel() } - } - - private fun onSelectAccount(sessionId: SessionId) { - callbacks.forEach { it.onSelectAccount(sessionId) } - } + private val callback: AccountSelectEntryPoint.Callback = callback() @Composable override fun View(modifier: Modifier) { val state = presenter.present() AccountSelectView( state = state, - onDismiss = ::onDismiss, - onSelectAccount = ::onSelectAccount, + onDismiss = callback::onCancel, + onSelectAccount = callback::onAccountSelected, modifier = modifier, ) } diff --git a/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectPresenter.kt b/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectPresenter.kt index f69708cc9d..be6a8bea6f 100644 --- a/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectPresenter.kt +++ b/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectPresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectState.kt b/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectState.kt index feaedaf90d..518c49f829 100644 --- a/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectState.kt +++ b/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectStateProvider.kt b/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectStateProvider.kt index 5d99bf3b0c..0c12d34352 100644 --- a/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectStateProvider.kt +++ b/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectView.kt b/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectView.kt index b589df23f6..bfa1bf45b6 100644 --- a/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectView.kt +++ b/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/DefaultAccountSelectEntryPoint.kt b/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/DefaultAccountSelectEntryPoint.kt index baf5ecd5b3..6d601f7bf8 100644 --- a/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/DefaultAccountSelectEntryPoint.kt +++ b/libraries/accountselect/impl/src/main/kotlin/io/element/android/libraries/accountselect/impl/DefaultAccountSelectEntryPoint.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,28 +10,18 @@ package io.element.android.libraries.accountselect.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.accountselect.api.AccountSelectEntryPoint import io.element.android.libraries.architecture.createNode @ContributesBinding(AppScope::class) -@Inject class DefaultAccountSelectEntryPoint : AccountSelectEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): AccountSelectEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : AccountSelectEntryPoint.NodeBuilder { - override fun callback(callback: AccountSelectEntryPoint.Callback): AccountSelectEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: AccountSelectEntryPoint.Callback, + ): Node { + return parentNode.createNode(buildContext, listOf(callback)) } } diff --git a/libraries/accountselect/impl/src/test/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectPresenterTest.kt b/libraries/accountselect/impl/src/test/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectPresenterTest.kt index 27a8d7d9cf..8b0f581f6d 100644 --- a/libraries/accountselect/impl/src/test/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectPresenterTest.kt +++ b/libraries/accountselect/impl/src/test/kotlin/io/element/android/libraries/accountselect/impl/AccountSelectPresenterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/accountselect/impl/src/test/kotlin/io/element/android/libraries/accountselect/impl/DefaultAccountSelectEntryPointTest.kt b/libraries/accountselect/impl/src/test/kotlin/io/element/android/libraries/accountselect/impl/DefaultAccountSelectEntryPointTest.kt index d61dcc89ba..870c43ebdd 100644 --- a/libraries/accountselect/impl/src/test/kotlin/io/element/android/libraries/accountselect/impl/DefaultAccountSelectEntryPointTest.kt +++ b/libraries/accountselect/impl/src/test/kotlin/io/element/android/libraries/accountselect/impl/DefaultAccountSelectEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -32,12 +33,14 @@ class DefaultAccountSelectEntryPointTest { ) } val callback = object : AccountSelectEntryPoint.Callback { - override fun onSelectAccount(sessionId: SessionId) = lambdaError() + override fun onAccountSelected(sessionId: SessionId) = lambdaError() override fun onCancel() = lambdaError() } - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + callback = callback, + ) assertThat(result).isInstanceOf(AccountSelectNode::class.java) assertThat(result.plugins).contains(callback) } diff --git a/libraries/androidutils/build.gradle.kts b/libraries/androidutils/build.gradle.kts index ac1e317b48..90aa4bdba3 100644 --- a/libraries/androidutils/build.gradle.kts +++ b/libraries/androidutils/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { @@ -32,6 +33,7 @@ dependencies { implementation(libs.androidx.recyclerview) implementation(libs.androidx.exifinterface) implementation(libs.androidx.datastore.preferences) + implementation(libs.serialization.json) api(libs.androidx.browser) testCommonDependencies(libs) diff --git a/libraries/androidutils/src/main/AndroidManifest.xml b/libraries/androidutils/src/main/AndroidManifest.xml index 7eb445a672..446c4606d7 100644 --- a/libraries/androidutils/src/main/AndroidManifest.xml +++ b/libraries/androidutils/src/main/AndroidManifest.xml @@ -1,7 +1,8 @@ diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/assets/AssetReader.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/assets/AssetReader.kt new file mode 100644 index 0000000000..da44ff43c8 --- /dev/null +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/assets/AssetReader.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.androidutils.assets + +import android.content.Context +import dev.zacsweers.metro.Inject +import io.element.android.libraries.di.annotations.ApplicationContext +import timber.log.Timber +import java.util.concurrent.ConcurrentHashMap + +/** + * Read asset files. + */ +@Inject +class AssetReader( + @ApplicationContext private val context: Context, +) { + private val cache = ConcurrentHashMap() + + /** + * Read an asset from resource and return a String or null in case of error. + * + * @param assetFilename Asset filename + * @return the content of the asset file, or null in case of error + */ + fun readAssetFile(assetFilename: String): String? { + return cache.getOrPut(assetFilename, { + return try { + context.assets.open(assetFilename).use { it.bufferedReader().readText() } + } catch (e: Exception) { + Timber.e(e, "## readAssetFile() failed") + null + } + }) + } + + fun clearCache() { + cache.clear() + } +} diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/bitmap/Bitmap.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/bitmap/Bitmap.kt index 42ae1c8ba5..0ef4c94225 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/bitmap/Bitmap.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/bitmap/Bitmap.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/browser/ChromeCustomTab.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/browser/ChromeCustomTab.kt index 5617d459f0..6650f70ecb 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/browser/ChromeCustomTab.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/browser/ChromeCustomTab.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -59,7 +60,7 @@ fun Activity.openUrlInChromeCustomTab( }) } .launchUrl(this, url.toUri()) - } catch (activityNotFoundException: ActivityNotFoundException) { + } catch (_: ActivityNotFoundException) { openUrlInExternalApp(url) } } diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/browser/ConsoleMessageLogger.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/browser/ConsoleMessageLogger.kt new file mode 100644 index 0000000000..d165da8640 --- /dev/null +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/browser/ConsoleMessageLogger.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.androidutils.browser + +import android.util.Log +import android.webkit.ConsoleMessage +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import timber.log.Timber + +interface ConsoleMessageLogger { + fun log( + tag: String, + consoleMessage: ConsoleMessage, + ) +} + +@ContributesBinding(AppScope::class) +class DefaultConsoleMessageLogger : ConsoleMessageLogger { + override fun log( + tag: String, + consoleMessage: ConsoleMessage, + ) { + val priority = when (consoleMessage.messageLevel()) { + ConsoleMessage.MessageLevel.ERROR -> Log.ERROR + ConsoleMessage.MessageLevel.WARNING -> Log.WARN + else -> Log.DEBUG + } + + val message = buildString { + append(consoleMessage.sourceId()) + append(":") + append(consoleMessage.lineNumber()) + append(" ") + append(consoleMessage.message()) + } + + // Avoid logging any messages that contain "password" to prevent leaking sensitive information + if (message.contains("password=")) { + return + } + + Timber.tag(tag).log( + priority = priority, + message = buildString { + append(consoleMessage.sourceId()) + append(":") + append(consoleMessage.lineNumber()) + append(" ") + append(consoleMessage.message()) + }, + ) + } +} diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/AndroidClipboardHelper.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/AndroidClipboardHelper.kt index 98fa682060..ce6f61f280 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/AndroidClipboardHelper.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/AndroidClipboardHelper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,13 +14,11 @@ import android.content.Context import androidx.core.content.getSystemService import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.di.annotations.ApplicationContext @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) -@Inject class AndroidClipboardHelper( @ApplicationContext private val context: Context, ) : ClipboardHelper { diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/ClipboardHelper.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/ClipboardHelper.kt index c789ee5900..1d0ab8e3c4 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/ClipboardHelper.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/ClipboardHelper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/FakeClipboardHelper.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/FakeClipboardHelper.kt index 1969313033..9f91b3a4ce 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/FakeClipboardHelper.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/clipboard/FakeClipboardHelper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/Compat.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/Compat.kt index 8021a1b54e..3abb7dc38a 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/Compat.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/Compat.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DefaultDiffCallback.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DefaultDiffCallback.kt index 190341611d..3940edad46 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DefaultDiffCallback.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DefaultDiffCallback.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCache.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCache.kt index e689f6e143..6737a9b879 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCache.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCache.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCacheInvalidator.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCacheInvalidator.kt index 4adcd7ed71..f8d43b7763 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCacheInvalidator.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCacheInvalidator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCacheUpdater.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCacheUpdater.kt index 227e49ab55..fb3c610a01 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCacheUpdater.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/diff/DiffCacheUpdater.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -27,7 +28,7 @@ class DiffCacheUpdater( private val cacheInvalidator: DiffCacheInvalidator = DefaultDiffCacheInvalidator(), private val areItemsTheSame: (oldItem: ListItem?, newItem: ListItem?) -> Boolean, ) { - private val lock = Object() + private val lock = Any() private var prevOriginalList: List = emptyList() private val listUpdateCallback = object : ListUpdateCallback { diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/Context.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/Context.kt index eaa0b578bc..65e56a3e0f 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/Context.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/Context.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt index 414b123129..21306ff192 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/FileCompression.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/FileCompression.kt index 7c6926e58d..1dbf87a529 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/FileCompression.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/FileCompression.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -38,8 +39,5 @@ fun compressFile(file: File): File? { } catch (e: Exception) { Timber.e(e, "## compressFile() failed") null - } catch (oom: OutOfMemoryError) { - Timber.e(oom, "## compressFile() failed") - null } } diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/TemporaryUriDeleter.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/TemporaryUriDeleter.kt index e8f7ef7f1d..beef6c0cfb 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/TemporaryUriDeleter.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/TemporaryUriDeleter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,6 @@ import android.content.Context import android.net.Uri import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.annotations.ApplicationContext import timber.log.Timber @@ -23,7 +23,6 @@ interface TemporaryUriDeleter { } @ContributesBinding(AppScope::class) -@Inject class DefaultTemporaryUriDeleter( @ApplicationContext private val context: Context, ) : TemporaryUriDeleter { diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatter.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatter.kt index 9578c96b2c..6854d099fa 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatter.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,12 +13,10 @@ import android.os.Build import android.text.format.Formatter import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider @ContributesBinding(AppScope::class) -@Inject class AndroidFileSizeFormatter( @ApplicationContext private val context: Context, private val sdkIntProvider: BuildVersionSdkIntProvider, @@ -25,12 +24,15 @@ class AndroidFileSizeFormatter( override fun format(fileSize: Long, useShortFormat: Boolean): String { // Since Android O, the system considers that 1kB = 1000 bytes instead of 1024 bytes. // We want to avoid that. + // Sadly we do not have access to the flags values Formatter.FLAG_IEC_UNITS and Formatter.FLAG_SHORTER + // nor the method Formatter.formatFileSize with the flags parameter. + // So for Android 0 and more, first convert the fileSize to MB/GB/TB ourselves val normalizedSize = if (sdkIntProvider.get() <= Build.VERSION_CODES.N) { fileSize } else { // First convert the size when { - fileSize < 1024 -> fileSize + fileSize <= 1 -> fileSize fileSize < 1024 * 1024 -> fileSize * 1000 / 1024 fileSize < 1024 * 1024 * 1024 -> fileSize * 1000 / 1024 * 1000 / 1024 else -> fileSize * 1000 / 1024 * 1000 / 1024 * 1000 / 1024 @@ -41,6 +43,6 @@ class AndroidFileSizeFormatter( Formatter.formatShortFileSize(context, normalizedSize) } else { Formatter.formatFileSize(context, normalizedSize) - } + }.replace("kB", "KB") } } diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FakeFileSizeFormatter.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FakeFileSizeFormatter.kt index b64bd79233..1a3e540acd 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FakeFileSizeFormatter.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FakeFileSizeFormatter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FileSizeFormatter.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FileSizeFormatter.kt index 3926c29c71..579c34b5ea 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FileSizeFormatter.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/filesize/FileSizeFormatter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hardware/VibratorTools.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hardware/VibratorTools.kt index d742d7defb..b6e01aa737 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hardware/VibratorTools.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hardware/VibratorTools.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.kt index 3574c68eb4..b699054ff0 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/hash/Hash.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -19,7 +20,7 @@ fun String.hash() = try { digest.digest() .joinToString("") { String.format(Locale.ROOT, "%02X", it) } .lowercase(Locale.ROOT) -} catch (exc: Exception) { +} catch (_: Exception) { // Should not happen, but just in case hashCode().toString() } diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/json/JsonProvider.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/json/JsonProvider.kt new file mode 100644 index 0000000000..1e25599962 --- /dev/null +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/json/JsonProvider.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.androidutils.json + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.SingleIn +import kotlinx.serialization.json.Json + +/** + * Provides a Json instance configured to ignore unknown keys. + */ +fun interface JsonProvider { + operator fun invoke(): Json +} + +@ContributesBinding(AppScope::class) +@SingleIn(AppScope::class) +class DefaultJsonProvider : JsonProvider { + private val json: Json by lazy { Json { ignoreUnknownKeys = true } } + override fun invoke() = json +} diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/media/MediaMetaDataRetriever.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/media/MediaMetaDataRetriever.kt index 5152fb42ab..1b0f783856 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/media/MediaMetaDataRetriever.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/media/MediaMetaDataRetriever.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/media/VideoCompressorHelper.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/media/VideoCompressorHelper.kt index 3ac32fd3a9..6262d00d62 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/media/VideoCompressorHelper.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/media/VideoCompressorHelper.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/metadata/IsInDebug.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/metadata/IsInDebug.kt index d82875201a..658a28cdaf 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/metadata/IsInDebug.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/metadata/IsInDebug.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/preferences/DefaultPreferencesCorruptionHandlerFactory.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/preferences/DefaultPreferencesCorruptionHandlerFactory.kt index e6270f4af3..b78e8eecf2 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/preferences/DefaultPreferencesCorruptionHandlerFactory.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/preferences/DefaultPreferencesCorruptionHandlerFactory.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/Accessibility.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/Accessibility.kt new file mode 100644 index 0000000000..35ebd89a74 --- /dev/null +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/Accessibility.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.androidutils.system + +import android.content.Context +import android.provider.Settings + +fun Context.getAnimationScale(): Float { + return Settings.Global.getFloat(contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f) +} + +fun Context.areAnimationsEnabled(): Boolean { + return getAnimationScale() > 0f +} diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/Brightness.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/Brightness.kt new file mode 100644 index 0000000000..8cf1cc6827 --- /dev/null +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/Brightness.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.androidutils.system + +import android.app.Activity +import android.view.WindowManager + +/** + * Set the screen brightness for the given activity. + * + * @receiver current Activity. + * @param full If true, override brightness to full; otherwise, set to none (default). + */ +fun Activity.setFullBrightness(full: Boolean) { + window.attributes = window.attributes.apply { + screenBrightness = if (full) { + WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL + } else { + WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE + } + } +} diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/CopyToClipboardUseCase.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/CopyToClipboardUseCase.kt index f67fe85c9f..53a1c33995 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/CopyToClipboardUseCase.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/CopyToClipboardUseCase.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/DateTimeObserver.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/DateTimeObserver.kt index e3c7973341..3723eac559 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/DateTimeObserver.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/DateTimeObserver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,6 @@ import android.content.Intent import android.content.IntentFilter import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.androidutils.system.DateTimeObserver.Event import io.element.android.libraries.di.annotations.ApplicationContext @@ -32,7 +32,6 @@ interface DateTimeObserver { @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) -@Inject class DefaultDateTimeObserver( @ApplicationContext context: Context ) : DateTimeObserver { 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 49895e47ec..861aca032d 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -31,7 +32,7 @@ fun Context.getApplicationLabel(packageName: String): String { return try { val ai = packageManager.getApplicationInfoCompat(packageName, 0) packageManager.getApplicationLabel(ai).toString() - } catch (e: PackageManager.NameNotFoundException) { + } catch (_: PackageManager.NameNotFoundException) { packageName } } @@ -95,7 +96,7 @@ fun Context.startNotificationSettingsIntent( } else { startActivity(intent) } - } catch (activityNotFoundException: ActivityNotFoundException) { + } catch (_: ActivityNotFoundException) { toast(noActivityFoundMessage) } } @@ -111,7 +112,7 @@ fun Context.openAppSettingsPage( data = Uri.fromParts("package", packageName, null) } ) - } catch (activityNotFoundException: ActivityNotFoundException) { + } catch (_: ActivityNotFoundException) { toast(noActivityFoundMessage) } } @@ -125,7 +126,7 @@ fun Context.startInstallFromSourceIntent( .setData("package:$packageName".toUri()) try { activityResultLauncher.launch(intent) - } catch (activityNotFoundException: ActivityNotFoundException) { + } catch (_: ActivityNotFoundException) { toast(noActivityFoundMessage) } } @@ -156,7 +157,7 @@ fun Context.startSharePlainTextIntent( } else { startActivity(intent) } - } catch (activityNotFoundException: ActivityNotFoundException) { + } catch (_: ActivityNotFoundException) { toast(noActivityFoundMessage) } } diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/text/LinkifyHelper.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/text/LinkifyHelper.kt index c0cb5a766a..b95dacc5a8 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/text/LinkifyHelper.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/text/LinkifyHelper.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -29,6 +30,7 @@ object LinkifyHelper { @LinkifyCompat.LinkifyMask linkifyMask: Int = Linkify.WEB_URLS or Linkify.PHONE_NUMBERS or Linkify.EMAIL_ADDRESSES, ): CharSequence { // Convert the text to a Spannable to be able to add URL spans, return the original text if it's not possible (in tests, i.e.) + @Suppress("USELESS_ELVIS") val spannable = text.toSpannable() ?: return text // Get all URL spans, as they will be removed by LinkifyCompat.addLinks diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/text/TextUtils.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/text/TextUtils.kt new file mode 100644 index 0000000000..dde63e79a6 --- /dev/null +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/text/TextUtils.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.androidutils.text + +import java.net.URLDecoder +import java.net.URLEncoder +import java.nio.charset.Charset + +fun String.urlEncoded(charset: Charset = Charsets.UTF_8): String = URLEncoder.encode(this, charset.name()) +fun String.urlDecoded(charset: Charset = Charsets.UTF_8): String = URLDecoder.decode(this, charset.name()) diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottler.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottler.kt index 8f6766cdd6..ebd4a5c6bd 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottler.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.androidutils.throttler diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/ui/View.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/ui/View.kt index e2dacf58e7..96a1e5f9aa 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/ui/View.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/ui/View.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/uri/UriExtensions.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/uri/UriExtensions.kt index bef2a50183..1c18c22804 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/uri/UriExtensions.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/uri/UriExtensions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2021-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2021-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/main/res/values-hr/translations.xml b/libraries/androidutils/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..1e19b4cb89 --- /dev/null +++ b/libraries/androidutils/src/main/res/values-hr/translations.xml @@ -0,0 +1,4 @@ + + + "Nije pronađena kompatibilna aplikacija za izvršavanje ove radnje." + diff --git a/libraries/androidutils/src/main/res/values-ldrtl/integers.xml b/libraries/androidutils/src/main/res/values-ldrtl/integers.xml index 410bf85b80..2f963a6449 100644 --- a/libraries/androidutils/src/main/res/values-ldrtl/integers.xml +++ b/libraries/androidutils/src/main/res/values-ldrtl/integers.xml @@ -1,7 +1,8 @@ diff --git a/libraries/androidutils/src/main/res/values/integers.xml b/libraries/androidutils/src/main/res/values/integers.xml index 6df186f3f2..c6598e8381 100644 --- a/libraries/androidutils/src/main/res/values/integers.xml +++ b/libraries/androidutils/src/main/res/values/integers.xml @@ -1,7 +1,8 @@ diff --git a/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatterTest.kt b/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatterTest.kt index 93f778d9fd..a83aa7d69f 100644 --- a/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatterTest.kt +++ b/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/filesize/AndroidFileSizeFormatterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,45 +15,59 @@ import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) class AndroidFileSizeFormatterTest { + @Config(sdk = [Build.VERSION_CODES.N]) @Test fun `test api 24 long format`() { val sut = createAndroidFileSizeFormatter(sdkLevel = Build.VERSION_CODES.N) - assertThat(sut.format(1, useShortFormat = false)).isEqualTo("1.00B") - assertThat(sut.format(1000, useShortFormat = false)).isEqualTo("0.98KB") - assertThat(sut.format(1024, useShortFormat = false)).isEqualTo("1.00KB") - assertThat(sut.format(1024 * 1024, useShortFormat = false)).isEqualTo("1.00MB") - assertThat(sut.format(1024 * 1024 * 1024, useShortFormat = false)).isEqualTo("1.00GB") + assertThat(sut.format(1, useShortFormat = false)).isEqualTo("1 B") + assertThat(sut.format(1000, useShortFormat = false)).isEqualTo("0.98 KB") + assertThat(sut.format(1024, useShortFormat = false)).isEqualTo("1.00 KB") + assertThat(sut.format(1024 * 500, useShortFormat = false)).isEqualTo("500 KB") + assertThat(sut.format(1024 * 1024, useShortFormat = false)).isEqualTo("1.00 MB") + assertThat(sut.format(1024 * 1024 * 500, useShortFormat = false)).isEqualTo("500 MB") + assertThat(sut.format(1024 * 1024 * 1024, useShortFormat = false)).isEqualTo("1.00 GB") } + @Config(sdk = [Build.VERSION_CODES.O]) @Test fun `test api 26 long format`() { val sut = createAndroidFileSizeFormatter(sdkLevel = Build.VERSION_CODES.O) - assertThat(sut.format(1, useShortFormat = false)).isEqualTo("1.00B") - assertThat(sut.format(1000, useShortFormat = false)).isEqualTo("0.98KB") - assertThat(sut.format(1024 * 1024, useShortFormat = false)).isEqualTo("0.95MB") - assertThat(sut.format(1024 * 1024 * 1024, useShortFormat = false)).isEqualTo("0.93GB") + assertThat(sut.format(1, useShortFormat = false)).isEqualTo("1 B") + assertThat(sut.format(1000, useShortFormat = false)).isEqualTo("0.98 KB") + assertThat(sut.format(1024, useShortFormat = false)).isEqualTo("1.00 KB") + assertThat(sut.format(1024 * 500, useShortFormat = false)).isEqualTo("500 KB") + assertThat(sut.format(1024 * 1024, useShortFormat = false)).isEqualTo("1.00 MB") + assertThat(sut.format(1024 * 1024 * 500, useShortFormat = false)).isEqualTo("500 MB") + assertThat(sut.format(1024 * 1024 * 1024, useShortFormat = false)).isEqualTo("1.00 GB") } + @Config(sdk = [Build.VERSION_CODES.N]) @Test fun `test api 24 short format`() { val sut = createAndroidFileSizeFormatter(sdkLevel = Build.VERSION_CODES.N) - assertThat(sut.format(1, useShortFormat = true)).isEqualTo("1.0B") - assertThat(sut.format(1000, useShortFormat = true)).isEqualTo("0.98KB") - assertThat(sut.format(1024, useShortFormat = true)).isEqualTo("1.0KB") - assertThat(sut.format(1024 * 1024, useShortFormat = true)).isEqualTo("1.0MB") - assertThat(sut.format(1024 * 1024 * 1024, useShortFormat = true)).isEqualTo("1.0GB") + assertThat(sut.format(1, useShortFormat = true)).isEqualTo("1 B") + assertThat(sut.format(1000, useShortFormat = true)).isEqualTo("0.98 KB") + assertThat(sut.format(1024, useShortFormat = true)).isEqualTo("1.0 KB") + assertThat(sut.format(1024 * 500, useShortFormat = true)).isEqualTo("500 KB") + assertThat(sut.format(1024 * 1024, useShortFormat = true)).isEqualTo("1.0 MB") + assertThat(sut.format(1024 * 1024 * 500, useShortFormat = true)).isEqualTo("500 MB") + assertThat(sut.format(1024 * 1024 * 1024, useShortFormat = true)).isEqualTo("1.0 GB") } + @Config(sdk = [Build.VERSION_CODES.O]) @Test fun `test api 26 short format`() { val sut = createAndroidFileSizeFormatter(sdkLevel = Build.VERSION_CODES.O) - assertThat(sut.format(1, useShortFormat = true)).isEqualTo("1.0B") - assertThat(sut.format(1000, useShortFormat = true)).isEqualTo("0.98KB") - assertThat(sut.format(1024 * 1024, useShortFormat = true)).isEqualTo("0.95MB") - assertThat(sut.format(1024 * 1024 * 1024, useShortFormat = true)).isEqualTo("0.93GB") + assertThat(sut.format(1, useShortFormat = true)).isEqualTo("1 B") + assertThat(sut.format(1000, useShortFormat = true)).isEqualTo("0.98 KB") + assertThat(sut.format(1024, useShortFormat = true)).isEqualTo("1.0 KB") + assertThat(sut.format(1024 * 500, useShortFormat = true)).isEqualTo("500 KB") + assertThat(sut.format(1024 * 1024, useShortFormat = true)).isEqualTo("1.0 MB") + assertThat(sut.format(1024 * 1024 * 1024, useShortFormat = true)).isEqualTo("1.0 GB") } private fun createAndroidFileSizeFormatter(sdkLevel: Int) = AndroidFileSizeFormatter( diff --git a/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/media/VideoCompressorHelperTest.kt b/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/media/VideoCompressorHelperTest.kt index 96d15d6bfa..158dd63845 100644 --- a/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/media/VideoCompressorHelperTest.kt +++ b/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/media/VideoCompressorHelperTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/text/LinkifierHelperTest.kt b/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/text/LinkifierHelperTest.kt index 69dcaa32d2..4722994cc8 100644 --- a/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/text/LinkifierHelperTest.kt +++ b/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/text/LinkifierHelperTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottlerTest.kt b/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottlerTest.kt index f33a3f237a..ef5132d047 100644 --- a/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottlerTest.kt +++ b/libraries/androidutils/src/test/kotlin/io/element/android/libraries/androidutils/throttler/FirstThrottlerTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/build.gradle.kts b/libraries/architecture/build.gradle.kts index 55b79abca0..c745bec6d3 100644 --- a/libraries/architecture/build.gradle.kts +++ b/libraries/architecture/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AssistedNodeFactory.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AssistedNodeFactory.kt index d333eae1cd..5dfda2aa2e 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AssistedNodeFactory.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AssistedNodeFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncAction.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncAction.kt index c02c64135d..bb833162e3 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncAction.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncAction.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -32,6 +33,11 @@ sealed interface AsyncAction { data object ConfirmingNoParams : Confirming + /** + * User cancels the action, use this object to ask for confirmation. + */ + data object ConfirmingCancellation : Confirming + /** * Represents an operation that is currently ongoing. */ @@ -149,16 +155,16 @@ inline fun MutableState>.runUpdatingStateNoSuccess( * @param resultBlock a suspending function that returns a [Result]. * @return the [Result] returned by [resultBlock]. */ -@OptIn(ExperimentalContracts::class) @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") suspend inline fun runUpdatingState( state: MutableState>, errorTransform: (Throwable) -> Throwable = { it }, resultBlock: suspend () -> Result, ): Result { - contract { - callsInPlace(resultBlock, InvocationKind.EXACTLY_ONCE) - } + // Restore when the issue with contracts and AGP 8.13.x is fixed +// contract { +// callsInPlace(resultBlock, InvocationKind.EXACTLY_ONCE) +// } state.value = AsyncAction.Loading return try { resultBlock() diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncData.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncData.kt index 7085db62f3..734ff16692 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncData.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncData.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,9 +11,6 @@ package io.element.android.libraries.architecture import androidx.compose.runtime.MutableState import androidx.compose.runtime.Stable import io.element.android.libraries.core.extensions.runCatchingExceptions -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract /** * Sealed type that allows to model an asynchronous operation. @@ -133,16 +131,16 @@ suspend inline fun MutableState>.runUpdatingState( * @param resultBlock a suspending function that returns a [Result]. * @return the [Result] returned by [resultBlock]. */ -@OptIn(ExperimentalContracts::class) @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") suspend inline fun runUpdatingState( state: MutableState>, errorTransform: (Throwable) -> Throwable = { it }, resultBlock: suspend () -> Result, ): Result { - contract { - callsInPlace(resultBlock, InvocationKind.EXACTLY_ONCE) - } + // Restore when the issue with contracts and AGP 8.13.x is fixed +// contract { +// callsInPlace(resultBlock, InvocationKind.EXACTLY_ONCE) +// } val prevData = state.value.dataOrNull() state.value = AsyncData.Loading(prevData = prevData) return resultBlock().fold( @@ -162,14 +160,14 @@ suspend inline fun runUpdatingState( } inline fun AsyncData.map( - transform: (T?) -> R, + transform: (T) -> R, ): AsyncData { return when (this) { is AsyncData.Failure -> AsyncData.Failure( error = error, - prevData = transform(prevData) + prevData = prevData?.let { transform(prevData) } ) - is AsyncData.Loading -> AsyncData.Loading(transform(prevData)) + is AsyncData.Loading -> AsyncData.Loading(prevData?.let { transform(prevData) }) is AsyncData.Success -> AsyncData.Success(transform(data)) AsyncData.Uninitialized -> AsyncData.Uninitialized } diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/BaseFlowNode.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/BaseFlowNode.kt index b8d678e79c..0ec0c1debe 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/BaseFlowNode.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/BaseFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/Bindings.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/Bindings.kt index 4188b0a70a..8ff4a3a6b7 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/Bindings.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/Bindings.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/FeatureEntryPoint.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/FeatureEntryPoint.kt index d5a932b705..ad4f2c9109 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/FeatureEntryPoint.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/FeatureEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/LifecycleExt.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/LifecycleExt.kt index 115a359822..1f948b14e3 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/LifecycleExt.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/LifecycleExt.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/NodeCallback.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/NodeCallback.kt new file mode 100644 index 0000000000..e949dcaf63 --- /dev/null +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/NodeCallback.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.architecture + +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin + +inline fun Node.callback(): I { + return plugins.callback() +} + +inline fun List.callback(): I { + return requireNotNull(filterIsInstance().singleOrNull()) { "Make sure to actually pass a Callback plugin to your node" } +} diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/NodeFactories.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/NodeFactories.kt index 63cb4c4ed9..5a19118659 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/NodeFactories.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/NodeFactories.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/NodeInputs.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/NodeInputs.kt index d55d9263fe..fe9a9e1418 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/NodeInputs.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/NodeInputs.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/NodeKey.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/NodeKey.kt index b5ba343b4e..636e90e184 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/NodeKey.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/NodeKey.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/ParentNodeExt.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/ParentNodeExt.kt index 2d0aaba27b..f79ef028ba 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/ParentNodeExt.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/ParentNodeExt.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/Presenter.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/Presenter.kt index 6e94e8132f..8d69214694 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/Presenter.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/Presenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/animation/ScreenTransition.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/animation/ScreenTransition.kt index 80a8bcbcf3..19c60044af 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/animation/ScreenTransition.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/animation/ScreenTransition.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx/BackStackExt.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx/BackStackExt.kt index e689f64c19..9fceaa8297 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx/BackStackExt.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx/BackStackExt.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx/DelegateTransitionHandler.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx/DelegateTransitionHandler.kt index 642ff6fc3a..157e7cc635 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx/DelegateTransitionHandler.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx/DelegateTransitionHandler.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx/NodeExt.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx/NodeExt.kt index 3874ec2080..2763596f0f 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx/NodeExt.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx/NodeExt.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/coverage/ExcludeFromCoverage.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/coverage/ExcludeFromCoverage.kt index 9f9a6ea399..7e0e709b81 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/coverage/ExcludeFromCoverage.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/coverage/ExcludeFromCoverage.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/HideOverlayBackPressHandler.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/HideOverlayBackPressHandler.kt index 74c609a3d9..528e143f4e 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/HideOverlayBackPressHandler.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/HideOverlayBackPressHandler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/Overlay.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/Overlay.kt index 2f51553bf6..2c6cd2416d 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/Overlay.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/Overlay.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Hide.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Hide.kt index 886c65d49f..142f965125 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Hide.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Hide.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/OverlayOperation.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/OverlayOperation.kt index 4dcf048715..0659080c57 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/OverlayOperation.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/OverlayOperation.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Show.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Show.kt index 608c63556f..bece75b13a 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Show.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Show.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/test/kotlin/io/element/android/libraries/architecture/AsyncActionTest.kt b/libraries/architecture/src/test/kotlin/io/element/android/libraries/architecture/AsyncActionTest.kt index fbf892a13f..9af8af9e85 100644 --- a/libraries/architecture/src/test/kotlin/io/element/android/libraries/architecture/AsyncActionTest.kt +++ b/libraries/architecture/src/test/kotlin/io/element/android/libraries/architecture/AsyncActionTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/architecture/src/test/kotlin/io/element/android/libraries/architecture/AsyncDataKtTest.kt b/libraries/architecture/src/test/kotlin/io/element/android/libraries/architecture/AsyncDataKtTest.kt index 1551360da2..39969c0c72 100644 --- a/libraries/architecture/src/test/kotlin/io/element/android/libraries/architecture/AsyncDataKtTest.kt +++ b/libraries/architecture/src/test/kotlin/io/element/android/libraries/architecture/AsyncDataKtTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -75,22 +76,21 @@ class AsyncDataKtTest { private class TestableMutableState( value: T ) : MutableState { - @Suppress("ktlint:standard:property-naming") - private val _deque = ArrayDeque(listOf(value)) + private val deque = ArrayDeque(listOf(value)) override var value: T - get() = _deque.last() + get() = deque.last() set(value) { - _deque.addLast(value) + deque.addLast(value) } /** * Returns the states that were set in the order they were set. */ - fun popFirst(): T = _deque.removeFirst() + fun popFirst(): T = deque.removeFirst() fun assertNoMoreValues() { - assertThat(_deque).isEmpty() + assertThat(deque).isEmpty() } override operator fun component1(): T = value diff --git a/libraries/audio/api/build.gradle.kts b/libraries/audio/api/build.gradle.kts index 3a82763787..208979ff32 100644 --- a/libraries/audio/api/build.gradle.kts +++ b/libraries/audio/api/build.gradle.kts @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/audio/api/src/main/kotlin/io/element/android/libraries/audio/api/AudioFocus.kt b/libraries/audio/api/src/main/kotlin/io/element/android/libraries/audio/api/AudioFocus.kt index c363114856..9a3c178b9c 100644 --- a/libraries/audio/api/src/main/kotlin/io/element/android/libraries/audio/api/AudioFocus.kt +++ b/libraries/audio/api/src/main/kotlin/io/element/android/libraries/audio/api/AudioFocus.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/audio/impl/build.gradle.kts b/libraries/audio/impl/build.gradle.kts index f2d9157f96..419e81962c 100644 --- a/libraries/audio/impl/build.gradle.kts +++ b/libraries/audio/impl/build.gradle.kts @@ -1,9 +1,10 @@ import extension.setupDependencyInjection /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/audio/impl/src/main/kotlin/io/element/android/libraries/audio/impl/DefaultAudioFocus.kt b/libraries/audio/impl/src/main/kotlin/io/element/android/libraries/audio/impl/DefaultAudioFocus.kt index f5edc2aabb..aa945ea507 100644 --- a/libraries/audio/impl/src/main/kotlin/io/element/android/libraries/audio/impl/DefaultAudioFocus.kt +++ b/libraries/audio/impl/src/main/kotlin/io/element/android/libraries/audio/impl/DefaultAudioFocus.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,13 +16,11 @@ import android.os.Build import androidx.core.content.getSystemService import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.audio.api.AudioFocus import io.element.android.libraries.audio.api.AudioFocusRequester import io.element.android.libraries.di.annotations.ApplicationContext @ContributesBinding(AppScope::class) -@Inject class DefaultAudioFocus( @ApplicationContext private val context: Context, ) : AudioFocus { diff --git a/libraries/audio/test/build.gradle.kts b/libraries/audio/test/build.gradle.kts index 57ffffbee7..2270881ac6 100644 --- a/libraries/audio/test/build.gradle.kts +++ b/libraries/audio/test/build.gradle.kts @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/audio/test/src/main/kotlin/io/element/android/libraries/mediaplayer/test/FakeAudioFocus.kt b/libraries/audio/test/src/main/kotlin/io/element/android/libraries/mediaplayer/test/FakeAudioFocus.kt index bb628e4cf9..ecb263dbb3 100644 --- a/libraries/audio/test/src/main/kotlin/io/element/android/libraries/mediaplayer/test/FakeAudioFocus.kt +++ b/libraries/audio/test/src/main/kotlin/io/element/android/libraries/mediaplayer/test/FakeAudioFocus.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/build.gradle.kts b/libraries/compound/build.gradle.kts index cbdb09d451..ce8479c47a 100644 --- a/libraries/compound/build.gradle.kts +++ b/libraries/compound/build.gradle.kts @@ -1,9 +1,10 @@ import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,12 +19,12 @@ android { testOptions { unitTests.isIncludeAndroidResources = true } - - dependencies { - implementation(libs.showkase) - testCommonDependencies(libs) - testImplementation(libs.test.roborazzi) - testImplementation(libs.test.roborazzi.compose) - testImplementation(libs.test.roborazzi.junit) - } +} + +dependencies { + implementation(libs.showkase) + testCommonDependencies(libs) + testImplementation(libs.test.roborazzi) + testImplementation(libs.test.roborazzi.compose) + testImplementation(libs.test.roborazzi.junit) } diff --git a/libraries/compound/screenshots/Compound Icons - Dark.png b/libraries/compound/screenshots/Compound Icons - Dark.png index acac494f89..c0d816a6b9 100644 --- a/libraries/compound/screenshots/Compound Icons - Dark.png +++ b/libraries/compound/screenshots/Compound Icons - Dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6bc48b9f792da838f9fc2c2a630cbbbb906de851a138cc1ac8b7bf67b801ad84 -size 211300 +oid sha256:fa16f659aa3e7d05fa03a51d52faddc0c40c3ab52231687f8c6c8a4ba81ff6f0 +size 219813 diff --git a/libraries/compound/screenshots/Compound Icons - Light.png b/libraries/compound/screenshots/Compound Icons - Light.png index c6b76c46bb..fd105971e0 100644 --- a/libraries/compound/screenshots/Compound Icons - Light.png +++ b/libraries/compound/screenshots/Compound Icons - Light.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e85fd2c67ff42829b8580ab5c0c2af1fee40973f5c0b34ef7de00e3663cee8e4 -size 223041 +oid sha256:72fb457dc50bf1a2261502fc1da15c01ab415344e9070354d38dc7b74234d790 +size 232095 diff --git a/libraries/compound/screenshots/Compound Icons - Rtl.png b/libraries/compound/screenshots/Compound Icons - Rtl.png index ce2ffe2e88..50c98caee5 100644 --- a/libraries/compound/screenshots/Compound Icons - Rtl.png +++ b/libraries/compound/screenshots/Compound Icons - Rtl.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:406b62991171a146e891f95bcad0321ebc60cf1fe2cabc9caedbb17fb062af13 -size 224320 +oid sha256:24cfe760717881ee71f36fae1fb201e74b2c32a2f9a5aef71ef21dab69ea5366 +size 233212 diff --git a/libraries/compound/screenshots/Compound Semantic Colors - Dark HC.png b/libraries/compound/screenshots/Compound Semantic Colors - Dark HC.png index 4890b91886..b66b386ed8 100644 --- a/libraries/compound/screenshots/Compound Semantic Colors - Dark HC.png +++ b/libraries/compound/screenshots/Compound Semantic Colors - Dark HC.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:578e9b5a38791e2686a7b9ba5c461eb1d1fb29dfbe950bf46c113ad75ceac175 -size 327758 +oid sha256:c846cd10b83361c368bdbb31ed6220cc22693c3cbf52791fb369841af1e9ea48 +size 327701 diff --git a/libraries/compound/screenshots/Compound Semantic Colors - Dark.png b/libraries/compound/screenshots/Compound Semantic Colors - Dark.png index 4cc125b4c8..35d684d39b 100644 --- a/libraries/compound/screenshots/Compound Semantic Colors - Dark.png +++ b/libraries/compound/screenshots/Compound Semantic Colors - Dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4cab40fc0506c8f2a2efafb1199e85f1da3ebacb49b176e9105e3f95175f85ee -size 325565 +oid sha256:05b35fedbd53dec2cc5c4c211a8db1a56055963de69425ddae2cab5aff7e3e75 +size 325750 diff --git a/libraries/compound/screenshots/Compound Semantic Colors - Light HC.png b/libraries/compound/screenshots/Compound Semantic Colors - Light HC.png index 5a8f5a6b32..9061d2b894 100644 --- a/libraries/compound/screenshots/Compound Semantic Colors - Light HC.png +++ b/libraries/compound/screenshots/Compound Semantic Colors - Light HC.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:174f9d4ee70a29c0c8c2a01a15daeb14281530678ff7d7fb19a208bfd789533a -size 309210 +oid sha256:8d98e64eda5d6333067ccc599e99636f618331397207bb7534595e2756edb75e +size 309312 diff --git a/libraries/compound/screenshots/Compound Semantic Colors - Light.png b/libraries/compound/screenshots/Compound Semantic Colors - Light.png index f010626dda..83c00ab51c 100644 --- a/libraries/compound/screenshots/Compound Semantic Colors - Light.png +++ b/libraries/compound/screenshots/Compound Semantic Colors - Light.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7598b98462c015f2bf74b3ea3ad95fc0220b2efb9bb81ac56025cf6a158e3f8a -size 308976 +oid sha256:5ccbf1234065b182939f001eb65eca0a62adae41a2d91ef0307d27b059407178 +size 309084 diff --git a/libraries/compound/screenshots/Compound Typography.png b/libraries/compound/screenshots/Compound Typography.png index 095ad6c71d..22e2d92ebb 100644 --- a/libraries/compound/screenshots/Compound Typography.png +++ b/libraries/compound/screenshots/Compound Typography.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac84a7175c4a4897aa28eddcf722b7997c6576f612eb38fa09ffabcf7be11e00 -size 119496 +oid sha256:32b12d0b26cd016a632a4cb87b71d5efcb2c0d816bf565bc90aee9963ce2d5df +size 134117 diff --git a/libraries/compound/screenshots/Compound Vector Icons - Dark.png b/libraries/compound/screenshots/Compound Vector Icons - Dark.png index 2b535c348b..638e4a5cba 100644 --- a/libraries/compound/screenshots/Compound Vector Icons - Dark.png +++ b/libraries/compound/screenshots/Compound Vector Icons - Dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ff6dfdfab51332cad3bdfa351a4d0e305de5f899853575a8514858cc871e904 -size 83609 +oid sha256:a699170cabca6fb912d034a588b45961485afe6ef6d2c24f0ab79f10ae00c168 +size 85629 diff --git a/libraries/compound/screenshots/Compound Vector Icons - Light.png b/libraries/compound/screenshots/Compound Vector Icons - Light.png index bc29dcd24d..d60a3fdadb 100644 --- a/libraries/compound/screenshots/Compound Vector Icons - Light.png +++ b/libraries/compound/screenshots/Compound Vector Icons - Light.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ca38f5f23c282a6dc4c01a54705f71bf8c927aeac9c1df0a1f3abe50c10b1b85 -size 89336 +oid sha256:dd4b2a40fcf02d6db29cb0bc371d93236b4a0be6d4446bab86358692cddb53f5 +size 91692 diff --git a/libraries/compound/src/main/assets/theme.iife.js b/libraries/compound/src/main/assets/theme.iife.js new file mode 100644 index 0000000000..71ce6b0013 --- /dev/null +++ b/libraries/compound/src/main/assets/theme.iife.js @@ -0,0 +1 @@ +var CompoundTheme=(function(Fe){"use strict";const Xe=(e,t=0,r=1)=>Zt(Jt(t,e),r),Yt=e=>{e._clipped=!1,e._unclipped=e.slice(0);for(let t=0;t<=3;t++)t<3?((e[t]<0||e[t]>255)&&(e._clipped=!0),e[t]=Xe(e[t],0,255)):t===3&&(e[t]=Xe(e[t],0,1));return e},co={};for(let e of["Boolean","Number","String","Function","Array","Date","RegExp","Undefined","Null"])co[`[object ${e}]`]=e.toLowerCase();function P(e){return co[Object.prototype.toString.call(e)]||"object"}const j=(e,t=null)=>e.length>=3?Array.prototype.slice.call(e):P(e[0])=="object"&&t?t.split("").filter(r=>e[0][r]!==void 0).map(r=>e[0][r]):e[0],pt=e=>{if(e.length<2)return null;const t=e.length-1;return P(e[t])=="string"?e[t].toLowerCase():null},{PI:mt,min:Zt,max:Jt}=Math,we=mt*2,Wt=mt/3,Ac=mt/180,Lc=180/mt,L={format:{},autodetect:[]};let $=class{constructor(...t){const r=this;if(P(t[0])==="object"&&t[0].constructor&&t[0].constructor===this.constructor)return t[0];let n=pt(t),o=!1;if(!n){o=!0,L.sorted||(L.autodetect=L.autodetect.sort((a,s)=>s.p-a.p),L.sorted=!0);for(let a of L.autodetect)if(n=a.test(...t),n)break}if(L.format[n]){const a=L.format[n].apply(null,o?t:t.slice(0,-1));r._rgb=Yt(a)}else throw new Error("unknown format: "+t);r._rgb.length===3&&r._rgb.push(1)}toString(){return P(this.hex)=="function"?this.hex():`[${this._rgb.join(",")}]`}};const Ec="2.6.0",O=(...e)=>new O.Color(...e);O.Color=$,O.version=Ec;const Tc=(...e)=>{e=j(e,"cmyk");const[t,r,n,o]=e,a=e.length>4?e[4]:1;return o===1?[0,0,0,a]:[t>=1?0:255*(1-t)*(1-o),r>=1?0:255*(1-r)*(1-o),n>=1?0:255*(1-n)*(1-o),a]},{max:io}=Math,Sc=(...e)=>{let[t,r,n]=j(e,"rgb");t=t/255,r=r/255,n=n/255;const o=1-io(t,io(r,n)),a=o<1?1/(1-o):0,s=(1-t-o)*a,c=(1-r-o)*a,i=(1-n-o)*a;return[s,c,i,o]};$.prototype.cmyk=function(){return Sc(this._rgb)},O.cmyk=(...e)=>new $(...e,"cmyk"),L.format.cmyk=Tc,L.autodetect.push({p:2,test:(...e)=>{if(e=j(e,"cmyk"),P(e)==="array"&&e.length===4)return"cmyk"}});const Ut=e=>Math.round(e*100)/100,Pc=(...e)=>{const t=j(e,"hsla");let r=pt(e)||"lsa";return t[0]=Ut(t[0]||0),t[1]=Ut(t[1]*100)+"%",t[2]=Ut(t[2]*100)+"%",r==="hsla"||t.length>3&&t[3]<1?(t[3]=t.length>3?t[3]:1,r="hsla"):t.length=3,`${r}(${t.join(",")})`},uo=(...e)=>{e=j(e,"rgba");let[t,r,n]=e;t/=255,r/=255,n/=255;const o=Zt(t,r,n),a=Jt(t,r,n),s=(a+o)/2;let c,i;return a===o?(c=0,i=Number.NaN):c=s<.5?(a-o)/(a+o):(a-o)/(2-a-o),t==a?i=(r-n)/(a-o):r==a?i=2+(n-t)/(a-o):n==a&&(i=4+(t-r)/(a-o)),i*=60,i<0&&(i+=360),e.length>3&&e[3]!==void 0?[i,c,s,e[3]]:[i,c,s]},{round:Qt}=Math,jc=(...e)=>{const t=j(e,"rgba");let r=pt(e)||"rgb";return r.substr(0,3)=="hsl"?Pc(uo(t),r):(t[0]=Qt(t[0]),t[1]=Qt(t[1]),t[2]=Qt(t[2]),(r==="rgba"||t.length>3&&t[3]<1)&&(t[3]=t.length>3?t[3]:1,r="rgba"),`${r}(${t.slice(0,r==="rgb"?3:4).join(",")})`)},{round:er}=Math,tr=(...e)=>{e=j(e,"hsl");const[t,r,n]=e;let o,a,s;if(r===0)o=a=s=n*255;else{const c=[0,0,0],i=[0,0,0],u=n<.5?n*(1+r):n+r-n*r,l=2*n-u,f=t/360;c[0]=f+1/3,c[1]=f,c[2]=f-1/3;for(let h=0;h<3;h++)c[h]<0&&(c[h]+=1),c[h]>1&&(c[h]-=1),6*c[h]<1?i[h]=l+(u-l)*6*c[h]:2*c[h]<1?i[h]=u:3*c[h]<2?i[h]=l+(u-l)*(2/3-c[h])*6:i[h]=l;[o,a,s]=[er(i[0]*255),er(i[1]*255),er(i[2]*255)]}return e.length>3?[o,a,s,e[3]]:[o,a,s,1]},fo=/^rgb\(\s*(-?\d+),\s*(-?\d+)\s*,\s*(-?\d+)\s*\)$/,lo=/^rgba\(\s*(-?\d+),\s*(-?\d+)\s*,\s*(-?\d+)\s*,\s*([01]|[01]?\.\d+)\)$/,ho=/^rgb\(\s*(-?\d+(?:\.\d+)?)%,\s*(-?\d+(?:\.\d+)?)%\s*,\s*(-?\d+(?:\.\d+)?)%\s*\)$/,bo=/^rgba\(\s*(-?\d+(?:\.\d+)?)%,\s*(-?\d+(?:\.\d+)?)%\s*,\s*(-?\d+(?:\.\d+)?)%\s*,\s*([01]|[01]?\.\d+)\)$/,po=/^hsl\(\s*(-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)%\s*,\s*(-?\d+(?:\.\d+)?)%\s*\)$/,mo=/^hsla\(\s*(-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)%\s*,\s*(-?\d+(?:\.\d+)?)%\s*,\s*([01]|[01]?\.\d+)\)$/,{round:go}=Math,rr=e=>{e=e.toLowerCase().trim();let t;if(L.format.named)try{return L.format.named(e)}catch{}if(t=e.match(fo)){const r=t.slice(1,4);for(let n=0;n<3;n++)r[n]=+r[n];return r[3]=1,r}if(t=e.match(lo)){const r=t.slice(1,5);for(let n=0;n<4;n++)r[n]=+r[n];return r}if(t=e.match(ho)){const r=t.slice(1,4);for(let n=0;n<3;n++)r[n]=go(r[n]*2.55);return r[3]=1,r}if(t=e.match(bo)){const r=t.slice(1,5);for(let n=0;n<3;n++)r[n]=go(r[n]*2.55);return r[3]=+r[3],r}if(t=e.match(po)){const r=t.slice(1,4);r[1]*=.01,r[2]*=.01;const n=tr(r);return n[3]=1,n}if(t=e.match(mo)){const r=t.slice(1,4);r[1]*=.01,r[2]*=.01;const n=tr(r);return n[3]=+t[4],n}};rr.test=e=>fo.test(e)||lo.test(e)||ho.test(e)||bo.test(e)||po.test(e)||mo.test(e),$.prototype.css=function(e){return jc(this._rgb,e)},O.css=(...e)=>new $(...e,"css"),L.format.css=rr,L.autodetect.push({p:5,test:(e,...t)=>{if(!t.length&&P(e)==="string"&&rr.test(e))return"css"}}),L.format.gl=(...e)=>{const t=j(e,"rgba");return t[0]*=255,t[1]*=255,t[2]*=255,t},O.gl=(...e)=>new $(...e,"gl"),$.prototype.gl=function(){const e=this._rgb;return[e[0]/255,e[1]/255,e[2]/255,e[3]]};const{floor:Bc}=Math,Gc=(...e)=>{e=j(e,"hcg");let[t,r,n]=e,o,a,s;n=n*255;const c=r*255;if(r===0)o=a=s=n;else{t===360&&(t=0),t>360&&(t-=360),t<0&&(t+=360),t/=60;const i=Bc(t),u=t-i,l=n*(1-r),f=l+c*(1-u),h=l+c*u,d=l+c;switch(i){case 0:[o,a,s]=[d,h,l];break;case 1:[o,a,s]=[f,d,l];break;case 2:[o,a,s]=[l,d,h];break;case 3:[o,a,s]=[l,f,d];break;case 4:[o,a,s]=[h,l,d];break;case 5:[o,a,s]=[d,l,f];break}}return[o,a,s,e.length>3?e[3]:1]},Ic=(...e)=>{const[t,r,n]=j(e,"rgb"),o=Zt(t,r,n),a=Jt(t,r,n),s=a-o,c=s*100/255,i=o/(255-s)*100;let u;return s===0?u=Number.NaN:(t===a&&(u=(r-n)/s),r===a&&(u=2+(n-t)/s),n===a&&(u=4+(t-r)/s),u*=60,u<0&&(u+=360)),[u,c,i]};$.prototype.hcg=function(){return Ic(this._rgb)},O.hcg=(...e)=>new $(...e,"hcg"),L.format.hcg=Gc,L.autodetect.push({p:1,test:(...e)=>{if(e=j(e,"hcg"),P(e)==="array"&&e.length===3)return"hcg"}});const zc=/^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/,Fc=/^#?([A-Fa-f0-9]{8}|[A-Fa-f0-9]{4})$/,vo=e=>{if(e.match(zc)){(e.length===4||e.length===7)&&(e=e.substr(1)),e.length===3&&(e=e.split(""),e=e[0]+e[0]+e[1]+e[1]+e[2]+e[2]);const t=parseInt(e,16),r=t>>16,n=t>>8&255,o=t&255;return[r,n,o,1]}if(e.match(Fc)){(e.length===5||e.length===9)&&(e=e.substr(1)),e.length===4&&(e=e.split(""),e=e[0]+e[0]+e[1]+e[1]+e[2]+e[2]+e[3]+e[3]);const t=parseInt(e,16),r=t>>24&255,n=t>>16&255,o=t>>8&255,a=Math.round((t&255)/255*100)/100;return[r,n,o,a]}throw new Error(`unknown hex color: ${e}`)},{round:gt}=Math,_o=(...e)=>{let[t,r,n,o]=j(e,"rgba"),a=pt(e)||"auto";o===void 0&&(o=1),a==="auto"&&(a=o<1?"rgba":"rgb"),t=gt(t),r=gt(r),n=gt(n);let c="000000"+(t<<16|r<<8|n).toString(16);c=c.substr(c.length-6);let i="0"+gt(o*255).toString(16);switch(i=i.substr(i.length-2),a.toLowerCase()){case"rgba":return`#${c}${i}`;case"argb":return`#${i}${c}`;default:return`#${c}`}};$.prototype.hex=function(e){return _o(this._rgb,e)},O.hex=(...e)=>new $(...e,"hex"),L.format.hex=vo,L.autodetect.push({p:4,test:(e,...t)=>{if(!t.length&&P(e)==="string"&&[3,4,5,6,7,8,9].indexOf(e.length)>=0)return"hex"}});const{cos:Ke}=Math,Xc=(...e)=>{e=j(e,"hsi");let[t,r,n]=e,o,a,s;return isNaN(t)&&(t=0),isNaN(r)&&(r=0),t>360&&(t-=360),t<0&&(t+=360),t/=360,t<1/3?(s=(1-r)/3,o=(1+r*Ke(we*t)/Ke(Wt-we*t))/3,a=1-(s+o)):t<2/3?(t-=1/3,o=(1-r)/3,a=(1+r*Ke(we*t)/Ke(Wt-we*t))/3,s=1-(o+a)):(t-=2/3,a=(1-r)/3,s=(1+r*Ke(we*t)/Ke(Wt-we*t))/3,o=1-(a+s)),o=Xe(n*o*3),a=Xe(n*a*3),s=Xe(n*s*3),[o*255,a*255,s*255,e.length>3?e[3]:1]},{min:Kc,sqrt:Dc,acos:Vc}=Math,Yc=(...e)=>{let[t,r,n]=j(e,"rgb");t/=255,r/=255,n/=255;let o;const a=Kc(t,r,n),s=(t+r+n)/3,c=s>0?1-a/s:0;return c===0?o=NaN:(o=(t-r+(t-n))/2,o/=Dc((t-r)*(t-r)+(t-n)*(r-n)),o=Vc(o),n>r&&(o=we-o),o/=we),[o*360,c,s]};$.prototype.hsi=function(){return Yc(this._rgb)},O.hsi=(...e)=>new $(...e,"hsi"),L.format.hsi=Xc,L.autodetect.push({p:2,test:(...e)=>{if(e=j(e,"hsi"),P(e)==="array"&&e.length===3)return"hsi"}}),$.prototype.hsl=function(){return uo(this._rgb)},O.hsl=(...e)=>new $(...e,"hsl"),L.format.hsl=tr,L.autodetect.push({p:2,test:(...e)=>{if(e=j(e,"hsl"),P(e)==="array"&&e.length===3)return"hsl"}});const{floor:Zc}=Math,Jc=(...e)=>{e=j(e,"hsv");let[t,r,n]=e,o,a,s;if(n*=255,r===0)o=a=s=n;else{t===360&&(t=0),t>360&&(t-=360),t<0&&(t+=360),t/=60;const c=Zc(t),i=t-c,u=n*(1-r),l=n*(1-r*i),f=n*(1-r*(1-i));switch(c){case 0:[o,a,s]=[n,f,u];break;case 1:[o,a,s]=[l,n,u];break;case 2:[o,a,s]=[u,n,f];break;case 3:[o,a,s]=[u,l,n];break;case 4:[o,a,s]=[f,u,n];break;case 5:[o,a,s]=[n,u,l];break}}return[o,a,s,e.length>3?e[3]:1]},{min:Wc,max:Uc}=Math,Qc=(...e)=>{e=j(e,"rgb");let[t,r,n]=e;const o=Wc(t,r,n),a=Uc(t,r,n),s=a-o;let c,i,u;return u=a/255,a===0?(c=Number.NaN,i=0):(i=s/a,t===a&&(c=(r-n)/s),r===a&&(c=2+(n-t)/s),n===a&&(c=4+(t-r)/s),c*=60,c<0&&(c+=360)),[c,i,u]};$.prototype.hsv=function(){return Qc(this._rgb)},O.hsv=(...e)=>new $(...e,"hsv"),L.format.hsv=Jc,L.autodetect.push({p:2,test:(...e)=>{if(e=j(e,"hsv"),P(e)==="array"&&e.length===3)return"hsv"}});const se={Kn:18,Xn:.95047,Yn:1,Zn:1.08883,t0:.137931034,t1:.206896552,t2:.12841855,t3:.008856452},{pow:e0}=Math,yo=(...e)=>{e=j(e,"lab");const[t,r,n]=e;let o,a,s,c,i,u;return a=(t+16)/116,o=isNaN(r)?a:a+r/500,s=isNaN(n)?a:a-n/200,a=se.Yn*or(a),o=se.Xn*or(o),s=se.Zn*or(s),c=nr(3.2404542*o-1.5371385*a-.4985314*s),i=nr(-.969266*o+1.8760108*a+.041556*s),u=nr(.0556434*o-.2040259*a+1.0572252*s),[c,i,u,e.length>3?e[3]:1]},nr=e=>255*(e<=.00304?12.92*e:1.055*e0(e,1/2.4)-.055),or=e=>e>se.t1?e*e*e:se.t2*(e-se.t0),{pow:wo}=Math,ko=(...e)=>{const[t,r,n]=j(e,"rgb"),[o,a,s]=t0(t,r,n),c=116*a-16;return[c<0?0:c,500*(o-a),200*(a-s)]},sr=e=>(e/=255)<=.04045?e/12.92:wo((e+.055)/1.055,2.4),ar=e=>e>se.t3?wo(e,1/3):e/se.t2+se.t0,t0=(e,t,r)=>{e=sr(e),t=sr(t),r=sr(r);const n=ar((.4124564*e+.3575761*t+.1804375*r)/se.Xn),o=ar((.2126729*e+.7151522*t+.072175*r)/se.Yn),a=ar((.0193339*e+.119192*t+.9503041*r)/se.Zn);return[n,o,a]};$.prototype.lab=function(){return ko(this._rgb)},O.lab=(...e)=>new $(...e,"lab"),L.format.lab=yo,L.autodetect.push({p:2,test:(...e)=>{if(e=j(e,"lab"),P(e)==="array"&&e.length===3)return"lab"}});const{sin:r0,cos:n0}=Math,$o=(...e)=>{let[t,r,n]=j(e,"lch");return isNaN(n)&&(n=0),n=n*Ac,[t,n0(n)*r,r0(n)*r]},Co=(...e)=>{e=j(e,"lch");const[t,r,n]=e,[o,a,s]=$o(t,r,n),[c,i,u]=yo(o,a,s);return[c,i,u,e.length>3?e[3]:1]},o0=(...e)=>{const t=j(e,"hcl").reverse();return Co(...t)},{sqrt:s0,atan2:a0,round:c0}=Math,xo=(...e)=>{const[t,r,n]=j(e,"lab"),o=s0(r*r+n*n);let a=(a0(n,r)*Lc+360)%360;return c0(o*1e4)===0&&(a=Number.NaN),[t,o,a]},Ro=(...e)=>{const[t,r,n]=j(e,"rgb"),[o,a,s]=ko(t,r,n);return xo(o,a,s)};$.prototype.lch=function(){return Ro(this._rgb)},$.prototype.hcl=function(){return Ro(this._rgb).reverse()},O.lch=(...e)=>new $(...e,"lch"),O.hcl=(...e)=>new $(...e,"hcl"),L.format.lch=Co,L.format.hcl=o0,["lch","hcl"].forEach(e=>L.autodetect.push({p:2,test:(...t)=>{if(t=j(t,e),P(t)==="array"&&t.length===3)return e}}));const De={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",laserlemon:"#ffff54",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrod:"#fafad2",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",maroon2:"#7f0000",maroon3:"#b03060",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",purple2:"#7f007f",purple3:"#a020f0",rebeccapurple:"#663399",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};$.prototype.name=function(){const e=_o(this._rgb,"rgb");for(let t of Object.keys(De))if(De[t]===e)return t.toLowerCase();return e},L.format.named=e=>{if(e=e.toLowerCase(),De[e])return vo(De[e]);throw new Error("unknown color name: "+e)},L.autodetect.push({p:5,test:(e,...t)=>{if(!t.length&&P(e)==="string"&&De[e.toLowerCase()])return"named"}});const i0=e=>{if(P(e)=="number"&&e>=0&&e<=16777215){const t=e>>16,r=e>>8&255,n=e&255;return[t,r,n,1]}throw new Error("unknown num color: "+e)},u0=(...e)=>{const[t,r,n]=j(e,"rgb");return(t<<16)+(r<<8)+n};$.prototype.num=function(){return u0(this._rgb)},O.num=(...e)=>new $(...e,"num"),L.format.num=i0,L.autodetect.push({p:5,test:(...e)=>{if(e.length===1&&P(e[0])==="number"&&e[0]>=0&&e[0]<=16777215)return"num"}});const{round:Ho}=Math;$.prototype.rgb=function(e=!0){return e===!1?this._rgb.slice(0,3):this._rgb.slice(0,3).map(Ho)},$.prototype.rgba=function(e=!0){return this._rgb.slice(0,4).map((t,r)=>r<3?e===!1?t:Ho(t):t)},O.rgb=(...e)=>new $(...e,"rgb"),L.format.rgb=(...e)=>{const t=j(e,"rgba");return t[3]===void 0&&(t[3]=1),t},L.autodetect.push({p:3,test:(...e)=>{if(e=j(e,"rgba"),P(e)==="array"&&(e.length===3||e.length===4&&P(e[3])=="number"&&e[3]>=0&&e[3]<=1))return"rgb"}});const{log:vt}=Math,qo=e=>{const t=e/100;let r,n,o;return t<66?(r=255,n=t<6?0:-155.25485562709179-.44596950469579133*(n=t-2)+104.49216199393888*vt(n),o=t<20?0:-254.76935184120902+.8274096064007395*(o=t-10)+115.67994401066147*vt(o)):(r=351.97690566805693+.114206453784165*(r=t-55)-40.25366309332127*vt(r),n=325.4494125711974+.07943456536662342*(n=t-50)-28.0852963507957*vt(n),o=255),[r,n,o,1]},{round:f0}=Math,l0=(...e)=>{const t=j(e,"rgb"),r=t[0],n=t[2];let o=1e3,a=4e4;const s=.4;let c;for(;a-o>s;){c=(a+o)*.5;const i=qo(c);i[2]/i[0]>=n/r?a=c:o=c}return f0(c)};$.prototype.temp=$.prototype.kelvin=$.prototype.temperature=function(){return l0(this._rgb)},O.temp=O.kelvin=O.temperature=(...e)=>new $(...e,"temp"),L.format.temp=L.format.kelvin=L.format.temperature=qo;const{pow:_t,sign:h0}=Math,Mo=(...e)=>{e=j(e,"lab");const[t,r,n]=e,o=_t(t+.3963377774*r+.2158037573*n,3),a=_t(t-.1055613458*r-.0638541728*n,3),s=_t(t-.0894841775*r-1.291485548*n,3);return[255*cr(4.0767416621*o-3.3077115913*a+.2309699292*s),255*cr(-1.2684380046*o+2.6097574011*a-.3413193965*s),255*cr(-.0041960863*o-.7034186147*a+1.707614701*s),e.length>3?e[3]:1]};function cr(e){const t=Math.abs(e);return t>.0031308?(h0(e)||1)*(1.055*_t(t,.4166666666666667)-.055):e*12.92}const{cbrt:ir,pow:d0,sign:b0}=Math,Oo=(...e)=>{const[t,r,n]=j(e,"rgb"),[o,a,s]=[ur(t/255),ur(r/255),ur(n/255)],c=ir(.4122214708*o+.5363325363*a+.0514459929*s),i=ir(.2119034982*o+.6806995451*a+.1073969566*s),u=ir(.0883024619*o+.2817188376*a+.6299787005*s);return[.2104542553*c+.793617785*i-.0040720468*u,1.9779984951*c-2.428592205*i+.4505937099*u,.0259040371*c+.7827717662*i-.808675766*u]};function ur(e){const t=Math.abs(e);return t<.04045?e/12.92:(b0(e)||1)*d0((t+.055)/1.055,2.4)}$.prototype.oklab=function(){return Oo(this._rgb)},O.oklab=(...e)=>new $(...e,"oklab"),L.format.oklab=Mo,L.autodetect.push({p:3,test:(...e)=>{if(e=j(e,"oklab"),P(e)==="array"&&e.length===3)return"oklab"}});const p0=(...e)=>{e=j(e,"lch");const[t,r,n]=e,[o,a,s]=$o(t,r,n),[c,i,u]=Mo(o,a,s);return[c,i,u,e.length>3?e[3]:1]},m0=(...e)=>{const[t,r,n]=j(e,"rgb"),[o,a,s]=Oo(t,r,n);return xo(o,a,s)};$.prototype.oklch=function(){return m0(this._rgb)},O.oklch=(...e)=>new $(...e,"oklch"),L.format.oklch=p0,L.autodetect.push({p:3,test:(...e)=>{if(e=j(e,"oklch"),P(e)==="array"&&e.length===3)return"oklch"}}),$.prototype.alpha=function(e,t=!1){return e!==void 0&&P(e)==="number"?t?(this._rgb[3]=e,this):new $([this._rgb[0],this._rgb[1],this._rgb[2],e],"rgb"):this._rgb[3]},$.prototype.clipped=function(){return this._rgb._clipped||!1},$.prototype.darken=function(e=1){const t=this,r=t.lab();return r[0]-=se.Kn*e,new $(r,"lab").alpha(t.alpha(),!0)},$.prototype.brighten=function(e=1){return this.darken(-e)},$.prototype.darker=$.prototype.darken,$.prototype.brighter=$.prototype.brighten,$.prototype.get=function(e){const[t,r]=e.split("."),n=this[t]();if(r){const o=t.indexOf(r)-(t.substr(0,2)==="ok"?2:0);if(o>-1)return n[o];throw new Error(`unknown channel ${r} in mode ${t}`)}else return n};const{pow:g0}=Math,v0=1e-7,_0=20;$.prototype.luminance=function(e,t="rgb"){if(e!==void 0&&P(e)==="number"){if(e===0)return new $([0,0,0,this._rgb[3]],"rgb");if(e===1)return new $([255,255,255,this._rgb[3]],"rgb");let r=this.luminance(),n=_0;const o=(s,c)=>{const i=s.interpolate(c,.5,t),u=i.luminance();return Math.abs(e-u)e?o(s,i):o(i,c)},a=(r>e?o(new $([0,0,0]),this):o(this,new $([255,255,255]))).rgb();return new $([...a,this._rgb[3]])}return y0(...this._rgb.slice(0,3))};const y0=(e,t,r)=>(e=fr(e),t=fr(t),r=fr(r),.2126*e+.7152*t+.0722*r),fr=e=>(e/=255,e<=.03928?e/12.92:g0((e+.055)/1.055,2.4)),re={},ct=(e,t,r=.5,...n)=>{let o=n[0]||"lrgb";if(!re[o]&&!n.length&&(o=Object.keys(re)[0]),!re[o])throw new Error(`interpolation mode ${o} is not defined`);return P(e)!=="object"&&(e=new $(e)),P(t)!=="object"&&(t=new $(t)),re[o](e,t,r).alpha(e.alpha()+r*(t.alpha()-e.alpha()))};$.prototype.mix=$.prototype.interpolate=function(e,t=.5,...r){return ct(this,e,t,...r)},$.prototype.premultiply=function(e=!1){const t=this._rgb,r=t[3];return e?(this._rgb=[t[0]*r,t[1]*r,t[2]*r,r],this):new $([t[0]*r,t[1]*r,t[2]*r,r],"rgb")},$.prototype.saturate=function(e=1){const t=this,r=t.lch();return r[1]+=se.Kn*e,r[1]<0&&(r[1]=0),new $(r,"lch").alpha(t.alpha(),!0)},$.prototype.desaturate=function(e=1){return this.saturate(-e)},$.prototype.set=function(e,t,r=!1){const[n,o]=e.split("."),a=this[n]();if(o){const s=n.indexOf(o)-(n.substr(0,2)==="ok"?2:0);if(s>-1){if(P(t)=="string")switch(t.charAt(0)){case"+":a[s]+=+t;break;case"-":a[s]+=+t;break;case"*":a[s]*=+t.substr(1);break;case"/":a[s]/=+t.substr(1);break;default:a[s]=+t}else if(P(t)==="number")a[s]=t;else throw new Error("unsupported value for Color.set");const c=new $(a,n);return r?(this._rgb=c._rgb,this):c}throw new Error(`unknown channel ${o} in mode ${n}`)}else return a},$.prototype.tint=function(e=.5,...t){return ct(this,"white",e,...t)},$.prototype.shade=function(e=.5,...t){return ct(this,"black",e,...t)};const w0=(e,t,r)=>{const n=e._rgb,o=t._rgb;return new $(n[0]+r*(o[0]-n[0]),n[1]+r*(o[1]-n[1]),n[2]+r*(o[2]-n[2]),"rgb")};re.rgb=w0;const{sqrt:lr,pow:Ve}=Math,k0=(e,t,r)=>{const[n,o,a]=e._rgb,[s,c,i]=t._rgb;return new $(lr(Ve(n,2)*(1-r)+Ve(s,2)*r),lr(Ve(o,2)*(1-r)+Ve(c,2)*r),lr(Ve(a,2)*(1-r)+Ve(i,2)*r),"rgb")};re.lrgb=k0;const $0=(e,t,r)=>{const n=e.lab(),o=t.lab();return new $(n[0]+r*(o[0]-n[0]),n[1]+r*(o[1]-n[1]),n[2]+r*(o[2]-n[2]),"lab")};re.lab=$0;const Ye=(e,t,r,n)=>{let o,a;n==="hsl"?(o=e.hsl(),a=t.hsl()):n==="hsv"?(o=e.hsv(),a=t.hsv()):n==="hcg"?(o=e.hcg(),a=t.hcg()):n==="hsi"?(o=e.hsi(),a=t.hsi()):n==="lch"||n==="hcl"?(n="hcl",o=e.hcl(),a=t.hcl()):n==="oklch"&&(o=e.oklch().reverse(),a=t.oklch().reverse());let s,c,i,u,l,f;(n.substr(0,1)==="h"||n==="oklch")&&([s,i,l]=o,[c,u,f]=a);let h,d,b,_;return!isNaN(s)&&!isNaN(c)?(c>s&&c-s>180?_=c-(s+360):c180?_=c+360-s:_=c-s,d=s+r*_):isNaN(s)?isNaN(c)?d=Number.NaN:(d=c,(l==1||l==0)&&n!="hsv"&&(h=u)):(d=s,(f==1||f==0)&&n!="hsv"&&(h=i)),h===void 0&&(h=i+r*(u-i)),b=l+r*(f-l),n==="oklch"?new $([b,h,d],n):new $([d,h,b],n)},No=(e,t,r)=>Ye(e,t,r,"lch");re.lch=No,re.hcl=No;const C0=(e,t,r)=>{const n=e.num(),o=t.num();return new $(n+r*(o-n),"num")};re.num=C0;const x0=(e,t,r)=>Ye(e,t,r,"hcg");re.hcg=x0;const R0=(e,t,r)=>Ye(e,t,r,"hsi");re.hsi=R0;const H0=(e,t,r)=>Ye(e,t,r,"hsl");re.hsl=H0;const q0=(e,t,r)=>Ye(e,t,r,"hsv");re.hsv=q0;const M0=(e,t,r)=>{const n=e.oklab(),o=t.oklab();return new $(n[0]+r*(o[0]-n[0]),n[1]+r*(o[1]-n[1]),n[2]+r*(o[2]-n[2]),"oklab")};re.oklab=M0;const O0=(e,t,r)=>Ye(e,t,r,"oklch");re.oklch=O0;const{pow:hr,sqrt:dr,PI:br,cos:Ao,sin:Lo,atan2:N0}=Math,A0=(e,t="lrgb",r=null)=>{const n=e.length;r||(r=Array.from(new Array(n)).map(()=>1));const o=n/r.reduce(function(f,h){return f+h});if(r.forEach((f,h)=>{r[h]*=o}),e=e.map(f=>new $(f)),t==="lrgb")return L0(e,r);const a=e.shift(),s=a.get(t),c=[];let i=0,u=0;for(let f=0;f{const d=f.get(t);l+=f.alpha()*r[h+1];for(let b=0;b=360;)h-=360;s[f]=h}else s[f]=s[f]/c[f];return l/=n,new $(s,t).alpha(l>.99999?1:l,!0)},L0=(e,t)=>{const r=e.length,n=[0,0,0,0];for(let o=0;o.9999999&&(n[3]=1),new $(Yt(n))},{pow:E0}=Math;function yt(e){let t="rgb",r=O("#ccc"),n=0,o=[0,1],a=[],s=[0,0],c=!1,i=[],u=!1,l=0,f=1,h=!1,d={},b=!0,_=1;const g=function(m){if(m=m||["#fff","#000"],m&&P(m)==="string"&&O.brewer&&O.brewer[m.toLowerCase()]&&(m=O.brewer[m.toLowerCase()]),P(m)==="array"){m.length===1&&(m=[m[0],m[0]]),m=m.slice(0);for(let p=0;p=c[y];)y++;return y-1}return 0};let H=m=>m,x=m=>m;const N=function(m,p){let y,w;if(p==null&&(p=!1),isNaN(m)||m===null)return r;p?w=m:c&&c.length>2?w=v(m)/(c.length-2):f!==l?w=(m-l)/(f-l):w=1,w=x(w),p||(w=H(w)),_!==1&&(w=E0(w,_)),w=s[0]+w*(1-s[0]-s[1]),w=Xe(w,0,1);const C=Math.floor(w*1e4);if(b&&d[C])y=d[C];else{if(P(i)==="array")for(let q=0;q=M&&q===a.length-1){y=i[q];break}if(w>M&&wd={};g(e);const R=function(m){const p=O(N(m));return u&&p[u]?p[u]():p};return R.classes=function(m){if(m!=null){if(P(m)==="array")c=m,o=[m[0],m[m.length-1]];else{const p=O.analyze(o);m===0?c=[p.min,p.max]:c=O.limits(p,"e",m)}return R}return c},R.domain=function(m){if(!arguments.length)return o;l=m[0],f=m[m.length-1],a=[];const p=i.length;if(m.length===p&&l!==f)for(let y of Array.from(m))a.push((y-l)/(f-l));else{for(let y=0;y2){const y=m.map((C,q)=>q/(m.length-1)),w=m.map(C=>(C-l)/(f-l));w.every((C,q)=>y[q]===C)||(x=C=>{if(C<=0||C>=1)return C;let q=0;for(;C>=w[q+1];)q++;const M=(C-w[q])/(w[q+1]-w[q]);return y[q]+M*(y[q+1]-y[q])})}}return o=[l,f],R},R.mode=function(m){return arguments.length?(t=m,A(),R):t},R.range=function(m,p){return g(m),R},R.out=function(m){return u=m,R},R.spread=function(m){return arguments.length?(n=m,R):n},R.correctLightness=function(m){return m==null&&(m=!0),h=m,A(),h?H=function(p){const y=N(0,!0).lab()[0],w=N(1,!0).lab()[0],C=y>w;let q=N(p,!0).lab()[0];const M=y+(w-y)*p;let S=q-M,X=0,D=1,U=20;for(;Math.abs(S)>.01&&U-- >0;)(function(){return C&&(S*=-1),S<0?(X=p,p+=(D-p)*.5):(D=p,p+=(X-p)*.5),q=N(p,!0).lab()[0],S=q-M})();return p}:H=p=>p,R},R.padding=function(m){return m!=null?(P(m)==="number"&&(m=[m,m]),s=m,R):s},R.colors=function(m,p){arguments.length<2&&(p="hex");let y=[];if(arguments.length===0)y=i.slice(0);else if(m===1)y=[R(.5)];else if(m>1){const w=o[0],C=o[1]-w;y=T0(0,m).map(q=>R(w+q/(m-1)*C))}else{e=[];let w=[];if(c&&c.length>2)for(let C=1,q=c.length,M=1<=q;M?Cq;M?C++:C--)w.push((c[C-1]+c[C])*.5);else w=o;y=w.map(C=>R(C))}return O[p]&&(y=y.map(w=>w[p]())),y},R.cache=function(m){return m!=null?(b=m,R):b},R.gamma=function(m){return m!=null?(_=m,R):_},R.nodata=function(m){return m!=null?(r=O(m),R):r},R}function T0(e,t,r){let n=[],o=ea;o?s++:s--)n.push(s);return n}const S0=function(e){let t=[1,1];for(let r=1;rnew $(a)),e.length===2)[r,n]=e.map(a=>a.lab()),t=function(a){const s=[0,1,2].map(c=>r[c]+a*(n[c]-r[c]));return new $(s,"lab")};else if(e.length===3)[r,n,o]=e.map(a=>a.lab()),t=function(a){const s=[0,1,2].map(c=>(1-a)*(1-a)*r[c]+2*(1-a)*a*n[c]+a*a*o[c]);return new $(s,"lab")};else if(e.length===4){let a;[r,n,o,a]=e.map(s=>s.lab()),t=function(s){const c=[0,1,2].map(i=>(1-s)*(1-s)*(1-s)*r[i]+3*(1-s)*(1-s)*s*n[i]+3*(1-s)*s*s*o[i]+s*s*s*a[i]);return new $(c,"lab")}}else if(e.length>=5){let a,s,c;a=e.map(i=>i.lab()),c=e.length-1,s=S0(c),t=function(i){const u=1-i,l=[0,1,2].map(f=>a.reduce((h,d,b)=>h+s[b]*u**(c-b)*i**b*d[f],0));return new $(l,"lab")}}else throw new RangeError("No point in running bezier with only one color.");return t},j0=e=>{const t=P0(e);return t.scale=()=>yt(t),t},de=(e,t,r)=>{if(!de[r])throw new Error("unknown blend mode "+r);return de[r](e,t)},Ne=e=>(t,r)=>{const n=O(r).rgb(),o=O(t).rgb();return O.rgb(e(n,o))},Ae=e=>(t,r)=>{const n=[];return n[0]=e(t[0],r[0]),n[1]=e(t[1],r[1]),n[2]=e(t[2],r[2]),n},B0=e=>e,G0=(e,t)=>e*t/255,I0=(e,t)=>e>t?t:e,z0=(e,t)=>e>t?e:t,F0=(e,t)=>255*(1-(1-e/255)*(1-t/255)),X0=(e,t)=>t<128?2*e*t/255:255*(1-2*(1-e/255)*(1-t/255)),K0=(e,t)=>255*(1-(1-t/255)/(e/255)),D0=(e,t)=>e===255?255:(e=255*(t/255)/(1-e/255),e>255?255:e);de.normal=Ne(Ae(B0)),de.multiply=Ne(Ae(G0)),de.screen=Ne(Ae(F0)),de.overlay=Ne(Ae(X0)),de.darken=Ne(Ae(I0)),de.lighten=Ne(Ae(z0)),de.dodge=Ne(Ae(D0)),de.burn=Ne(Ae(K0));const{pow:V0,sin:Y0,cos:Z0}=Math;function J0(e=300,t=-1.5,r=1,n=1,o=[0,1]){let a=0,s;P(o)==="array"?s=o[1]-o[0]:(s=0,o=[o,o]);const c=function(i){const u=we*((e+120)/360+t*i),l=V0(o[0]+s*i,n),h=(a!==0?r[0]+i*a:r)*l*(1-l)/2,d=Z0(u),b=Y0(u),_=l+h*(-.14861*d+1.78277*b),g=l+h*(-.29227*d-.90649*b),v=l+h*(1.97294*d);return O(Yt([_*255,g*255,v*255,1]))};return c.start=function(i){return i==null?e:(e=i,c)},c.rotations=function(i){return i==null?t:(t=i,c)},c.gamma=function(i){return i==null?n:(n=i,c)},c.hue=function(i){return i==null?r:(r=i,P(r)==="array"?(a=r[1]-r[0],a===0&&(r=r[1])):a=0,c)},c.lightness=function(i){return i==null?o:(P(i)==="array"?(o=i,s=i[1]-i[0]):(o=[i,i],s=0),c)},c.scale=()=>O.scale(c),c.hue(r),c}const W0="0123456789abcdef",{floor:U0,random:Q0}=Math,ei=()=>{let e="#";for(let t=0;t<6;t++)e+=W0.charAt(U0(Q0()*16));return new $(e,"hex")},{log:Eo,pow:ti,floor:ri,abs:ni}=Math;function To(e,t=null){const r={min:Number.MAX_VALUE,max:Number.MAX_VALUE*-1,sum:0,values:[],count:0};return P(e)==="object"&&(e=Object.values(e)),e.forEach(n=>{t&&P(n)==="object"&&(n=n[t]),n!=null&&!isNaN(n)&&(r.values.push(n),r.sum+=n,nr.max&&(r.max=n),r.count+=1)}),r.domain=[r.min,r.max],r.limits=(n,o)=>So(r,n,o),r}function So(e,t="equal",r=7){P(e)=="array"&&(e=To(e));const{min:n,max:o}=e,a=e.values.sort((c,i)=>c-i);if(r===1)return[n,o];const s=[];if(t.substr(0,1)==="c"&&(s.push(n),s.push(o)),t.substr(0,1)==="e"){s.push(n);for(let c=1;c 0");const c=Math.LOG10E*Eo(n),i=Math.LOG10E*Eo(o);s.push(n);for(let u=1;u200&&(f=!1)}const b={};for(let g=0;gg-v),s.push(_[0]);for(let g=1;g<_.length;g+=2){const v=_[g];!isNaN(v)&&s.indexOf(v)===-1&&s.push(v)}}return s}const oi=(e,t)=>{e=new $(e),t=new $(t);const r=e.luminance(),n=t.luminance();return r>n?(r+.05)/(n+.05):(n+.05)/(r+.05)},{sqrt:ke,pow:V,min:si,max:ai,atan2:Po,abs:jo,cos:wt,sin:Bo,exp:ci,PI:Go}=Math;function ii(e,t,r=1,n=1,o=1){var a=function(he){return 360*he/(2*Go)},s=function(he){return 2*Go*he/360};e=new $(e),t=new $(t);const[c,i,u]=Array.from(e.lab()),[l,f,h]=Array.from(t.lab()),d=(c+l)/2,b=ke(V(i,2)+V(u,2)),_=ke(V(f,2)+V(h,2)),g=(b+_)/2,v=.5*(1-ke(V(g,7)/(V(g,7)+V(25,7)))),H=i*(1+v),x=f*(1+v),N=ke(V(H,2)+V(u,2)),A=ke(V(x,2)+V(h,2)),R=(N+A)/2,m=a(Po(u,H)),p=a(Po(h,x)),y=m>=0?m:m+360,w=p>=0?p:p+360,C=jo(y-w)>180?(y+w+360)/2:(y+w)/2,q=1-.17*wt(s(C-30))+.24*wt(s(2*C))+.32*wt(s(3*C+6))-.2*wt(s(4*C-63));let M=w-y;M=jo(M)<=180?M:w<=y?M+360:M-360,M=2*ke(N*A)*Bo(s(M)/2);const S=l-c,X=A-N,D=1+.015*V(d-50,2)/ke(20+V(d-50,2)),U=1+.045*R,ce=1+.015*R*q,ye=30*ci(-V((C-275)/25,2)),le=-(2*ke(V(R,7)/(V(R,7)+V(25,7))))*Bo(2*s(ye)),qe=ke(V(S/(r*D),2)+V(X/(n*U),2)+V(M/(o*ce),2)+le*(X/(n*U))*(M/(o*ce)));return ai(0,si(100,qe))}function ui(e,t,r="lab"){e=new $(e),t=new $(t);const n=e.get(r),o=t.get(r);let a=0;for(let s in n){const c=(n[s]||0)-(o[s]||0);a+=c*c}return Math.sqrt(a)}const fi=(...e)=>{try{return new $(...e),!0}catch{return!1}},li={cool(){return yt([O.hsl(180,1,.9),O.hsl(250,.7,.4)])},hot(){return yt(["#000","#f00","#ff0","#fff"]).mode("rgb")}},kt={OrRd:["#fff7ec","#fee8c8","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#b30000","#7f0000"],PuBu:["#fff7fb","#ece7f2","#d0d1e6","#a6bddb","#74a9cf","#3690c0","#0570b0","#045a8d","#023858"],BuPu:["#f7fcfd","#e0ecf4","#bfd3e6","#9ebcda","#8c96c6","#8c6bb1","#88419d","#810f7c","#4d004b"],Oranges:["#fff5eb","#fee6ce","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#a63603","#7f2704"],BuGn:["#f7fcfd","#e5f5f9","#ccece6","#99d8c9","#66c2a4","#41ae76","#238b45","#006d2c","#00441b"],YlOrBr:["#ffffe5","#fff7bc","#fee391","#fec44f","#fe9929","#ec7014","#cc4c02","#993404","#662506"],YlGn:["#ffffe5","#f7fcb9","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#006837","#004529"],Reds:["#fff5f0","#fee0d2","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#a50f15","#67000d"],RdPu:["#fff7f3","#fde0dd","#fcc5c0","#fa9fb5","#f768a1","#dd3497","#ae017e","#7a0177","#49006a"],Greens:["#f7fcf5","#e5f5e0","#c7e9c0","#a1d99b","#74c476","#41ab5d","#238b45","#006d2c","#00441b"],YlGnBu:["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"],Purples:["#fcfbfd","#efedf5","#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#54278f","#3f007d"],GnBu:["#f7fcf0","#e0f3db","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#0868ac","#084081"],Greys:["#ffffff","#f0f0f0","#d9d9d9","#bdbdbd","#969696","#737373","#525252","#252525","#000000"],YlOrRd:["#ffffcc","#ffeda0","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#bd0026","#800026"],PuRd:["#f7f4f9","#e7e1ef","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#980043","#67001f"],Blues:["#f7fbff","#deebf7","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#08519c","#08306b"],PuBuGn:["#fff7fb","#ece2f0","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016c59","#014636"],Viridis:["#440154","#482777","#3f4a8a","#31678e","#26838f","#1f9d8a","#6cce5a","#b6de2b","#fee825"],Spectral:["#9e0142","#d53e4f","#f46d43","#fdae61","#fee08b","#ffffbf","#e6f598","#abdda4","#66c2a5","#3288bd","#5e4fa2"],RdYlGn:["#a50026","#d73027","#f46d43","#fdae61","#fee08b","#ffffbf","#d9ef8b","#a6d96a","#66bd63","#1a9850","#006837"],RdBu:["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#f7f7f7","#d1e5f0","#92c5de","#4393c3","#2166ac","#053061"],PiYG:["#8e0152","#c51b7d","#de77ae","#f1b6da","#fde0ef","#f7f7f7","#e6f5d0","#b8e186","#7fbc41","#4d9221","#276419"],PRGn:["#40004b","#762a83","#9970ab","#c2a5cf","#e7d4e8","#f7f7f7","#d9f0d3","#a6dba0","#5aae61","#1b7837","#00441b"],RdYlBu:["#a50026","#d73027","#f46d43","#fdae61","#fee090","#ffffbf","#e0f3f8","#abd9e9","#74add1","#4575b4","#313695"],BrBG:["#543005","#8c510a","#bf812d","#dfc27d","#f6e8c3","#f5f5f5","#c7eae5","#80cdc1","#35978f","#01665e","#003c30"],RdGy:["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#ffffff","#e0e0e0","#bababa","#878787","#4d4d4d","#1a1a1a"],PuOr:["#7f3b08","#b35806","#e08214","#fdb863","#fee0b6","#f7f7f7","#d8daeb","#b2abd2","#8073ac","#542788","#2d004b"],Set2:["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f","#e5c494","#b3b3b3"],Accent:["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f","#bf5b17","#666666"],Set1:["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628","#f781bf","#999999"],Set3:["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd","#ccebc5","#ffed6f"],Dark2:["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02","#a6761d","#666666"],Paired:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a","#ffff99","#b15928"],Pastel2:["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9","#fff2ae","#f1e2cc","#cccccc"],Pastel1:["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd","#fddaec","#f2f2f2"]};for(let e of Object.keys(kt))kt[e.toLowerCase()]=kt[e];Object.assign(O,{average:A0,bezier:j0,blend:de,cubehelix:J0,mix:ct,interpolate:ct,random:ei,scale:yt,analyze:To,contrast:oi,deltaE:ii,distance:ui,limits:So,valid:fi,scales:li,input:L,colors:De,brewer:kt});function pr(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var mr,Io;function hi(){if(Io)return mr;Io=1;var e=e||{};e.Geometry=function(){},e.Geometry.intersectLineLine=function(r,n){var o=(r.intercept-n.intercept)/(n.slope-r.slope),a=r.slope*o+r.intercept;return{x:o,y:a}},e.Geometry.distanceFromOrigin=function(r){return Math.sqrt(Math.pow(r.x,2)+Math.pow(r.y,2))},e.Geometry.distanceLineFromOrigin=function(r){return Math.abs(r.intercept)/Math.sqrt(Math.pow(r.slope,2)+1)},e.Geometry.perpendicularThroughPoint=function(r,n){var o=-1/r.slope,a=n.y-o*n.x;return{slope:o,intercept:a}},e.Geometry.angleFromOrigin=function(r){return Math.atan2(r.y,r.x)},e.Geometry.normalizeAngle=function(r){var n=2*Math.PI;return(r%n+n)%n},e.Geometry.lengthOfRayUntilIntersect=function(r,n){return n.intercept/(Math.sin(r)-n.slope*Math.cos(r))},e.Hsluv=function(){},e.Hsluv.getBounds=function(r){for(var n=[],o=Math.pow(r+16,3)/1560896,a=o>e.Hsluv.epsilon?o:r/e.Hsluv.kappa,s=0;s<3;)for(var c=s++,i=e.Hsluv.m[c][0],u=e.Hsluv.m[c][1],l=e.Hsluv.m[c][2],f=0;f<2;){var h=f++,d=(284517*i-94839*l)*a,b=(838422*l+769860*u+731718*i)*r*a-769860*h*r,_=(632260*l-126452*u)*a+126452*h;n.push({slope:d/_,intercept:b/_})}return n},e.Hsluv.maxSafeChromaForL=function(r){for(var n=e.Hsluv.getBounds(r),o=1/0,a=0;a=0&&(s=Math.min(s,u))}return s},e.Hsluv.dotProduct=function(r,n){for(var o=0,a=0,s=r.length;a.04045?Math.pow((r+.055)/1.055,2.4):r/12.92},e.Hsluv.xyzToRgb=function(r){return[e.Hsluv.fromLinear(e.Hsluv.dotProduct(e.Hsluv.m[0],r)),e.Hsluv.fromLinear(e.Hsluv.dotProduct(e.Hsluv.m[1],r)),e.Hsluv.fromLinear(e.Hsluv.dotProduct(e.Hsluv.m[2],r))]},e.Hsluv.rgbToXyz=function(r){var n=[e.Hsluv.toLinear(r[0]),e.Hsluv.toLinear(r[1]),e.Hsluv.toLinear(r[2])];return[e.Hsluv.dotProduct(e.Hsluv.minv[0],n),e.Hsluv.dotProduct(e.Hsluv.minv[1],n),e.Hsluv.dotProduct(e.Hsluv.minv[2],n)]},e.Hsluv.yToL=function(r){return r<=e.Hsluv.epsilon?r/e.Hsluv.refY*e.Hsluv.kappa:116*Math.pow(r/e.Hsluv.refY,.3333333333333333)-16},e.Hsluv.lToY=function(r){return r<=8?e.Hsluv.refY*r/e.Hsluv.kappa:e.Hsluv.refY*Math.pow((r+16)/116,3)},e.Hsluv.xyzToLuv=function(r){var n=r[0],o=r[1],a=r[2],s=n+15*o+3*a,c=4*n,i=9*o;s!=0?(c/=s,i/=s):(c=NaN,i=NaN);var u=e.Hsluv.yToL(o);if(u==0)return[0,0,0];var l=13*u*(c-e.Hsluv.refU),f=13*u*(i-e.Hsluv.refV);return[u,l,f]},e.Hsluv.luvToXyz=function(r){var n=r[0],o=r[1],a=r[2];if(n==0)return[0,0,0];var s=o/(13*n)+e.Hsluv.refU,c=a/(13*n)+e.Hsluv.refV,i=e.Hsluv.lToY(n),u=0-9*i*s/((s-4)*c-s*c),l=(9*i-15*c*i-c*u)/(3*c);return[u,i,l]},e.Hsluv.luvToLch=function(r){var n=r[0],o=r[1],a=r[2],s=Math.sqrt(o*o+a*a),c;if(s<1e-8)c=0;else{var i=Math.atan2(a,o);c=i*180/Math.PI,c<0&&(c=360+c)}return[n,s,c]},e.Hsluv.lchToLuv=function(r){var n=r[0],o=r[1],a=r[2],s=a/360*2*Math.PI,c=Math.cos(s)*o,i=Math.sin(s)*o;return[n,c,i]},e.Hsluv.hsluvToLch=function(r){var n=r[0],o=r[1],a=r[2];if(a>99.9999999)return[100,0,n];if(a<1e-8)return[0,0,n];var s=e.Hsluv.maxChromaForLH(a,n),c=s/100*o;return[a,c,n]},e.Hsluv.lchToHsluv=function(r){var n=r[0],o=r[1],a=r[2];if(n>99.9999999)return[a,0,100];if(n<1e-8)return[a,0,0];var s=e.Hsluv.maxChromaForLH(n,a),c=o/s*100;return[a,c,n]},e.Hsluv.hpluvToLch=function(r){var n=r[0],o=r[1],a=r[2];if(a>99.9999999)return[100,0,n];if(a<1e-8)return[0,0,n];var s=e.Hsluv.maxSafeChromaForL(a),c=s/100*o;return[a,c,n]},e.Hsluv.lchToHpluv=function(r){var n=r[0],o=r[1],a=r[2];if(n>99.9999999)return[a,0,100];if(n<1e-8)return[a,0,0];var s=e.Hsluv.maxSafeChromaForL(n),c=o/s*100;return[a,c,n]},e.Hsluv.rgbToHex=function(r){for(var n="#",o=0;o<3;){var a=o++,s=r[a],c=Math.round(s*255),i=c%16,u=(c-i)/16|0;n+=e.Hsluv.hexChars.charAt(u)+e.Hsluv.hexChars.charAt(i)}return n},e.Hsluv.hexToRgb=function(r){r=r.toLowerCase();for(var n=[],o=0;o<3;){var a=o++,s=e.Hsluv.hexChars.indexOf(r.charAt(a*2+1)),c=e.Hsluv.hexChars.indexOf(r.charAt(a*2+2)),i=s*16+c;n.push(i/255)}return n},e.Hsluv.lchToRgb=function(r){return e.Hsluv.xyzToRgb(e.Hsluv.luvToXyz(e.Hsluv.lchToLuv(r)))},e.Hsluv.rgbToLch=function(r){return e.Hsluv.luvToLch(e.Hsluv.xyzToLuv(e.Hsluv.rgbToXyz(r)))},e.Hsluv.hsluvToRgb=function(r){return e.Hsluv.lchToRgb(e.Hsluv.hsluvToLch(r))},e.Hsluv.rgbToHsluv=function(r){return e.Hsluv.lchToHsluv(e.Hsluv.rgbToLch(r))},e.Hsluv.hpluvToRgb=function(r){return e.Hsluv.lchToRgb(e.Hsluv.hpluvToLch(r))},e.Hsluv.rgbToHpluv=function(r){return e.Hsluv.lchToHpluv(e.Hsluv.rgbToLch(r))},e.Hsluv.hsluvToHex=function(r){return e.Hsluv.rgbToHex(e.Hsluv.hsluvToRgb(r))},e.Hsluv.hpluvToHex=function(r){return e.Hsluv.rgbToHex(e.Hsluv.hpluvToRgb(r))},e.Hsluv.hexToHsluv=function(r){return e.Hsluv.rgbToHsluv(e.Hsluv.hexToRgb(r))},e.Hsluv.hexToHpluv=function(r){return e.Hsluv.rgbToHpluv(e.Hsluv.hexToRgb(r))},e.Hsluv.m=[[3.240969941904521,-1.537383177570093,-.498610760293],[-.96924363628087,1.87596750150772,.041555057407175],[.055630079696993,-.20397695888897,1.056971514242878]],e.Hsluv.minv=[[.41239079926595,.35758433938387,.18048078840183],[.21263900587151,.71516867876775,.072192315360733],[.019330818715591,.11919477979462,.95053215224966]],e.Hsluv.refY=1,e.Hsluv.refU=.19783000664283,e.Hsluv.refV=.46831999493879,e.Hsluv.kappa=903.2962962,e.Hsluv.epsilon=.0088564516,e.Hsluv.hexChars="0123456789abcdef";var t={hsluvToRgb:e.Hsluv.hsluvToRgb,rgbToHsluv:e.Hsluv.rgbToHsluv,hpluvToRgb:e.Hsluv.hpluvToRgb,rgbToHpluv:e.Hsluv.rgbToHpluv,hsluvToHex:e.Hsluv.hsluvToHex,hexToHsluv:e.Hsluv.hexToHsluv,hpluvToHex:e.Hsluv.hpluvToHex,hexToHpluv:e.Hsluv.hexToHpluv,lchToHpluv:e.Hsluv.lchToHpluv,hpluvToLch:e.Hsluv.hpluvToLch,lchToHsluv:e.Hsluv.lchToHsluv,hsluvToLch:e.Hsluv.hsluvToLch,lchToLuv:e.Hsluv.lchToLuv,luvToLch:e.Hsluv.luvToLch,xyzToLuv:e.Hsluv.xyzToLuv,luvToXyz:e.Hsluv.luvToXyz,xyzToRgb:e.Hsluv.xyzToRgb,rgbToXyz:e.Hsluv.rgbToXyz,lchToRgb:e.Hsluv.lchToRgb,rgbToLch:e.Hsluv.rgbToLch};return mr=t,mr}var di=hi();const gr=pr(di);var $t={exports:{}},vr,zo;function it(){if(zo)return vr;zo=1;function e(t,r){return Object.prototype.hasOwnProperty.call(t,r)}return vr=e,vr}var _r,Fo;function yr(){if(Fo)return _r;Fo=1;var e=it(),t,r;function n(){r=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],t=!0;for(var s in{toString:null})t=!1}function o(s,c,i){var u,l=0;t==null&&n();for(u in s)if(a(c,s,u,i)===!1)break;if(t)for(var f=s.constructor,h=!!f&&s===f.prototype;(u=r[l++])&&!((u!=="constructor"||!h&&e(s,u))&&s[u]!==Object.prototype[u]&&a(c,s,u,i)===!1););}function a(s,c,i,u){return s.call(u,c[i],i,c)}return _r=o,_r}var wr,Xo;function Ko(){if(Xo)return wr;Xo=1;var e=yr();function t(r){var n=[];return e(r,function(o,a){typeof o=="function"&&n.push(a)}),n.sort()}return wr=t,wr}var kr,Do;function ut(){if(Do)return kr;Do=1;function e(t,r,n){var o=t.length;r==null?r=0:r<0?r=Math.max(o+r,0):r=Math.min(r,o),n==null?n=o:n<0?n=Math.max(o+n,0):n=Math.min(n,o);for(var a=[];r1?n(arguments,1):e(a);r(c,function(i){a[i]=t(a[i],a)})}return Rr=o,Rr}var Hr,Jo;function Q(){if(Jo)return Hr;Jo=1;var e=it(),t=yr();function r(n,o,a){t(n,function(s,c){if(e(n,c))return o.call(a,n[c],c,n)})}return Hr=r,Hr}var qr,Wo;function mi(){if(Wo)return qr;Wo=1;function e(t){return t}return qr=e,qr}var Mr,Uo;function Qo(){if(Uo)return Mr;Uo=1;function e(t){return function(r){return r[t]}}return Mr=e,Mr}var Or,es;function Nr(){if(es)return Or;es=1;var e=/^\[object (.*)\]$/,t=Object.prototype.toString,r;function n(o){return o===null?"Null":o===r?"Undefined":e.exec(t.call(o))[1]}return Or=n,Or}var Ar,ts;function Lr(){if(ts)return Ar;ts=1;var e=Nr();function t(r,n){return e(r)===n}return Ar=t,Ar}var Er,rs;function gi(){if(rs)return Er;rs=1;var e=Lr(),t=Array.isArray||function(r){return e(r,"Array")};return Er=t,Er}var Tr,ns;function os(){if(ns)return Tr;ns=1;var e=Q(),t=gi();function r(s,c){for(var i=-1,u=s.length;++is&&(s=i,a=c);return a}return rn=t,rn}var nn,Ns;function on(){if(Ns)return nn;Ns=1;var e=Q();function t(r){var n=[];return e(r,function(o,a){n.push(o)}),n}return nn=t,nn}var sn,As;function Mi(){if(As)return sn;As=1;var e=qi(),t=on();function r(n,o){return e(t(n),o)}return sn=r,sn}var an,Ls;function Es(){if(Ls)return an;Ls=1;var e=Q();function t(n,o){for(var a=0,s=arguments.length,c;++a2;if(!t(n)&&!c)throw new Error("reduce of empty object with no initial value");return e(n,function(i,u,l){c?a=o.call(s,a,i,u,l):(a=i,c=!0)}),a}return yn=r,yn}var wn,Js;function Ii(){if(Js)return wn;Js=1;var e=_s(),t=Le();function r(n,o,a){return o=t(o,a),e(n,function(s,c,i){return!o(s,c,i)},a)}return wn=r,wn}var kn,Ws;function zi(){if(Ws)return kn;Ws=1;var e=Lr();function t(r){return e(r,"Function")}return kn=t,kn}var $n,Us;function Fi(){if(Us)return $n;Us=1;var e=zi();function t(r,n){var o=r[n];if(o!==void 0)return e(o)?o.call(r):o}return $n=t,$n}var Cn,Qs;function Xi(){if(Qs)return Cn;Qs=1;var e=Is();function t(r,n,o){var a=/^(.+)\.(.+)$/.exec(n);a?e(r,a[1])[a[2]]=o:r[n]=o}return Cn=t,Cn}var xn,ea;function Ki(){if(ea)return xn;ea=1;var e=xs();function t(r,n){if(e(r,n)){for(var o=n.split("."),a=o.pop();n=o.shift();)r=r[n];return delete r[a]}else return!0}return xn=t,xn}var Rn,ta;function Hn(){return ta||(ta=1,Rn={bindAll:pi(),contains:vi(),deepFillIn:_i(),deepMatches:os(),deepMixIn:yi(),equals:ki(),every:hs(),fillIn:$i(),filter:_s(),find:Ci(),flatten:xi(),forIn:yr(),forOwn:Q(),functions:Ko(),get:$s(),has:xs(),hasOwn:it(),keys:Ri(),map:qs(),matches:Hi(),max:Mi(),merge:Ai(),min:Ei(),mixIn:Es(),namespace:Is(),omit:Pi(),pick:ji(),pluck:Bi(),reduce:Gi(),reject:Ii(),result:Fi(),set:Xi(),size:Ys(),some:jr(),unset:Ki(),values:on()}),Rn}var ra;function na(){return ra||(ra=1,(function(e,t){Object.defineProperty(t,"__esModule",{value:!0});var r=Hn(),n={A:{x:.44758,y:.40745},C:{x:.31006,y:.31616},D50:{x:.34567,y:.35851},D65:{x:.31272,y:.32903},D55:{x:.33243,y:.34744},D75:{x:.29903,y:.31488}},o=(0,r.map)(n,function(a){var s=100*(a.x/a.y),c=100,i=100*(1-a.x-a.y)/a.y;return[s,c,i]});t.default=o,e.exports=t.default})($t,$t.exports)),$t.exports}var Ct={exports:{}},oa;function sa(){return oa||(oa=1,(function(e,t){Object.defineProperty(t,"__esModule",{value:!0});var r=Math,n=r.pow,o=r.sign,a=r.abs,s={decode:function(f){return f<=.04045?f/12.92:n((f+.055)/1.055,2.4)},encode:function(f){return f<=.0031308?12.92*f:1.055*n(f,1/2.4)-.055}},c={encode:function(f){return f<.001953125?16*f:n(f,1/1.8)},decode:function(f){return f<16*.001953125?f/16:n(f,1.8)}};function i(l){return{decode:function(h){return o(h)*n(a(h),l)},encode:function(h){return o(h)*n(a(h),1/l)}}}var u={sRGB:{r:{x:.64,y:.33},g:{x:.3,y:.6},b:{x:.15,y:.06},gamma:s},"Adobe RGB":{r:{x:.64,y:.33},g:{x:.21,y:.71},b:{x:.15,y:.06},gamma:i(2.2)},"Wide Gamut RGB":{r:{x:.7347,y:.2653},g:{x:.1152,y:.8264},b:{x:.1566,y:.0177},gamma:i(563/256)},"ProPhoto RGB":{r:{x:.7347,y:.2653},g:{x:.1596,y:.8404},b:{x:.0366,y:1e-4},gamma:c}};t.default=u,e.exports=t.default})(Ct,Ct.exports)),Ct.exports}var $e={},aa;function ca(){if(aa)return $e;aa=1,Object.defineProperty($e,"__esModule",{value:!0});function e(s){return[[s[0][0],s[1][0],s[2][0]],[s[0][1],s[1][1],s[2][1]],[s[0][2],s[1][2],s[2][2]]]}function t(s){return s[0][0]*(s[2][2]*s[1][1]-s[2][1]*s[1][2])+s[1][0]*(s[2][1]*s[0][2]-s[2][2]*s[0][1])+s[2][0]*(s[1][2]*s[0][1]-s[1][1]*s[0][2])}function r(s){var c=1/t(s);return[[(s[2][2]*s[1][1]-s[2][1]*s[1][2])*c,(s[2][1]*s[0][2]-s[2][2]*s[0][1])*c,(s[1][2]*s[0][1]-s[1][1]*s[0][2])*c],[(s[2][0]*s[1][2]-s[2][2]*s[1][0])*c,(s[2][2]*s[0][0]-s[2][0]*s[0][2])*c,(s[1][0]*s[0][2]-s[1][2]*s[0][0])*c],[(s[2][1]*s[1][0]-s[2][0]*s[1][1])*c,(s[2][0]*s[0][1]-s[2][1]*s[0][0])*c,(s[1][1]*s[0][0]-s[1][0]*s[0][1])*c]]}function n(s,c){return[s[0][0]*c[0]+s[0][1]*c[1]+s[0][2]*c[2],s[1][0]*c[0]+s[1][1]*c[1]+s[1][2]*c[2],s[2][0]*c[0]+s[2][1]*c[1]+s[2][2]*c[2]]}function o(s,c){return[[s[0][0]*c[0],s[0][1]*c[1],s[0][2]*c[2]],[s[1][0]*c[0],s[1][1]*c[1],s[1][2]*c[2]],[s[2][0]*c[0],s[2][1]*c[1],s[2][2]*c[2]]]}function a(s,c){return[[s[0][0]*c[0][0]+s[0][1]*c[1][0]+s[0][2]*c[2][0],s[0][0]*c[0][1]+s[0][1]*c[1][1]+s[0][2]*c[2][1],s[0][0]*c[0][2]+s[0][1]*c[1][2]+s[0][2]*c[2][2]],[s[1][0]*c[0][0]+s[1][1]*c[1][0]+s[1][2]*c[2][0],s[1][0]*c[0][1]+s[1][1]*c[1][1]+s[1][2]*c[2][1],s[1][0]*c[0][2]+s[1][1]*c[1][2]+s[1][2]*c[2][2]],[s[2][0]*c[0][0]+s[2][1]*c[1][0]+s[2][2]*c[2][0],s[2][0]*c[0][1]+s[2][1]*c[1][1]+s[2][2]*c[2][1],s[2][0]*c[0][2]+s[2][1]*c[1][2]+s[2][2]*c[2][2]]]}return $e.transpose=e,$e.determinant=t,$e.inverse=r,$e.multiply=n,$e.scalar=o,$e.product=a,$e}var lt={},ia;function Di(){if(ia)return lt;ia=1,Object.defineProperty(lt,"__esModule",{value:!0});var e=Math,t=e.PI;function r(o){for(var a=o*180/t;a<0;)a+=360;for(;a>360;)a-=360;return a}function n(o){for(var a=t*o/180;a<0;)a+=2*t;for(;a>2*t;)a-=2*t;return a}return lt.fromRadian=r,lt.toRadian=n,lt}var ht={},ua;function Vi(){if(ua)return ht;ua=1,Object.defineProperty(ht,"__esModule",{value:!0});var e=Math,t=e.round;function r(o){return o[0]=="#"&&(o=o.slice(1)),o.length<6&&(o=o.split("").map(function(a){return a+a}).join("")),o.match(/../g).map(function(a){return parseInt(a,16)/255})}function n(o){var a=o.map(function(s){return s=t(255*s).toString(16),s.length<2&&(s="0"+s),s}).join("");return"#"+a}return ht.fromHex=r,ht.toHex=n,ht}var xt={exports:{}},fa;function Yi(){return fa||(fa=1,(function(e,t){Object.defineProperty(t,"__esModule",{value:!0});var r=ca(),n=u(r),o=na(),a=i(o),s=sa(),c=i(s);function i(f){return f&&f.__esModule?f:{default:f}}function u(f){if(f&&f.__esModule)return f;var h={};if(f!=null)for(var d in f)Object.prototype.hasOwnProperty.call(f,d)&&(h[d]=f[d]);return h.default=f,h}function l(){var f=arguments.length<=0||arguments[0]===void 0?c.default.sRGB:arguments[0],h=arguments.length<=1||arguments[1]===void 0?a.default.D65:arguments[1],d=[f.r,f.g,f.b],b=n.transpose(d.map(function(x){var N=x.x/x.y,A=1,R=(1-x.x-x.y)/x.y;return[N,A,R]})),_=f.gamma,g=n.multiply(n.inverse(b),h),v=n.scalar(b,g),H=n.inverse(v);return{fromRgb:function(N){return n.multiply(v,N.map(_.decode))},toRgb:function(N){return n.multiply(H,N).map(_.encode)}}}t.default=l,e.exports=t.default})(xt,xt.exports)),xt.exports}var qn,la;function Rt(){if(la)return qn;la=1;var e=na(),t=sa(),r=ca(),n=Di(),o=Vi(),a=Yi();return qn={illuminant:e,workspace:t,matrix:r,degree:n,rgb:o,xyz:a},qn}var Zi=Rt();const Ht=pr(Zi);var be={},ha;function qt(){if(ha)return be;ha=1,Object.defineProperty(be,"__esModule",{value:!0}),be.cfs=be.distance=be.lerp=be.corLerp=void 0;var e=Hn();function t(h,d,b){return d in h?Object.defineProperty(h,d,{value:b,enumerable:!0,configurable:!0,writable:!0}):h[d]=b,h}function r(h){if(Array.isArray(h)){for(var d=0,b=Array(h.length);dg/2&&(h>d?d+=g:h+=g)}return((1-b)*h+b*d)%(g||1/0)}function u(h,d,b){var _={};for(var g in h)_[g]=i(h[g],d[g],b,g);return _}function l(h,d){var b=0;for(var _ in h)b+=a(h[_]-d[_],2);return s(b)}function f(h){return e.merge.apply(void 0,r(h.split("").map(function(d){return t({},d,!0)})))}return be.corLerp=i,be.lerp=u,be.distance=l,be.cfs=f,be}var Mt={exports:{}},da;function Ji(){return da||(da=1,(function(e,t){var r=(function(){function s(c,i){var u=[],l=!0,f=!1,h=void 0;try{for(var d=c[Symbol.iterator](),b;!(l=(b=d.next()).done)&&(u.push(b.value),!(i&&u.length===i));l=!0);}catch(_){f=!0,h=_}finally{try{!l&&d.return&&d.return()}finally{if(f)throw h}}return u}return function(c,i){if(Array.isArray(c))return c;if(Symbol.iterator in Object(c))return s(c,i);throw new TypeError("Invalid attempt to destructure non-iterable instance")}})();Object.defineProperty(t,"__esModule",{value:!0});var n=Rt(),o=qt();function a(s,c){var i=arguments.length<=2||arguments[2]===void 0?1e-6:arguments[2],u=-i,l=1+i,f=Math,h=f.min,d=f.max,b=["000","fff"].map(function(R){return c.fromXyz(s.fromRgb(n.rgb.fromHex(R)))}),_=r(b,2),g=_[0],v=_[1];function H(R){var m=s.toRgb(c.toXyz(R)),p=m.map(function(y){return y>=u&&y<=l}).reduce(function(y,w){return y&&w},!0);return[p,m]}function x(R,m){for(var p=arguments.length<=2||arguments[2]===void 0?.001:arguments[2];(0,o.distance)(R,m)>p;){var y=(0,o.lerp)(R,m,.5),w=H(y),C=r(w,1),q=C[0];q?R=y:m=y}return R}function N(R){return(0,o.lerp)(g,v,R)}function A(R){return R.map(function(m){return d(u,h(l,m))})}return{contains:H,limit:x,spine:N,crop:A}}t.default=a,e.exports=t.default})(Mt,Mt.exports)),Mt.exports}var Ot={exports:{}},pe={},ba;function pa(){if(ba)return pe;ba=1;var e=(function(){function f(h,d){var b=[],_=!0,g=!1,v=void 0;try{for(var H=h[Symbol.iterator](),x;!(_=(x=H.next()).done)&&(b.push(x.value),!(d&&b.length===d));_=!0);}catch(N){g=!0,v=N}finally{try{!_&&H.return&&H.return()}finally{if(g)throw v}}return b}return function(h,d){if(Array.isArray(h))return h;if(Symbol.iterator in Object(h))return f(h,d);throw new TypeError("Invalid attempt to destructure non-iterable instance")}})();Object.defineProperty(pe,"__esModule",{value:!0}),pe.toNotation=pe.fromNotation=pe.toHue=pe.fromHue=void 0;var t=qt(),r=Math,n=r.floor,o=[{s:"R",h:20.14,e:.8,H:0},{s:"Y",h:90,e:.7,H:100},{s:"G",h:164.25,e:1,H:200},{s:"B",h:237.53,e:1.2,H:300},{s:"R",h:380.14,e:.8,H:400}],a=o.map(function(f){return f.s}).slice(0,-1).join("");function s(f){f50){var _=[d,h];h=_[0],d=_[1],b=100-b}return b<1?a[h]:a[h]+b.toFixed()+a[d]}return pe.fromHue=s,pe.toHue=c,pe.fromNotation=u,pe.toNotation=l,pe}var ma;function Wi(){return ma||(ma=1,(function(e,t){var r=(function(){function S(X,D){var U=[],ce=!0,ye=!1,st=void 0;try{for(var le=X[Symbol.iterator](),qe;!(ce=(qe=le.next()).done)&&(U.push(qe.value),!(D&&U.length===D));ce=!0);}catch(he){ye=!0,st=he}finally{try{!ce&&le.return&&le.return()}finally{if(ye)throw st}}return U}return function(X,D){if(Array.isArray(X))return X;if(Symbol.iterator in Object(X))return S(X,D);throw new TypeError("Invalid attempt to destructure non-iterable instance")}})();Object.defineProperty(t,"__esModule",{value:!0});var n=Rt(),o=pa(),a=i(o),s=qt(),c=Hn();function i(S){if(S&&S.__esModule)return S;var X={};if(S!=null)for(var D in S)Object.prototype.hasOwnProperty.call(S,D)&&(X[D]=S[D]);return X.default=S,X}var u=Math,l=u.pow,f=u.sqrt,h=u.exp,d=u.abs,b=u.sign,_=Math,g=_.sin,v=_.cos,H=_.atan2,x={average:{F:1,c:.69,N_c:1},dim:{F:.9,c:.59,N_c:.9},dark:{F:.8,c:.535,N_c:.8}},N=[[.7328,.4296,-.1624],[-.7036,1.6975,.0061],[.003,.0136,.9834]],A=[[.38971,.68898,-.07868],[-.22981,1.1834,.04641],[0,0,1]],R=N,m=n.matrix.inverse(N),p=n.matrix.product(A,n.matrix.inverse(N)),y=n.matrix.product(N,n.matrix.inverse(A)),w={whitePoint:n.illuminant.D65,adaptingLuminance:40,backgroundLuminance:20,surroundType:"average",discounting:!1},C=(0,s.cfs)("QJMCshH"),q=(0,s.cfs)("JCh");function M(){var S=arguments.length<=0||arguments[0]===void 0?{}:arguments[0],X=arguments.length<=1||arguments[1]===void 0?C:arguments[1];S=(0,c.merge)(w,S);var D=S.whitePoint,U=S.adaptingLuminance,ce=S.backgroundLuminance,ye=x[S.surroundType],st=ye.F,le=ye.c,qe=ye.N_c,he=D[1],wc=1/(5*U+1),Ge=.2*l(wc,4)*5*U+.1*l(1-l(wc,4),2)*l(5*U,1/3),Xt=ce/he,no=.725*l(1/Xt,.2),kc=no,$c=1.48+f(Xt),Cc=S.discounting?1:st*(1-1/3.6*h(-(U+42)/92)),$l=n.matrix.multiply(N,D),Cl=$l.map(function(I){return Cc*he/I+1-Cc}),oo=r(Cl,3),xc=oo[0],Rc=oo[1],Hc=oo[2],xl=qc(D),Rl=Mc(xl),Kt=Oc(Rl);function qc(I){var z=n.matrix.multiply(R,I),F=r(z,3),ee=F[0],W=F[1],ie=F[2];return[xc*ee,Rc*W,Hc*ie]}function Hl(I){var z=r(I,3),F=z[0],ee=z[1],W=z[2];return n.matrix.multiply(m,[F/xc,ee/Rc,W/Hc])}function Mc(I){return n.matrix.multiply(p,I).map(function(z){var F=l(Ge*d(z)/100,.42);return b(z)*400*F/(27.13+F)+.1})}function ql(I){return n.matrix.multiply(y,I.map(function(z){var F=z-.1;return b(F)*100/Ge*l(27.13*d(F)/(400-d(F)),2.380952380952381)}))}function Oc(I){var z=r(I,3),F=z[0],ee=z[1],W=z[2];return(F*2+ee+W/20-.305)*no}function so(I){return 4/le*f(I/100)*(Kt+4)*l(Ge,.25)}function Ml(I){return 6.25*l(le*I/((Kt+4)*l(Ge,.25)),2)}function Nc(I){return I*l(Ge,.25)}function Ol(I,z){return l(I/100,2)*z/l(Ge,.25)}function Nl(I){return I/l(Ge,.25)}function Al(I,z){return 100*f(I/z)}function ao(I,z){var F=z.Q,ee=z.J,W=z.M,ie=z.C,ve=z.s,Me=z.h,Oe=z.H,te={};return I.J&&(te.J=isNaN(ee)?Ml(F):ee),I.C&&(isNaN(ie)?isNaN(W)?(F=isNaN(F)?so(ee):F,te.C=Ol(ve,F)):te.C=Nl(W):te.C=z.C),I.h&&(te.h=isNaN(Me)?a.toHue(Oe):Me),I.Q&&(te.Q=isNaN(F)?so(ee):F),I.M&&(te.M=isNaN(W)?Nc(ie):W),I.s&&(isNaN(ve)?(F=isNaN(F)?so(ee):F,W=isNaN(W)?Nc(ie):W,te.s=Al(W,F)):te.s=ve),I.H&&(te.H=isNaN(Oe)?a.fromHue(Me):Oe),te}function Ll(I){var z=qc(I),F=Mc(z),ee=r(F,3),W=ee[0],ie=ee[1],ve=ee[2],Me=W-ie*12/11+ve/11,Oe=(W+ie-2*ve)/9,te=H(Oe,Me),at=n.degree.fromRadian(te),Dt=1/4*(v(te+2)+3.8),Vt=Oc(F),bt=100*l(Vt/Kt,le*$c),Pe=5e4/13*qe*kc*Dt*f(Me*Me+Oe*Oe)/(W+ie+21/20*ve),je=l(Pe,.9)*f(bt/100)*l(1.64-l(.29,Xt),.73);return ao(X,{J:bt,C:je,h:at})}function El(I){var z=ao(q,I),F=z.J,ee=z.C,W=z.h,ie=n.degree.toRadian(W),ve=l(ee/(f(F/100)*l(1.64-l(.29,Xt),.73)),10/9),Me=1/4*(v(ie+2)+3.8),Oe=Kt*l(F/100,1/le/$c),te=5e4/13*qe*kc*Me/ve,at=Oe/no+.305,Dt=at*61/20*460/1403,Vt=61/20*220/1403,bt=21/20*6300/1403-27/1403,Pe=g(ie),je=v(ie),Ie,ze;ve===0||isNaN(ve)?Ie=ze=0:d(Pe)>=d(je)?(ze=Dt/(te/Pe+Vt*je/Pe+bt),Ie=ze*je/Pe):(Ie=Dt/(te/je+Vt+bt*Pe/je),ze=Ie*Pe/je);var Tl=[20/61*at+451/1403*Ie+288/1403*ze,20/61*at-891/1403*Ie-261/1403*ze,20/61*at-220/1403*Ie-6300/1403*ze],Sl=ql(Tl),Pl=Hl(Sl);return Pl}return{fromXyz:Ll,toXyz:El,fillOut:ao}}t.default=M,e.exports=t.default})(Ot,Ot.exports)),Ot.exports}var Nt={exports:{}},ga;function Ui(){return ga||(ga=1,(function(e,t){Object.defineProperty(t,"__esModule",{value:!0});var r=Rt(),n=Math,o=n.sqrt,a=n.pow,s=n.exp,c=n.log,i=n.cos,u=n.sin,l=n.atan2,f={LCD:{K_L:.77,c_1:.007,c_2:.0053},SCD:{K_L:1.24,c_1:.007,c_2:.0363},UCS:{K_L:1,c_1:.007,c_2:.0228}};function h(){var d=arguments.length<=0||arguments[0]===void 0?"UCS":arguments[0],b=f[d],_=b.K_L,g=b.c_1,v=b.c_2;function H(A){var R=A.J,m=A.M,p=A.h,y=r.degree.toRadian(p),w=(1+100*g)*R/(1+g*R),C=1/v*c(1+v*m),q=C*i(y),M=C*u(y);return{J_p:w,a_p:q,b_p:M}}function x(A){var R=A.J_p,m=A.a_p,p=A.b_p,y=-R/(g*R-100*g-1),w=o(a(m,2)+a(p,2)),C=(s(v*w)-1)/v,q=l(p,m),M=r.degree.fromRadian(q);return{J:y,M:C,h:M}}function N(A,R){return o(a((A.J_p-R.J_p)/_,2)+a(A.a_p-R.a_p,2)+a(A.b_p-R.b_p,2))}return{fromCam:H,toCam:x,distance:N}}t.default=h,e.exports=t.default})(Nt,Nt.exports)),Nt.exports}var Mn,va;function Qi(){if(va)return Mn;va=1;var e=qt(),t=Ji(),r=Wi(),n=Ui(),o=pa();return Mn={gamut:t,cfs:e.cfs,lerp:e.lerp,cam:r,ucs:n,hq:o},Mn}var eu=Qi();const _a=pr(eu),ya=_a.cam({whitePoint:Ht.illuminant.D65,adaptingLuminance:40,backgroundLuminance:20,surroundType:"average",discounting:!1},_a.cfs("JCh")),wa=Ht.xyz(Ht.workspace.sRGB,Ht.illuminant.D65),ka=e=>wa.toRgb(ya.toXyz({J:e[0],C:e[1],h:e[2]})),On=e=>{const t=ya.fromXyz(wa.fromRgb(e));return[t.J,t.C,t.h]},[tu,ru]=(()=>{const e={k_l:1,c1:.007,c2:.0228},t=Math.PI,r=64/t/5,n=1/(5*r+1),o=.2*n**4*(5*r)+.1*(1-n**4)**2*(5*r)**(1/3);return[a=>{const[s,c,i]=a,u=c*o**.25;let l=(1+100*e.c1)*s/(1+e.c1*s);l/=e.k_l;const f=1/e.c2*Math.log(1+e.c2*u),h=f*Math.cos(i*(t/180)),d=f*Math.sin(i*(t/180));return[l,h,d]},a=>{const[s,c,i]=a,u=Math.sqrt(c*c+i*i),l=(Math.exp(u*e.c2)-1)/e.c2,f=(180/t*Math.atan2(i,c)+360)%360,h=l/o**.25;return[s/(1+e.c1*(100-s)),h,f]}]})(),nu=e=>ka(ru(e)),$a=e=>tu(On(e)),At=console;At.color=(e,t="")=>{const n=O(e).luminance();At.log(`%c${e} ${t}`,`background-color: ${e};padding: 5px; border-radius: 5px; color: ${n>.5?"#000":"#fff"}`)},At.ramp=(e,t=1)=>{At.log("%c ",`font-size: 1px;line-height: 16px;background: ${O.getCSSGradient(e,t)};padding: 0 0 0 200px; border-radius: 2px;`)};const Ca=(e,t,r,n,o,a,s=.1)=>{if(e===r||t===n)return!0;const c=(n-t)/(r-e),i=(a+o/c-t+c*e)/(c+1/c),u=a+o/c-i/c;return(o-i)**2+(a-u)**2{const o=(t[0]+r[0])/2,a=e(o);return Ca(...t,...r,o,a,n)?null:[o,a]},Nn=(e,t,r,n=.1)=>{const o=(r-t)/10,a=[];for(let s=t;sMath.round(e*10**t)/10**t,su=(e,t=1,r=90,n=.005)=>{const o=Nn(i=>e(i).gl()[0],0,t,n),a=Nn(i=>e(i).gl()[1],0,t,n),s=Nn(i=>e(i).gl()[2],0,t,n),c=Array.from(new Set([...o.map(i=>Lt(i[0])),...a.map(i=>Lt(i[0])),...s.map(i=>Lt(i[0]))].sort((i,u)=>i-u)));return`linear-gradient(${r}deg, ${c.map(i=>`${e(i).hex()} ${Lt(i*100)}%`).join()});`},au=e=>{e.Color.prototype.jch=function(){return On(this._rgb.slice(0,3).map(o=>o/255))},e.jch=(...o)=>new e.Color(...ka(o).map(a=>Math.floor(a*255)),"rgb"),e.Color.prototype.jab=function(){return $a(this._rgb.slice(0,3).map(o=>o/255))},e.jab=(...o)=>new e.Color(...nu(o).map(a=>Math.floor(a*255)),"rgb"),e.Color.prototype.hsluv=function(){return gr.rgbToHsluv(this._rgb.slice(0,3).map(o=>o/255))},e.hsluv=(...o)=>new e.Color(...gr.hsluvToRgb(o).map(a=>Math.floor(a*255)),"rgb");const t=e.interpolate,r={jch:On,jab:$a,hsluv:gr.rgbToHsluv},n=(o,a,s)=>(Math.abs(o-a)>360/2&&(o>a?a+=360:o+=360),((1-s)*o+s*a)%360);e.interpolate=(o,a,s=.5,c="lrgb")=>{if(r[c]){typeof o!="object"&&(o=new e.Color(o)),typeof a!="object"&&(a=new e.Color(a));const i=r[c](o.gl()),u=r[c](a.gl()),l=Number.isNaN(o.hsl()[0]),f=Number.isNaN(a.hsl()[0]);let h,d,b;switch(c){case"hsluv":i[1]<1e-10&&(i[0]=u[0]),i[1]===0&&(i[1]=u[1]),u[1]<1e-10&&(u[0]=i[0]),u[1]===0&&(u[1]=i[1]),h=n(i[0],u[0],s),d=i[1]+(u[1]-i[1])*s,b=i[2]+(u[2]-i[2])*s;break;case"jch":l&&(i[2]=u[2]),f&&(u[2]=i[2]),h=i[0]+(u[0]-i[0])*s,d=i[1]+(u[1]-i[1])*s,b=n(i[2],u[2],s);break;default:h=i[0]+(u[0]-i[0])*s,d=i[1]+(u[1]-i[1])*s,b=i[2]+(u[2]-i[2])*s}return e[c](h,d,b).alpha(o.alpha()+s*(a.alpha()-o.alpha()))}return t(o,a,s,c)},e.getCSSGradient=su};const Y={mainTRC:2.4,sRco:.2126729,sGco:.7151522,sBco:.072175,normBG:.56,normTXT:.57,revTXT:.62,revBG:.65,blkThrs:.022,blkClmp:1.414,scaleBoW:1.14,scaleWoB:1.14,loBoWoffset:.027,loWoBoffset:.027,deltaYmin:5e-4,loClip:.1};function xa(e,t,r=-1){const n=[0,1.1];if(isNaN(e)||isNaN(t)||Math.min(e,t)n[1])return 0;let o=0,a=0,s="BoW";return e=e>Y.blkThrs?e:e+Math.pow(Y.blkThrs-e,Y.blkClmp),t=t>Y.blkThrs?t:t+Math.pow(Y.blkThrs-t,Y.blkClmp),Math.abs(t-e)e?(o=(Math.pow(t,Y.normBG)-Math.pow(e,Y.normTXT))*Y.scaleBoW,a=o-.1?0:o+Y.loWoBoffset),r<0?a*100:r==0?Math.round(Math.abs(a)*100)+""+s+"":Number.isInteger(r)?(a*100).toFixed(r):0)}function Et(e=[0,0,0]){function t(r){return Math.pow(r/255,Y.mainTRC)}return Y.sRco*t(e[0])+Y.sGco*t(e[1])+Y.sBco*t(e[2])}const Ra=(e,t,r,n,o,a,s,c,i)=>{const u=1-i,l=u*u,f=l*u,d=i*i*i,b=f*e+l*3*i*r+u*3*i*i*o+d*s,_=f*t+l*3*i*n+u*3*i*i*a+d*c;return{x:b,y:_}},cu=(e,t)=>{const r=[];let n={x:+e[0],y:+e[1]};for(let o=0,a=e.length;a-2*!0>o;o+=2){const s=[{x:+e[o-2],y:+e[o-1]},{x:+e[o],y:+e[o+1]},{x:+e[o+2],y:+e[o+3]},{x:+e[o+4],y:+e[o+5]}];a-4===o?s[3]=s[2]:o||(s[0]={x:+e[o],y:+e[o+1]}),r.push([n.x,n.y,(-s[0].x+6*s[1].x+s[2].x)/6,(-s[0].y+6*s[1].y+s[2].y)/6,(s[1].x+6*s[2].x-s[3].x)/6,(s[1].y+6*s[2].y-s[3].y)/6,s[2].x,s[2].y]),n=s[2]}return r},iu=(e,t,r,n,o,a,s,c)=>{let u=e,l=t,f=0;for(let h=1;h<5;h++){const{x:d,y:b}=Ra(e,t,r,n,o,a,s,c,h/5);f+=Math.hypot(d-u,b-l),u=d,l=b}return f+=Math.hypot(s-u,c-l),f},uu=(e,t,r,n,o,a,s,c)=>{const i=Math.floor(iu(e,t,r,n,o,a,s,c)*.75),u=[];let l=0;for(let f=0;f<=i;f++){const h=f/i,d=Ra(e,t,r,n,o,a,s,c,h),b=Math.round(d.x);if(u[b]=d.y,b-l>1){const _=u[l],g=u[b];for(let v=l+1;vu[Math.round(f)]||null},Ze={CAM02:"jab",CAM02p:"jch",HEX:"hex",HSL:"hsl",HSLuv:"hsluv",HSV:"hsv",LAB:"lab",LCH:"lch",RGB:"rgb",OKLAB:"oklab",OKLCH:"oklch"};function Ee(e,t=0){const r=10**t;return Math.round(e*r)/r}function fu(e,t){let r;return e>1?r=(e-1)*t+1:e<-1?r=(e+1)*t-1:r=1,Ee(r,2)}function lu(e){return O(String(e)).jch()}function hu(e){return O(String(e)).hsluv()}function du(e,t,r){const n=[[],[],[]];if(e.forEach((a,s)=>n.forEach((c,i)=>c.push(t[s],a[i]))),r==="hcl"){const a=n[1];for(let s=1;s{const s=[];for(let c=1;c{a[i]=a[c]}),s.length=0;break}if(s.length){const c=O("#ccc").jch()[2];s.forEach(i=>{a[i]=c})}s.length=0;for(let c=a.length-1;c>0;c-=2)if(Number.isNaN(a[c]))s.push(c);else{s.forEach(i=>{a[i]=a[c]});break}for(let c=1;ccu(a).map(s=>uu(...s)));return a=>{const s=o.map(c=>{for(let i=0;in*a**e+o}function An({swatches:e,colorKeys:t,colorspace:r="LAB",shift:n=1,fullScale:o=!0,smooth:a=!1,distributeLightness:s="linear",sortColor:c=!0,asFun:i=!1}={}){const u=Ze[r];if(!u)throw new Error(`Colorspace “${r}” not supported`);if(!t)throw new Error(`Colorkeys missing: returned “${t}”`);let l;if(o)l=t.map(H=>e-e*(O(H).jch()[0]/100)).sort((H,x)=>H-x).concat(e),l.unshift(0);else{let H=t.map(A=>O(A).jch()[0]/100),x=Math.min(...H),N=Math.max(...H);l=H.map(A=>A===0||isNaN((A-x)/(N-x))?0:e-(A-x)/(N-x)*e).sort((A,R)=>A-R)}let f=bu(n,[1,e],[1,e]);if(f=l.map(H=>Math.max(0,f(H))),l=f,s==="polynomial"){const H=A=>Math.sqrt(Math.sqrt((Math.pow(A,2.25)+Math.pow(A,4))/2));l=f.map(A=>A/e).map(A=>H(A)*e)}const h=t.map((H,x)=>({colorKeys:lu(H),index:x})).sort((H,x)=>x.colorKeys[0]-H.colorKeys[0]).map(H=>t[H.index]);let d=[],b;if(o){const H=u==="lch"?O.lch(...O("#fff").lch()):"#ffffff",x=u==="lch"?O.lch(...O("#000").lch()):"#000000";d=[H,...h,x]}else c?d=h:d=t;let _;if(a){const H=d;if(d=d.map(x=>O(String(x))[u]()),u==="hcl"&&d.forEach(x=>{x[1]=Number.isNaN(x[1])?0:x[1]}),u==="jch")for(let x=0;xb(N))}else b=O.scale(d.map(H=>typeof H=="object"&&H.constructor===O.Color?H:String(H))).domain(l).mode(u);return i?b:(!a||a===!1?b.colors(e):_).filter(H=>H!=null)}function pu(e,t){const r=[],n={};return Object.keys(e).forEach(s=>{n[e[s][t]]=e[s]}),Object.keys(n).forEach(s=>r.push(n[s])),r}function mu(e){return Number.isNaN(e)?0:e}function Ln(e,t,r=!1){if(!e)throw new Error(`Cannot convert color value of “${e}”`);if(!Ze[t])throw new Error(`Cannot convert to colorspace “${t}”`);const n=Ze[t],o=O(String(e))[n]();if(t==="HSL"&&o.pop(),t==="HEX"){if(r){const u=O(String(e)).rgb();return{r:u[0],g:u[1],b:u[2]}}return o}const a={};let s=o.map(mu);s=s.map((u,l)=>{let f=Ee(u),h=l;n==="hsluv"&&(h+=2);let d=n.charAt(h);return n==="jch"&&d==="c"&&(d="C"),a[d==="j"?"J":d]=f,n in{lab:1,lch:1,jab:1,jch:1}?r||((d==="l"||d==="j")&&(f+="%"),d==="h"&&(f+="deg")):n!=="hsluv"&&(d==="s"||d==="l"||d==="v"?(a[d]=Ee(u,2),r||(f=Ee(u*100),f+="%")):d==="h"&&!r&&(f+="deg")),f});const i=`${n}(${s.join(", ")})`;return r?a:i}function Ha(e,t,r){const n=[e,t,r].map(o=>(o/=255,o<=.03928?o/12.92:((o+.055)/1.055)**2.4));return n[0]*.2126+n[1]*.7152+n[2]*.0722}function gu(e,t,r,n="wcag2"){if(r===void 0){const o=O.rgb(...t).hsluv()[2];r=Ee(o/100,2)}if(n==="wcag2"){const o=Ha(e[0],e[1],e[2]),a=Ha(t[0],t[1],t[2]),s=(o+.05)/(a+.05),c=(a+.05)/(o+.05);return r<.5?s>=1?s:-c:s<1?c:s===1?s:-s}else{if(n==="wcag3")return r<.5?xa(Et(e),Et(t))*-1:xa(Et(e),Et(t));throw new Error(`Contrast calculation method ${n} unsupported; use 'wcag2' or 'wcag3'`)}}function vu(e,t){if(!e)throw new Error("Array undefined");if(!Array.isArray(e))throw new Error("Passed object is not an array");const r=t==="wcag2"?0:1;return Math.min(...e.filter(n=>n>=r))}function _u(e,t){if(!e)throw new Error("Ratios undefined");e=e.sort((c,i)=>c-i);const r=vu(e,t),n=e.indexOf(r),o=[],a=e.slice(0,n),s=e.slice(n,e.length);for(let c=0;cc-i),o}const yu=(e,t,r,n,o)=>{const s=An({swatches:3e3,colorKeys:e._modifiedKeys,colorspace:e._colorspace,shift:1,smooth:e._smooth,asFun:!0}),c={},i=f=>{if(c[f])return c[f];const h=O(s(f)).rgb(),d=gu(h,t,r,o);return c[f]=d,d},u=f=>{const h=i(0),d=i(3e3),b=h_&&x;)x--,g/=2,Hl.push(s(u(+f)))),l};let ae=class{constructor({name:t,colorKeys:r,colorspace:n="RGB",ratios:o,smooth:a=!1,output:s="HEX",saturation:c=100}){if(this._name=t,this._colorKeys=r,this._modifiedKeys=r,this._colorspace=n,this._ratios=o,this._smooth=a,this._output=s,this._saturation=c,!this._name)throw new Error("Color missing name");if(!this._colorKeys)throw new Error("Color Keys are undefined");if(!Ze[this._colorspace])throw new Error(`Colorspace “${n}” not supported`);if(!Ze[this._output])throw new Error(`Output “${n}” not supported`);for(let i=0;i{let n=O(`${r}`).oklch(),a=n[1]*(this._saturation/100),s=O.oklch(n[0],a,n[2]),c=O.rgb(s).hex();t.push(c)}),this._modifiedKeys=t,this._generateColorScale()}_generateColorScale(){this._colorScale=An({swatches:3e3,colorKeys:this._modifiedKeys,colorspace:this._colorspace,shift:1,smooth:this._smooth,asFun:!0})}};class qa extends ae{get backgroundColorScale(){return this._backgroundColorScale||this._generateColorScale(),this._backgroundColorScale}_generateColorScale(){ae.prototype._generateColorScale.call(this);const t=An({swatches:1e3,colorKeys:this._colorKeys,colorspace:this._colorspace,shift:1,smooth:this._smooth});t.push(...this.colorKeys);const r=t.map((a,s)=>({value:Math.round(hu(a)[2]),index:s})),o=pu(r,"value").map(a=>t[a.index]);return o.length>=101&&(o.length=100,o.push("#ffffff")),this._backgroundColorScale=o.map(a=>Ln(a,this._output)),this._backgroundColorScale}}class wu{constructor({colors:t,backgroundColor:r,lightness:n,contrast:o=1,saturation:a=100,output:s="HEX",formula:c="wcag2"}){if(this._output=s,this._colors=t,this._lightness=n,this._saturation=a,this._formula=c,this._setBackgroundColor(r),this._setBackgroundColorValue(),this._contrast=o,!this._colors)throw new Error("No colors are defined");if(!this._backgroundColor)throw new Error("Background color is undefined");if(t.forEach(i=>{if(!i.ratios)throw new Error(`Color ${i.name}'s ratios are undefined`)}),!Ze[this._output])throw new Error(`Output “${s}” not supported`);this._saturation<100&&this._updateColorSaturation(this._saturation),this._findContrastColors(),this._findContrastColorPairs(),this._findContrastColorValues()}set formula(t){this._formula=t,this._findContrastColors()}get formula(){return this._formula}set contrast(t){this._contrast=t,this._findContrastColors()}get contrast(){return this._contrast}set lightness(t){this._lightness=t,this._setBackgroundColor(this._backgroundColor),this._findContrastColors()}get lightness(){return this._lightness}set saturation(t){this._saturation=t,this._updateColorSaturation(t),this._findContrastColors()}get saturation(){return this._saturation}set backgroundColor(t){this._setBackgroundColor(t),this._findContrastColors()}get backgroundColorValue(){return this._backgroundColorValue}get backgroundColor(){return this._backgroundColor}set colors(t){this._colors=t,this._findContrastColors()}get colors(){return this._colors}set addColor(t){this._colors.push(t),this._findContrastColors()}set removeColor(t){const r=this._colors.filter(n=>n.name!==t.name);this._colors=r,this._findContrastColors()}set updateColor(t){if(Array.isArray(t))for(let r=0;rs.name===t[r].color);n=n[0];let o=this._colors.indexOf(n);const a=this._colors.filter(s=>s.name!==t[r].color);t[r].name&&(n.name=t[r].name),t[r].colorKeys&&(n.colorKeys=t[r].colorKeys),t[r].ratios&&(n.ratios=t[r].ratios),t[r].colorspace&&(n.colorspace=t[r].colorspace),t[r].smooth&&(n.smooth=t[r].smooth),n._generateColorScale(),a.splice(o,0,n),this._colors=a}else{let r=this._colors.filter(a=>a.name===t.color);r=r[0];let n=this._colors.indexOf(r);const o=this._colors.filter(a=>a.name!==t.color);t.name&&(r.name=t.name),t.colorKeys&&(r.colorKeys=t.colorKeys),t.ratios&&(r.ratios=t.ratios),t.colorspace&&(r.colorspace=t.colorspace),t.smooth&&(r.smooth=t.smooth),r._generateColorScale(),o.splice(n,0,r),this._colors=o}this._findContrastColors()}set output(t){this._output=t,this._colors.forEach(r=>{r.output=this._output}),this._backgroundColor.output=this._output,this._findContrastColors()}get output(){return this._output}get contrastColors(){return this._contrastColors}get contrastColorPairs(){return this._contrastColorPairs}get contrastColorValues(){return this._contrastColorValues}_setBackgroundColor(t){if(typeof t=="string"){const r=new qa({name:"background",colorKeys:[t],output:"RGB"}),n=Ee(O(String(t)).hsluv()[2]);this._backgroundColor=r,this._lightness=n,this._backgroundColorValue=r[this._lightness]}else{t.output="RGB";const r=t.backgroundColorScale[this._lightness];this._backgroundColor=t,this._backgroundColorValue=r}}_setBackgroundColorValue(){this._backgroundColorValue=this._backgroundColor.backgroundColorScale[this._lightness]}_updateColorSaturation(t){this._colors.map(r=>{r.saturation=t})}_findContrastColors(){const t=O(String(this._backgroundColorValue)).rgb(),r=this._lightness/100,o={background:Ln(this._backgroundColorValue,this._output)},a=[],s=[],c={...o};return a.push(o),this._colors.map(i=>{if(i.ratios!==void 0){let u;const l=[],f={name:i.name,values:l};let h;Array.isArray(i.ratios)?h=i.ratios:Array.isArray(i.ratios)||(u=Object.keys(i.ratios),h=Object.values(i.ratios)),h=h.map(b=>fu(+b,this._contrast));const d=yu(i,t,r,h,this._formula).map(b=>Ln(b,this._output));for(let b=0;bku($u(t,e),r),En=e=>{e._clipped=!1,e._unclipped=e.slice(0);for(let t=0;t<=3;t++)t<3?((e[t]<0||e[t]>255)&&(e._clipped=!0),e[t]=Be(e[t],0,255)):t===3&&(e[t]=Be(e[t],0,1));return e},Ma={};for(let e of["Boolean","Number","String","Function","Array","Date","RegExp","Undefined","Null"])Ma[`[object ${e}]`]=e.toLowerCase();function B(e){return Ma[Object.prototype.toString.call(e)]||"object"}const T=(e,t=null)=>e.length>=3?Array.prototype.slice.call(e):B(e[0])=="object"&&t?t.split("").filter(r=>e[0][r]!==void 0).map(r=>e[0][r]):e[0].slice(0),Je=e=>{if(e.length<2)return null;const t=e.length-1;return B(e[t])=="string"?e[t].toLowerCase():null},{PI:Tt,min:Oa,max:Na}=Math,ue=e=>Math.round(e*100)/100,Tn=e=>Math.round(e*100)/100,Ce=Tt*2,Sn=Tt/3,Cu=Tt/180,xu=180/Tt;function Aa(e){return[...e.slice(0,3).reverse(),...e.slice(3)]}const E={format:{},autodetect:[]};class k{constructor(...t){const r=this;if(B(t[0])==="object"&&t[0].constructor&&t[0].constructor===this.constructor)return t[0];let n=Je(t),o=!1;if(!n){o=!0,E.sorted||(E.autodetect=E.autodetect.sort((a,s)=>s.p-a.p),E.sorted=!0);for(let a of E.autodetect)if(n=a.test(...t),n)break}if(E.format[n]){const a=E.format[n].apply(null,o?t:t.slice(0,-1));r._rgb=En(a)}else throw new Error("unknown format: "+t);r._rgb.length===3&&r._rgb.push(1)}toString(){return B(this.hex)=="function"?this.hex():`[${this._rgb.join(",")}]`}}const Ru="3.2.0",G=(...e)=>new k(...e);G.version=Ru;const We={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",laserlemon:"#ffff54",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrod:"#fafad2",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",maroon2:"#7f0000",maroon3:"#b03060",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",purple2:"#7f007f",purple3:"#a020f0",rebeccapurple:"#663399",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"},Hu=/^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/,qu=/^#?([A-Fa-f0-9]{8}|[A-Fa-f0-9]{4})$/,La=e=>{if(e.match(Hu)){(e.length===4||e.length===7)&&(e=e.substr(1)),e.length===3&&(e=e.split(""),e=e[0]+e[0]+e[1]+e[1]+e[2]+e[2]);const t=parseInt(e,16),r=t>>16,n=t>>8&255,o=t&255;return[r,n,o,1]}if(e.match(qu)){(e.length===5||e.length===9)&&(e=e.substr(1)),e.length===4&&(e=e.split(""),e=e[0]+e[0]+e[1]+e[1]+e[2]+e[2]+e[3]+e[3]);const t=parseInt(e,16),r=t>>24&255,n=t>>16&255,o=t>>8&255,a=Math.round((t&255)/255*100)/100;return[r,n,o,a]}throw new Error(`unknown hex color: ${e}`)},{round:St}=Math,Ea=(...e)=>{let[t,r,n,o]=T(e,"rgba"),a=Je(e)||"auto";o===void 0&&(o=1),a==="auto"&&(a=o<1?"rgba":"rgb"),t=St(t),r=St(r),n=St(n);let c="000000"+(t<<16|r<<8|n).toString(16);c=c.substr(c.length-6);let i="0"+St(o*255).toString(16);switch(i=i.substr(i.length-2),a.toLowerCase()){case"rgba":return`#${c}${i}`;case"argb":return`#${i}${c}`;default:return`#${c}`}};k.prototype.name=function(){const e=Ea(this._rgb,"rgb");for(let t of Object.keys(We))if(We[t]===e)return t.toLowerCase();return e},E.format.named=e=>{if(e=e.toLowerCase(),We[e])return La(We[e]);throw new Error("unknown color name: "+e)},E.autodetect.push({p:5,test:(e,...t)=>{if(!t.length&&B(e)==="string"&&We[e.toLowerCase()])return"named"}}),k.prototype.alpha=function(e,t=!1){return e!==void 0&&B(e)==="number"?t?(this._rgb[3]=e,this):new k([this._rgb[0],this._rgb[1],this._rgb[2],e],"rgb"):this._rgb[3]},k.prototype.clipped=function(){return this._rgb._clipped||!1};const _e={Kn:18,labWhitePoint:"d65",Xn:.95047,Yn:1,Zn:1.08883,kE:216/24389,kKE:8,kK:24389/27,RefWhiteRGB:{X:.95047,Y:1,Z:1.08883},MtxRGB2XYZ:{m00:.4124564390896922,m01:.21267285140562253,m02:.0193338955823293,m10:.357576077643909,m11:.715152155287818,m12:.11919202588130297,m20:.18043748326639894,m21:.07217499330655958,m22:.9503040785363679},MtxXYZ2RGB:{m00:3.2404541621141045,m01:-.9692660305051868,m02:.055643430959114726,m10:-1.5371385127977166,m11:1.8760108454466942,m12:-.2040259135167538,m20:-.498531409556016,m21:.041556017530349834,m22:1.0572251882231791},As:.9414285350000001,Bs:1.040417467,Cs:1.089532651,MtxAdaptMa:{m00:.8951,m01:-.7502,m02:.0389,m10:.2664,m11:1.7135,m12:-.0685,m20:-.1614,m21:.0367,m22:1.0296},MtxAdaptMaI:{m00:.9869929054667123,m01:.43230526972339456,m02:-.008528664575177328,m10:-.14705425642099013,m11:.5183602715367776,m12:.04004282165408487,m20:.15996265166373125,m21:.0492912282128556,m22:.9684866957875502}},Mu=new Map([["a",[1.0985,.35585]],["b",[1.0985,.35585]],["c",[.98074,1.18232]],["d50",[.96422,.82521]],["d55",[.95682,.92149]],["d65",[.95047,1.08883]],["e",[1,1,1]],["f2",[.99186,.67393]],["f7",[.95041,1.08747]],["f11",[1.00962,.6435]],["icc",[.96422,.82521]]]);function xe(e){const t=Mu.get(String(e).toLowerCase());if(!t)throw new Error("unknown Lab illuminant "+e);_e.labWhitePoint=e,_e.Xn=t[0],_e.Zn=t[1]}function dt(){return _e.labWhitePoint}const Pn=(...e)=>{e=T(e,"lab");const[t,r,n]=e,[o,a,s]=Ou(t,r,n),[c,i,u]=Ta(o,a,s);return[c,i,u,e.length>3?e[3]:1]},Ou=(e,t,r)=>{const{kE:n,kK:o,kKE:a,Xn:s,Yn:c,Zn:i}=_e,u=(e+16)/116,l=.002*t+u,f=u-.005*r,h=l*l*l,d=f*f*f,b=h>n?h:(116*l-16)/o,_=e>a?Math.pow((e+16)/116,3):e/o,g=d>n?d:(116*f-16)/o,v=b*s,H=_*c,x=g*i;return[v,H,x]},jn=e=>{const t=Math.sign(e);return e=Math.abs(e),(e<=.0031308?e*12.92:1.055*Math.pow(e,1/2.4)-.055)*t},Ta=(e,t,r)=>{const{MtxAdaptMa:n,MtxAdaptMaI:o,MtxXYZ2RGB:a,RefWhiteRGB:s,Xn:c,Yn:i,Zn:u}=_e,l=c*n.m00+i*n.m10+u*n.m20,f=c*n.m01+i*n.m11+u*n.m21,h=c*n.m02+i*n.m12+u*n.m22,d=s.X*n.m00+s.Y*n.m10+s.Z*n.m20,b=s.X*n.m01+s.Y*n.m11+s.Z*n.m21,_=s.X*n.m02+s.Y*n.m12+s.Z*n.m22,g=(e*n.m00+t*n.m10+r*n.m20)*(d/l),v=(e*n.m01+t*n.m11+r*n.m21)*(b/f),H=(e*n.m02+t*n.m12+r*n.m22)*(_/h),x=g*o.m00+v*o.m10+H*o.m20,N=g*o.m01+v*o.m11+H*o.m21,A=g*o.m02+v*o.m12+H*o.m22,R=jn(x*a.m00+N*a.m10+A*a.m20),m=jn(x*a.m01+N*a.m11+A*a.m21),p=jn(x*a.m02+N*a.m12+A*a.m22);return[R*255,m*255,p*255]},Bn=(...e)=>{const[t,r,n,...o]=T(e,"rgb"),[a,s,c]=Sa(t,r,n),[i,u,l]=Nu(a,s,c);return[i,u,l,...o.length>0&&o[0]<1?[o[0]]:[]]};function Nu(e,t,r){const{Xn:n,Yn:o,Zn:a,kE:s,kK:c}=_e,i=e/n,u=t/o,l=r/a,f=i>s?Math.pow(i,1/3):(c*i+16)/116,h=u>s?Math.pow(u,1/3):(c*u+16)/116,d=l>s?Math.pow(l,1/3):(c*l+16)/116;return[116*h-16,500*(f-h),200*(h-d)]}function Gn(e){const t=Math.sign(e);return e=Math.abs(e),(e<=.04045?e/12.92:Math.pow((e+.055)/1.055,2.4))*t}const Sa=(e,t,r)=>{e=Gn(e/255),t=Gn(t/255),r=Gn(r/255);const{MtxRGB2XYZ:n,MtxAdaptMa:o,MtxAdaptMaI:a,Xn:s,Yn:c,Zn:i,As:u,Bs:l,Cs:f}=_e;let h=e*n.m00+t*n.m10+r*n.m20,d=e*n.m01+t*n.m11+r*n.m21,b=e*n.m02+t*n.m12+r*n.m22;const _=s*o.m00+c*o.m10+i*o.m20,g=s*o.m01+c*o.m11+i*o.m21,v=s*o.m02+c*o.m12+i*o.m22;let H=h*o.m00+d*o.m10+b*o.m20,x=h*o.m01+d*o.m11+b*o.m21,N=h*o.m02+d*o.m12+b*o.m22;return H*=_/u,x*=g/l,N*=v/f,h=H*a.m00+x*a.m10+N*a.m20,d=H*a.m01+x*a.m11+N*a.m21,b=H*a.m02+x*a.m12+N*a.m22,[h,d,b]};k.prototype.lab=function(){return Bn(this._rgb)},Object.assign(G,{lab:(...e)=>new k(...e,"lab"),getLabWhitePoint:dt,setLabWhitePoint:xe}),E.format.lab=Pn,E.autodetect.push({p:2,test:(...e)=>{if(e=T(e,"lab"),B(e)==="array"&&e.length===3)return"lab"}}),k.prototype.darken=function(e=1){const t=this,r=t.lab();return r[0]-=_e.Kn*e,new k(r,"lab").alpha(t.alpha(),!0)},k.prototype.brighten=function(e=1){return this.darken(-e)},k.prototype.darker=k.prototype.darken,k.prototype.brighter=k.prototype.brighten,k.prototype.get=function(e){const[t,r]=e.split("."),n=this[t]();if(r){const o=t.indexOf(r)-(t.substr(0,2)==="ok"?2:0);if(o>-1)return n[o];throw new Error(`unknown channel ${r} in mode ${t}`)}else return n};const{pow:Au}=Math,Lu=1e-7,Eu=20;k.prototype.luminance=function(e,t="rgb"){if(e!==void 0&&B(e)==="number"){if(e===0)return new k([0,0,0,this._rgb[3]],"rgb");if(e===1)return new k([255,255,255,this._rgb[3]],"rgb");let r=this.luminance(),n=Eu;const o=(s,c)=>{const i=s.interpolate(c,.5,t),u=i.luminance();return Math.abs(e-u)e?o(s,i):o(i,c)},a=(r>e?o(new k([0,0,0]),this):o(this,new k([255,255,255]))).rgb();return new k([...a,this._rgb[3]])}return Tu(...this._rgb.slice(0,3))};const Tu=(e,t,r)=>(e=In(e),t=In(t),r=In(r),.2126*e+.7152*t+.0722*r),In=e=>(e/=255,e<=.03928?e/12.92:Au((e+.055)/1.055,2.4)),ne={},Ue=(e,t,r=.5,...n)=>{let o=n[0]||"lrgb";if(!ne[o]&&!n.length&&(o=Object.keys(ne)[0]),!ne[o])throw new Error(`interpolation mode ${o} is not defined`);return B(e)!=="object"&&(e=new k(e)),B(t)!=="object"&&(t=new k(t)),ne[o](e,t,r).alpha(e.alpha()+r*(t.alpha()-e.alpha()))};k.prototype.mix=k.prototype.interpolate=function(e,t=.5,...r){return Ue(this,e,t,...r)},k.prototype.premultiply=function(e=!1){const t=this._rgb,r=t[3];return e?(this._rgb=[t[0]*r,t[1]*r,t[2]*r,r],this):new k([t[0]*r,t[1]*r,t[2]*r,r],"rgb")};const{sin:Su,cos:Pu}=Math,Pa=(...e)=>{let[t,r,n]=T(e,"lch");return isNaN(n)&&(n=0),n=n*Cu,[t,Pu(n)*r,Su(n)*r]},zn=(...e)=>{e=T(e,"lch");const[t,r,n]=e,[o,a,s]=Pa(t,r,n),[c,i,u]=Pn(o,a,s);return[c,i,u,e.length>3?e[3]:1]},ju=(...e)=>{const t=Aa(T(e,"hcl"));return zn(...t)},{sqrt:Bu,atan2:Gu,round:Iu}=Math,ja=(...e)=>{const[t,r,n]=T(e,"lab"),o=Bu(r*r+n*n);let a=(Gu(n,r)*xu+360)%360;return Iu(o*1e4)===0&&(a=Number.NaN),[t,o,a]},Fn=(...e)=>{const[t,r,n,...o]=T(e,"rgb"),[a,s,c]=Bn(t,r,n),[i,u,l]=ja(a,s,c);return[i,u,l,...o.length>0&&o[0]<1?[o[0]]:[]]};k.prototype.lch=function(){return Fn(this._rgb)},k.prototype.hcl=function(){return Aa(Fn(this._rgb))},Object.assign(G,{lch:(...e)=>new k(...e,"lch"),hcl:(...e)=>new k(...e,"hcl")}),E.format.lch=zn,E.format.hcl=ju,["lch","hcl"].forEach(e=>E.autodetect.push({p:2,test:(...t)=>{if(t=T(t,e),B(t)==="array"&&t.length===3)return e}})),k.prototype.saturate=function(e=1){const t=this,r=t.lch();return r[1]+=_e.Kn*e,r[1]<0&&(r[1]=0),new k(r,"lch").alpha(t.alpha(),!0)},k.prototype.desaturate=function(e=1){return this.saturate(-e)},k.prototype.set=function(e,t,r=!1){const[n,o]=e.split("."),a=this[n]();if(o){const s=n.indexOf(o)-(n.substr(0,2)==="ok"?2:0);if(s>-1){if(B(t)=="string")switch(t.charAt(0)){case"+":a[s]+=+t;break;case"-":a[s]+=+t;break;case"*":a[s]*=+t.substr(1);break;case"/":a[s]/=+t.substr(1);break;default:a[s]=+t}else if(B(t)==="number")a[s]=t;else throw new Error("unsupported value for Color.set");const c=new k(a,n);return r?(this._rgb=c._rgb,this):c}throw new Error(`unknown channel ${o} in mode ${n}`)}else return a},k.prototype.tint=function(e=.5,...t){return Ue(this,"white",e,...t)},k.prototype.shade=function(e=.5,...t){return Ue(this,"black",e,...t)};const zu=(e,t,r)=>{const n=e._rgb,o=t._rgb;return new k(n[0]+r*(o[0]-n[0]),n[1]+r*(o[1]-n[1]),n[2]+r*(o[2]-n[2]),"rgb")};ne.rgb=zu;const{sqrt:Xn,pow:Qe}=Math,Fu=(e,t,r)=>{const[n,o,a]=e._rgb,[s,c,i]=t._rgb;return new k(Xn(Qe(n,2)*(1-r)+Qe(s,2)*r),Xn(Qe(o,2)*(1-r)+Qe(c,2)*r),Xn(Qe(a,2)*(1-r)+Qe(i,2)*r),"rgb")};ne.lrgb=Fu;const Xu=(e,t,r)=>{const n=e.lab(),o=t.lab();return new k(n[0]+r*(o[0]-n[0]),n[1]+r*(o[1]-n[1]),n[2]+r*(o[2]-n[2]),"lab")};ne.lab=Xu;const et=(e,t,r,n)=>{let o,a;n==="hsl"?(o=e.hsl(),a=t.hsl()):n==="hsv"?(o=e.hsv(),a=t.hsv()):n==="hcg"?(o=e.hcg(),a=t.hcg()):n==="hsi"?(o=e.hsi(),a=t.hsi()):n==="lch"||n==="hcl"?(n="hcl",o=e.hcl(),a=t.hcl()):n==="oklch"&&(o=e.oklch().reverse(),a=t.oklch().reverse());let s,c,i,u,l,f;(n.substr(0,1)==="h"||n==="oklch")&&([s,i,l]=o,[c,u,f]=a);let h,d,b,_;return!isNaN(s)&&!isNaN(c)?(c>s&&c-s>180?_=c-(s+360):c180?_=c+360-s:_=c-s,d=s+r*_):isNaN(s)?isNaN(c)?d=Number.NaN:(d=c,(l==1||l==0)&&n!="hsv"&&(h=u)):(d=s,(f==1||f==0)&&n!="hsv"&&(h=i)),h===void 0&&(h=i+r*(u-i)),b=l+r*(f-l),n==="oklch"?new k([b,h,d],n):new k([d,h,b],n)},Ba=(e,t,r)=>et(e,t,r,"lch");ne.lch=Ba,ne.hcl=Ba;const Ku=e=>{if(B(e)=="number"&&e>=0&&e<=16777215){const t=e>>16,r=e>>8&255,n=e&255;return[t,r,n,1]}throw new Error("unknown num color: "+e)},Du=(...e)=>{const[t,r,n]=T(e,"rgb");return(t<<16)+(r<<8)+n};k.prototype.num=function(){return Du(this._rgb)},Object.assign(G,{num:(...e)=>new k(...e,"num")}),E.format.num=Ku,E.autodetect.push({p:5,test:(...e)=>{if(e.length===1&&B(e[0])==="number"&&e[0]>=0&&e[0]<=16777215)return"num"}});const Vu=(e,t,r)=>{const n=e.num(),o=t.num();return new k(n+r*(o-n),"num")};ne.num=Vu;const{floor:Yu}=Math,Zu=(...e)=>{e=T(e,"hcg");let[t,r,n]=e,o,a,s;n=n*255;const c=r*255;if(r===0)o=a=s=n;else{t===360&&(t=0),t>360&&(t-=360),t<0&&(t+=360),t/=60;const i=Yu(t),u=t-i,l=n*(1-r),f=l+c*(1-u),h=l+c*u,d=l+c;switch(i){case 0:[o,a,s]=[d,h,l];break;case 1:[o,a,s]=[f,d,l];break;case 2:[o,a,s]=[l,d,h];break;case 3:[o,a,s]=[l,f,d];break;case 4:[o,a,s]=[h,l,d];break;case 5:[o,a,s]=[d,l,f];break}}return[o,a,s,e.length>3?e[3]:1]},Ju=(...e)=>{const[t,r,n]=T(e,"rgb"),o=Oa(t,r,n),a=Na(t,r,n),s=a-o,c=s*100/255,i=o/(255-s)*100;let u;return s===0?u=Number.NaN:(t===a&&(u=(r-n)/s),r===a&&(u=2+(n-t)/s),n===a&&(u=4+(t-r)/s),u*=60,u<0&&(u+=360)),[u,c,i]};k.prototype.hcg=function(){return Ju(this._rgb)};const Wu=(...e)=>new k(...e,"hcg");G.hcg=Wu,E.format.hcg=Zu,E.autodetect.push({p:1,test:(...e)=>{if(e=T(e,"hcg"),B(e)==="array"&&e.length===3)return"hcg"}});const Uu=(e,t,r)=>et(e,t,r,"hcg");ne.hcg=Uu;const{cos:tt}=Math,Qu=(...e)=>{e=T(e,"hsi");let[t,r,n]=e,o,a,s;return isNaN(t)&&(t=0),isNaN(r)&&(r=0),t>360&&(t-=360),t<0&&(t+=360),t/=360,t<1/3?(s=(1-r)/3,o=(1+r*tt(Ce*t)/tt(Sn-Ce*t))/3,a=1-(s+o)):t<2/3?(t-=1/3,o=(1-r)/3,a=(1+r*tt(Ce*t)/tt(Sn-Ce*t))/3,s=1-(o+a)):(t-=2/3,a=(1-r)/3,s=(1+r*tt(Ce*t)/tt(Sn-Ce*t))/3,o=1-(a+s)),o=Be(n*o*3),a=Be(n*a*3),s=Be(n*s*3),[o*255,a*255,s*255,e.length>3?e[3]:1]},{min:ef,sqrt:tf,acos:rf}=Math,nf=(...e)=>{let[t,r,n]=T(e,"rgb");t/=255,r/=255,n/=255;let o;const a=ef(t,r,n),s=(t+r+n)/3,c=s>0?1-a/s:0;return c===0?o=NaN:(o=(t-r+(t-n))/2,o/=tf((t-r)*(t-r)+(t-n)*(r-n)),o=rf(o),n>r&&(o=Ce-o),o/=Ce),[o*360,c,s]};k.prototype.hsi=function(){return nf(this._rgb)};const of=(...e)=>new k(...e,"hsi");G.hsi=of,E.format.hsi=Qu,E.autodetect.push({p:2,test:(...e)=>{if(e=T(e,"hsi"),B(e)==="array"&&e.length===3)return"hsi"}});const sf=(e,t,r)=>et(e,t,r,"hsi");ne.hsi=sf;const Kn=(...e)=>{e=T(e,"hsl");const[t,r,n]=e;let o,a,s;if(r===0)o=a=s=n*255;else{const c=[0,0,0],i=[0,0,0],u=n<.5?n*(1+r):n+r-n*r,l=2*n-u,f=t/360;c[0]=f+1/3,c[1]=f,c[2]=f-1/3;for(let h=0;h<3;h++)c[h]<0&&(c[h]+=1),c[h]>1&&(c[h]-=1),6*c[h]<1?i[h]=l+(u-l)*6*c[h]:2*c[h]<1?i[h]=u:3*c[h]<2?i[h]=l+(u-l)*(2/3-c[h])*6:i[h]=l;[o,a,s]=[i[0]*255,i[1]*255,i[2]*255]}return e.length>3?[o,a,s,e[3]]:[o,a,s,1]},Ga=(...e)=>{e=T(e,"rgba");let[t,r,n]=e;t/=255,r/=255,n/=255;const o=Oa(t,r,n),a=Na(t,r,n),s=(a+o)/2;let c,i;return a===o?(c=0,i=Number.NaN):c=s<.5?(a-o)/(a+o):(a-o)/(2-a-o),t==a?i=(r-n)/(a-o):r==a?i=2+(n-t)/(a-o):n==a&&(i=4+(t-r)/(a-o)),i*=60,i<0&&(i+=360),e.length>3&&e[3]!==void 0?[i,c,s,e[3]]:[i,c,s]};k.prototype.hsl=function(){return Ga(this._rgb)};const af=(...e)=>new k(...e,"hsl");G.hsl=af,E.format.hsl=Kn,E.autodetect.push({p:2,test:(...e)=>{if(e=T(e,"hsl"),B(e)==="array"&&e.length===3)return"hsl"}});const cf=(e,t,r)=>et(e,t,r,"hsl");ne.hsl=cf;const{floor:uf}=Math,ff=(...e)=>{e=T(e,"hsv");let[t,r,n]=e,o,a,s;if(n*=255,r===0)o=a=s=n;else{t===360&&(t=0),t>360&&(t-=360),t<0&&(t+=360),t/=60;const c=uf(t),i=t-c,u=n*(1-r),l=n*(1-r*i),f=n*(1-r*(1-i));switch(c){case 0:[o,a,s]=[n,f,u];break;case 1:[o,a,s]=[l,n,u];break;case 2:[o,a,s]=[u,n,f];break;case 3:[o,a,s]=[u,l,n];break;case 4:[o,a,s]=[f,u,n];break;case 5:[o,a,s]=[n,u,l];break}}return[o,a,s,e.length>3?e[3]:1]},{min:lf,max:hf}=Math,df=(...e)=>{e=T(e,"rgb");let[t,r,n]=e;const o=lf(t,r,n),a=hf(t,r,n),s=a-o;let c,i,u;return u=a/255,a===0?(c=Number.NaN,i=0):(i=s/a,t===a&&(c=(r-n)/s),r===a&&(c=2+(n-t)/s),n===a&&(c=4+(t-r)/s),c*=60,c<0&&(c+=360)),[c,i,u]};k.prototype.hsv=function(){return df(this._rgb)};const bf=(...e)=>new k(...e,"hsv");G.hsv=bf,E.format.hsv=ff,E.autodetect.push({p:2,test:(...e)=>{if(e=T(e,"hsv"),B(e)==="array"&&e.length===3)return"hsv"}});const pf=(e,t,r)=>et(e,t,r,"hsv");ne.hsv=pf;function Pt(e,t){let r=e.length;Array.isArray(e[0])||(e=[e]),Array.isArray(t[0])||(t=t.map(s=>[s]));let n=t[0].length,o=t[0].map((s,c)=>t.map(i=>i[c])),a=e.map(s=>o.map(c=>Array.isArray(s)?s.reduce((i,u,l)=>i+u*(c[l]||0),0):c.reduce((i,u)=>i+u*s,0)));return r===1&&(a=a[0]),n===1?a.map(s=>s[0]):a}const Dn=(...e)=>{e=T(e,"lab");const[t,r,n,...o]=e,[a,s,c]=mf([t,r,n]),[i,u,l]=Ta(a,s,c);return[i,u,l,...o.length>0&&o[0]<1?[o[0]]:[]]};function mf(e){var t=[[1.2268798758459243,-.5578149944602171,.2813910456659647],[-.0405757452148008,1.112286803280317,-.0717110580655164],[-.0763729366746601,-.4214933324022432,1.5869240198367816]],r=[[1,.3963377773761749,.2158037573099136],[1,-.1055613458156586,-.0638541728258133],[1,-.0894841775298119,-1.2914855480194092]],n=Pt(r,e);return Pt(t,n.map(o=>o**3))}const Vn=(...e)=>{const[t,r,n,...o]=T(e,"rgb"),a=Sa(t,r,n);return[...gf(a),...o.length>0&&o[0]<1?[o[0]]:[]]};function gf(e){const t=[[.819022437996703,.3619062600528904,-.1288737815209879],[.0329836539323885,.9292868615863434,.0361446663506424],[.0481771893596242,.2642395317527308,.6335478284694309]],r=[[.210454268309314,.7936177747023054,-.0040720430116193],[1.9779985324311684,-2.42859224204858,.450593709617411],[.0259040424655478,.7827717124575296,-.8086757549230774]],n=Pt(t,e);return Pt(r,n.map(o=>Math.cbrt(o)))}k.prototype.oklab=function(){return Vn(this._rgb)},Object.assign(G,{oklab:(...e)=>new k(...e,"oklab")}),E.format.oklab=Dn,E.autodetect.push({p:2,test:(...e)=>{if(e=T(e,"oklab"),B(e)==="array"&&e.length===3)return"oklab"}});const vf=(e,t,r)=>{const n=e.oklab(),o=t.oklab();return new k(n[0]+r*(o[0]-n[0]),n[1]+r*(o[1]-n[1]),n[2]+r*(o[2]-n[2]),"oklab")};ne.oklab=vf;const _f=(e,t,r)=>et(e,t,r,"oklch");ne.oklch=_f;const{pow:Yn,sqrt:Zn,PI:Jn,cos:Ia,sin:za,atan2:yf}=Math,wf=(e,t="lrgb",r=null)=>{const n=e.length;r||(r=Array.from(new Array(n)).map(()=>1));const o=n/r.reduce(function(f,h){return f+h});if(r.forEach((f,h)=>{r[h]*=o}),e=e.map(f=>new k(f)),t==="lrgb")return kf(e,r);const a=e.shift(),s=a.get(t),c=[];let i=0,u=0;for(let f=0;f{const d=f.get(t);l+=f.alpha()*r[h+1];for(let b=0;b=360;)h-=360;s[f]=h}else s[f]=s[f]/c[f];return l/=n,new k(s,t).alpha(l>.99999?1:l,!0)},kf=(e,t)=>{const r=e.length,n=[0,0,0,0];for(let o=0;o.9999999&&(n[3]=1),new k(En(n))},{pow:$f}=Math;function jt(e){let t="rgb",r=G("#ccc"),n=0,o=[0,1],a=[0,1],s=[],c=[0,0],i=!1,u=[],l=!1,f=0,h=1,d=!1,b={},_=!0,g=1;const v=function(p){if(p=p||["#fff","#000"],p&&B(p)==="string"&&G.brewer&&G.brewer[p.toLowerCase()]&&(p=G.brewer[p.toLowerCase()]),B(p)==="array"){p.length===1&&(p=[p[0],p[0]]),p=p.slice(0);for(let y=0;y=i[w];)w++;return w-1}return 0};let x=p=>p,N=p=>p;const A=function(p,y){let w,C;if(y==null&&(y=!1),isNaN(p)||p===null)return r;y?C=p:i&&i.length>2?C=H(p)/(i.length-2):h!==f?C=(p-f)/(h-f):C=1,C=N(C),y||(C=x(C)),g!==1&&(C=$f(C,g)),C=c[0]+C*(1-c[0]-c[1]),C=Be(C,0,1);const q=Math.floor(C*1e4);if(_&&b[q])w=b[q];else{if(B(u)==="array")for(let M=0;M=S&&M===s.length-1){w=u[M];break}if(C>S&&Cb={};v(e);const m=function(p){const y=G(A(p));return l&&y[l]?y[l]():y};return m.classes=function(p){if(p!=null){if(B(p)==="array")i=p,o=[p[0],p[p.length-1]];else{const y=G.analyze(o);p===0?i=[y.min,y.max]:i=G.limits(y,"e",p)}return m}return i},m.domain=function(p){if(!arguments.length)return a;a=p.slice(0),f=p[0],h=p[p.length-1],s=[];const y=u.length;if(p.length===y&&f!==h)for(let w of Array.from(p))s.push((w-f)/(h-f));else{for(let w=0;w2){const w=p.map((q,M)=>M/(p.length-1)),C=p.map(q=>(q-f)/(h-f));C.every((q,M)=>w[M]===q)||(N=q=>{if(q<=0||q>=1)return q;let M=0;for(;q>=C[M+1];)M++;const S=(q-C[M])/(C[M+1]-C[M]);return w[M]+S*(w[M+1]-w[M])})}}return o=[f,h],m},m.mode=function(p){return arguments.length?(t=p,R(),m):t},m.range=function(p,y){return v(p),m},m.out=function(p){return l=p,m},m.spread=function(p){return arguments.length?(n=p,m):n},m.correctLightness=function(p){return p==null&&(p=!0),d=p,R(),d?x=function(y){const w=A(0,!0).lab()[0],C=A(1,!0).lab()[0],q=w>C;let M=A(y,!0).lab()[0];const S=w+(C-w)*y;let X=M-S,D=0,U=1,ce=20;for(;Math.abs(X)>.01&&ce-- >0;)(function(){return q&&(X*=-1),X<0?(D=y,y+=(U-y)*.5):(U=y,y+=(D-y)*.5),M=A(y,!0).lab()[0],X=M-S})();return y}:x=y=>y,m},m.padding=function(p){return p!=null?(B(p)==="number"&&(p=[p,p]),c=p,m):c},m.colors=function(p,y){arguments.length<2&&(y="hex");let w=[];if(arguments.length===0)w=u.slice(0);else if(p===1)w=[m(.5)];else if(p>1){const C=o[0],q=o[1]-C;w=Cf(0,p).map(M=>m(C+M/(p-1)*q))}else{e=[];let C=[];if(i&&i.length>2)for(let q=1,M=i.length,S=1<=M;S?qM;S?q++:q--)C.push((i[q-1]+i[q])*.5);else C=o;w=C.map(q=>m(q))}return G[y]&&(w=w.map(C=>C[y]())),w},m.cache=function(p){return p!=null?(_=p,m):_},m.gamma=function(p){return p!=null?(g=p,m):g},m.nodata=function(p){return p!=null?(r=G(p),m):r},m}function Cf(e,t,r){let n=[],o=ea;o?s++:s--)n.push(s);return n}const xf=function(e){let t=[1,1];for(let r=1;rnew k(a)),e.length===2)[r,n]=e.map(a=>a.lab()),t=function(a){const s=[0,1,2].map(c=>r[c]+a*(n[c]-r[c]));return new k(s,"lab")};else if(e.length===3)[r,n,o]=e.map(a=>a.lab()),t=function(a){const s=[0,1,2].map(c=>(1-a)*(1-a)*r[c]+2*(1-a)*a*n[c]+a*a*o[c]);return new k(s,"lab")};else if(e.length===4){let a;[r,n,o,a]=e.map(s=>s.lab()),t=function(s){const c=[0,1,2].map(i=>(1-s)*(1-s)*(1-s)*r[i]+3*(1-s)*(1-s)*s*n[i]+3*(1-s)*s*s*o[i]+s*s*s*a[i]);return new k(c,"lab")}}else if(e.length>=5){let a,s,c;a=e.map(i=>i.lab()),c=e.length-1,s=xf(c),t=function(i){const u=1-i,l=[0,1,2].map(f=>a.reduce((h,d,b)=>h+s[b]*u**(c-b)*i**b*d[f],0));return new k(l,"lab")}}else throw new RangeError("No point in running bezier with only one color.");return t},Hf=e=>{const t=Rf(e);return t.scale=()=>jt(t),t},{round:Fa}=Math;k.prototype.rgb=function(e=!0){return e===!1?this._rgb.slice(0,3):this._rgb.slice(0,3).map(Fa)},k.prototype.rgba=function(e=!0){return this._rgb.slice(0,4).map((t,r)=>r<3?e===!1?t:Fa(t):t)},Object.assign(G,{rgb:(...e)=>new k(...e,"rgb")}),E.format.rgb=(...e)=>{const t=T(e,"rgba");return t[3]===void 0&&(t[3]=1),t},E.autodetect.push({p:3,test:(...e)=>{if(e=T(e,"rgba"),B(e)==="array"&&(e.length===3||e.length===4&&B(e[3])=="number"&&e[3]>=0&&e[3]<=1))return"rgb"}});const me=(e,t,r)=>{if(!me[r])throw new Error("unknown blend mode "+r);return me[r](e,t)},Te=e=>(t,r)=>{const n=G(r).rgb(),o=G(t).rgb();return G.rgb(e(n,o))},Se=e=>(t,r)=>{const n=[];return n[0]=e(t[0],r[0]),n[1]=e(t[1],r[1]),n[2]=e(t[2],r[2]),n},qf=e=>e,Mf=(e,t)=>e*t/255,Of=(e,t)=>e>t?t:e,Nf=(e,t)=>e>t?e:t,Af=(e,t)=>255*(1-(1-e/255)*(1-t/255)),Lf=(e,t)=>t<128?2*e*t/255:255*(1-2*(1-e/255)*(1-t/255)),Ef=(e,t)=>255*(1-(1-t/255)/(e/255)),Tf=(e,t)=>e===255?255:(e=255*(t/255)/(1-e/255),e>255?255:e);me.normal=Te(Se(qf)),me.multiply=Te(Se(Mf)),me.screen=Te(Se(Af)),me.overlay=Te(Se(Lf)),me.darken=Te(Se(Of)),me.lighten=Te(Se(Nf)),me.dodge=Te(Se(Tf)),me.burn=Te(Se(Ef));const{pow:Sf,sin:Pf,cos:jf}=Math;function Bf(e=300,t=-1.5,r=1,n=1,o=[0,1]){let a=0,s;B(o)==="array"?s=o[1]-o[0]:(s=0,o=[o,o]);const c=function(i){const u=Ce*((e+120)/360+t*i),l=Sf(o[0]+s*i,n),h=(a!==0?r[0]+i*a:r)*l*(1-l)/2,d=jf(u),b=Pf(u),_=l+h*(-.14861*d+1.78277*b),g=l+h*(-.29227*d-.90649*b),v=l+h*(1.97294*d);return G(En([_*255,g*255,v*255,1]))};return c.start=function(i){return i==null?e:(e=i,c)},c.rotations=function(i){return i==null?t:(t=i,c)},c.gamma=function(i){return i==null?n:(n=i,c)},c.hue=function(i){return i==null?r:(r=i,B(r)==="array"?(a=r[1]-r[0],a===0&&(r=r[1])):a=0,c)},c.lightness=function(i){return i==null?o:(B(i)==="array"?(o=i,s=i[1]-i[0]):(o=[i,i],s=0),c)},c.scale=()=>G.scale(c),c.hue(r),c}const Gf="0123456789abcdef",{floor:If,random:zf}=Math,Ff=(e=zf)=>{let t="#";for(let r=0;r<6;r++)t+=Gf.charAt(If(e()*16));return new k(t,"hex")},{log:Xa,pow:Xf,floor:Kf,abs:Df}=Math;function Ka(e,t=null){const r={min:Number.MAX_VALUE,max:Number.MAX_VALUE*-1,sum:0,values:[],count:0};return B(e)==="object"&&(e=Object.values(e)),e.forEach(n=>{t&&B(n)==="object"&&(n=n[t]),n!=null&&!isNaN(n)&&(r.values.push(n),r.sum+=n,nr.max&&(r.max=n),r.count+=1)}),r.domain=[r.min,r.max],r.limits=(n,o)=>Da(r,n,o),r}function Da(e,t="equal",r=7){B(e)=="array"&&(e=Ka(e));const{min:n,max:o}=e,a=e.values.sort((c,i)=>c-i);if(r===1)return[n,o];const s=[];if(t.substr(0,1)==="c"&&(s.push(n),s.push(o)),t.substr(0,1)==="e"){s.push(n);for(let c=1;c 0");const c=Math.LOG10E*Xa(n),i=Math.LOG10E*Xa(o);s.push(n);for(let u=1;u200&&(f=!1)}const b={};for(let g=0;gg-v),s.push(_[0]);for(let g=1;g<_.length;g+=2){const v=_[g];!isNaN(v)&&s.indexOf(v)===-1&&s.push(v)}}return s}const Vf=(e,t)=>{e=new k(e),t=new k(t);const r=e.luminance(),n=t.luminance();return r>n?(r+.05)/(n+.05):(n+.05)/(r+.05)};const Va=.027,Yf=5e-4,Zf=.1,Ya=1.14,Bt=.022,Za=1.414,Jf=(e,t)=>{e=new k(e),t=new k(t),e.alpha()<1&&(e=Ue(t,e,e.alpha(),"rgb"));const r=Ja(...e.rgb()),n=Ja(...t.rgb()),o=r>=Bt?r:r+Math.pow(Bt-r,Za),a=n>=Bt?n:n+Math.pow(Bt-n,Za),s=Math.pow(a,.56)-Math.pow(o,.57),c=Math.pow(a,.65)-Math.pow(o,.62),i=Math.abs(a-o)0?i-Va:i+Va)*100};function Ja(e,t,r){return .2126729*Math.pow(e/255,2.4)+.7151522*Math.pow(t/255,2.4)+.072175*Math.pow(r/255,2.4)}const{sqrt:Re,pow:Z,min:Wf,max:Uf,atan2:Wa,abs:Ua,cos:Gt,sin:Qa,exp:Qf,PI:ec}=Math;function el(e,t,r=1,n=1,o=1){var a=function(he){return 360*he/(2*ec)},s=function(he){return 2*ec*he/360};e=new k(e),t=new k(t);const[c,i,u]=Array.from(e.lab()),[l,f,h]=Array.from(t.lab()),d=(c+l)/2,b=Re(Z(i,2)+Z(u,2)),_=Re(Z(f,2)+Z(h,2)),g=(b+_)/2,v=.5*(1-Re(Z(g,7)/(Z(g,7)+Z(25,7)))),H=i*(1+v),x=f*(1+v),N=Re(Z(H,2)+Z(u,2)),A=Re(Z(x,2)+Z(h,2)),R=(N+A)/2,m=a(Wa(u,H)),p=a(Wa(h,x)),y=m>=0?m:m+360,w=p>=0?p:p+360,C=Ua(y-w)>180?(y+w+360)/2:(y+w)/2,q=1-.17*Gt(s(C-30))+.24*Gt(s(2*C))+.32*Gt(s(3*C+6))-.2*Gt(s(4*C-63));let M=w-y;M=Ua(M)<=180?M:w<=y?M+360:M-360,M=2*Re(N*A)*Qa(s(M)/2);const S=l-c,X=A-N,D=1+.015*Z(d-50,2)/Re(20+Z(d-50,2)),U=1+.045*R,ce=1+.015*R*q,ye=30*Qf(-Z((C-275)/25,2)),le=-(2*Re(Z(R,7)/(Z(R,7)+Z(25,7))))*Qa(2*s(ye)),qe=Re(Z(S/(r*D),2)+Z(X/(n*U),2)+Z(M/(o*ce),2)+le*(X/(n*U))*(M/(o*ce)));return Uf(0,Wf(100,qe))}function tl(e,t,r="lab"){e=new k(e),t=new k(t);const n=e.get(r),o=t.get(r);let a=0;for(let s in n){const c=(n[s]||0)-(o[s]||0);a+=c*c}return Math.sqrt(a)}const rl=(...e)=>{try{return new k(...e),!0}catch{return!1}},nl={cool(){return jt([G.hsl(180,1,.9),G.hsl(250,.7,.4)])},hot(){return jt(["#000","#f00","#ff0","#fff"]).mode("rgb")}},Wn={OrRd:["#fff7ec","#fee8c8","#fdd49e","#fdbb84","#fc8d59","#ef6548","#d7301f","#b30000","#7f0000"],PuBu:["#fff7fb","#ece7f2","#d0d1e6","#a6bddb","#74a9cf","#3690c0","#0570b0","#045a8d","#023858"],BuPu:["#f7fcfd","#e0ecf4","#bfd3e6","#9ebcda","#8c96c6","#8c6bb1","#88419d","#810f7c","#4d004b"],Oranges:["#fff5eb","#fee6ce","#fdd0a2","#fdae6b","#fd8d3c","#f16913","#d94801","#a63603","#7f2704"],BuGn:["#f7fcfd","#e5f5f9","#ccece6","#99d8c9","#66c2a4","#41ae76","#238b45","#006d2c","#00441b"],YlOrBr:["#ffffe5","#fff7bc","#fee391","#fec44f","#fe9929","#ec7014","#cc4c02","#993404","#662506"],YlGn:["#ffffe5","#f7fcb9","#d9f0a3","#addd8e","#78c679","#41ab5d","#238443","#006837","#004529"],Reds:["#fff5f0","#fee0d2","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#a50f15","#67000d"],RdPu:["#fff7f3","#fde0dd","#fcc5c0","#fa9fb5","#f768a1","#dd3497","#ae017e","#7a0177","#49006a"],Greens:["#f7fcf5","#e5f5e0","#c7e9c0","#a1d99b","#74c476","#41ab5d","#238b45","#006d2c","#00441b"],YlGnBu:["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"],Purples:["#fcfbfd","#efedf5","#dadaeb","#bcbddc","#9e9ac8","#807dba","#6a51a3","#54278f","#3f007d"],GnBu:["#f7fcf0","#e0f3db","#ccebc5","#a8ddb5","#7bccc4","#4eb3d3","#2b8cbe","#0868ac","#084081"],Greys:["#ffffff","#f0f0f0","#d9d9d9","#bdbdbd","#969696","#737373","#525252","#252525","#000000"],YlOrRd:["#ffffcc","#ffeda0","#fed976","#feb24c","#fd8d3c","#fc4e2a","#e31a1c","#bd0026","#800026"],PuRd:["#f7f4f9","#e7e1ef","#d4b9da","#c994c7","#df65b0","#e7298a","#ce1256","#980043","#67001f"],Blues:["#f7fbff","#deebf7","#c6dbef","#9ecae1","#6baed6","#4292c6","#2171b5","#08519c","#08306b"],PuBuGn:["#fff7fb","#ece2f0","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016c59","#014636"],Viridis:["#440154","#482777","#3f4a8a","#31678e","#26838f","#1f9d8a","#6cce5a","#b6de2b","#fee825"],Spectral:["#9e0142","#d53e4f","#f46d43","#fdae61","#fee08b","#ffffbf","#e6f598","#abdda4","#66c2a5","#3288bd","#5e4fa2"],RdYlGn:["#a50026","#d73027","#f46d43","#fdae61","#fee08b","#ffffbf","#d9ef8b","#a6d96a","#66bd63","#1a9850","#006837"],RdBu:["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#f7f7f7","#d1e5f0","#92c5de","#4393c3","#2166ac","#053061"],PiYG:["#8e0152","#c51b7d","#de77ae","#f1b6da","#fde0ef","#f7f7f7","#e6f5d0","#b8e186","#7fbc41","#4d9221","#276419"],PRGn:["#40004b","#762a83","#9970ab","#c2a5cf","#e7d4e8","#f7f7f7","#d9f0d3","#a6dba0","#5aae61","#1b7837","#00441b"],RdYlBu:["#a50026","#d73027","#f46d43","#fdae61","#fee090","#ffffbf","#e0f3f8","#abd9e9","#74add1","#4575b4","#313695"],BrBG:["#543005","#8c510a","#bf812d","#dfc27d","#f6e8c3","#f5f5f5","#c7eae5","#80cdc1","#35978f","#01665e","#003c30"],RdGy:["#67001f","#b2182b","#d6604d","#f4a582","#fddbc7","#ffffff","#e0e0e0","#bababa","#878787","#4d4d4d","#1a1a1a"],PuOr:["#7f3b08","#b35806","#e08214","#fdb863","#fee0b6","#f7f7f7","#d8daeb","#b2abd2","#8073ac","#542788","#2d004b"],Set2:["#66c2a5","#fc8d62","#8da0cb","#e78ac3","#a6d854","#ffd92f","#e5c494","#b3b3b3"],Accent:["#7fc97f","#beaed4","#fdc086","#ffff99","#386cb0","#f0027f","#bf5b17","#666666"],Set1:["#e41a1c","#377eb8","#4daf4a","#984ea3","#ff7f00","#ffff33","#a65628","#f781bf","#999999"],Set3:["#8dd3c7","#ffffb3","#bebada","#fb8072","#80b1d3","#fdb462","#b3de69","#fccde5","#d9d9d9","#bc80bd","#ccebc5","#ffed6f"],Dark2:["#1b9e77","#d95f02","#7570b3","#e7298a","#66a61e","#e6ab02","#a6761d","#666666"],Paired:["#a6cee3","#1f78b4","#b2df8a","#33a02c","#fb9a99","#e31a1c","#fdbf6f","#ff7f00","#cab2d6","#6a3d9a","#ffff99","#b15928"],Pastel2:["#b3e2cd","#fdcdac","#cbd5e8","#f4cae4","#e6f5c9","#fff2ae","#f1e2cc","#cccccc"],Pastel1:["#fbb4ae","#b3cde3","#ccebc5","#decbe4","#fed9a6","#ffffcc","#e5d8bd","#fddaec","#f2f2f2"]},tc=Object.keys(Wn),rc=new Map(tc.map(e=>[e.toLowerCase(),e])),ol=typeof Proxy=="function"?new Proxy(Wn,{get(e,t){const r=t.toLowerCase();if(rc.has(r))return e[rc.get(r)]},getOwnPropertyNames(){return Object.getOwnPropertyNames(tc)}}):Wn,sl=(...e)=>{e=T(e,"cmyk");const[t,r,n,o]=e,a=e.length>4?e[4]:1;return o===1?[0,0,0,a]:[t>=1?0:255*(1-t)*(1-o),r>=1?0:255*(1-r)*(1-o),n>=1?0:255*(1-n)*(1-o),a]},{max:nc}=Math,al=(...e)=>{let[t,r,n]=T(e,"rgb");t=t/255,r=r/255,n=n/255;const o=1-nc(t,nc(r,n)),a=o<1?1/(1-o):0,s=(1-t-o)*a,c=(1-r-o)*a,i=(1-n-o)*a;return[s,c,i,o]};k.prototype.cmyk=function(){return al(this._rgb)},Object.assign(G,{cmyk:(...e)=>new k(...e,"cmyk")}),E.format.cmyk=sl,E.autodetect.push({p:2,test:(...e)=>{if(e=T(e,"cmyk"),B(e)==="array"&&e.length===4)return"cmyk"}});const cl=(...e)=>{const t=T(e,"hsla");let r=Je(e)||"lsa";return t[0]=ue(t[0]||0)+"deg",t[1]=ue(t[1]*100)+"%",t[2]=ue(t[2]*100)+"%",r==="hsla"||t.length>3&&t[3]<1?(t[3]="/ "+(t.length>3?t[3]:1),r="hsla"):t.length=3,`${r.substr(0,3)}(${t.join(" ")})`},il=(...e)=>{const t=T(e,"lab");let r=Je(e)||"lab";return t[0]=ue(t[0])+"%",t[1]=ue(t[1]),t[2]=ue(t[2]),r==="laba"||t.length>3&&t[3]<1?t[3]="/ "+(t.length>3?t[3]:1):t.length=3,`lab(${t.join(" ")})`},ul=(...e)=>{const t=T(e,"lch");let r=Je(e)||"lab";return t[0]=ue(t[0])+"%",t[1]=ue(t[1]),t[2]=isNaN(t[2])?"none":ue(t[2])+"deg",r==="lcha"||t.length>3&&t[3]<1?t[3]="/ "+(t.length>3?t[3]:1):t.length=3,`lch(${t.join(" ")})`},fl=(...e)=>{const t=T(e,"lab");return t[0]=ue(t[0]*100)+"%",t[1]=Tn(t[1]),t[2]=Tn(t[2]),t.length>3&&t[3]<1?t[3]="/ "+(t.length>3?t[3]:1):t.length=3,`oklab(${t.join(" ")})`},oc=(...e)=>{const[t,r,n,...o]=T(e,"rgb"),[a,s,c]=Vn(t,r,n),[i,u,l]=ja(a,s,c);return[i,u,l,...o.length>0&&o[0]<1?[o[0]]:[]]},ll=(...e)=>{const t=T(e,"lch");return t[0]=ue(t[0]*100)+"%",t[1]=Tn(t[1]),t[2]=isNaN(t[2])?"none":ue(t[2])+"deg",t.length>3&&t[3]<1?t[3]="/ "+(t.length>3?t[3]:1):t.length=3,`oklch(${t.join(" ")})`},{round:Un}=Math,hl=(...e)=>{const t=T(e,"rgba");let r=Je(e)||"rgb";if(r.substr(0,3)==="hsl")return cl(Ga(t),r);if(r.substr(0,3)==="lab"){const n=dt();xe("d50");const o=il(Bn(t),r);return xe(n),o}if(r.substr(0,3)==="lch"){const n=dt();xe("d50");const o=ul(Fn(t),r);return xe(n),o}return r.substr(0,5)==="oklab"?fl(Vn(t)):r.substr(0,5)==="oklch"?ll(oc(t)):(t[0]=Un(t[0]),t[1]=Un(t[1]),t[2]=Un(t[2]),(r==="rgba"||t.length>3&&t[3]<1)&&(t[3]="/ "+(t.length>3?t[3]:1),r="rgba"),`${r.substr(0,3)}(${t.slice(0,r==="rgb"?3:4).join(" ")})`)},sc=(...e)=>{e=T(e,"lch");const[t,r,n,...o]=e,[a,s,c]=Pa(t,r,n),[i,u,l]=Dn(a,s,c);return[i,u,l,...o.length>0&&o[0]<1?[o[0]]:[]]},He=/((?:-?\d+)|(?:-?\d+(?:\.\d+)?)%|none)/.source,ge=/((?:-?(?:\d+(?:\.\d*)?|\.\d+)%?)|none)/.source,It=/((?:-?(?:\d+(?:\.\d*)?|\.\d+)%)|none)/.source,fe=/\s*/.source,rt=/\s+/.source,Qn=/\s*,\s*/.source,zt=/((?:-?(?:\d+(?:\.\d*)?|\.\d+)(?:deg)?)|none)/.source,nt=/\s*(?:\/\s*((?:[01]|[01]?\.\d+)|\d+(?:\.\d+)?%))?/.source,ac=new RegExp("^rgba?\\("+fe+[He,He,He].join(rt)+nt+"\\)$"),cc=new RegExp("^rgb\\("+fe+[He,He,He].join(Qn)+fe+"\\)$"),ic=new RegExp("^rgba\\("+fe+[He,He,He,ge].join(Qn)+fe+"\\)$"),uc=new RegExp("^hsla?\\("+fe+[zt,It,It].join(rt)+nt+"\\)$"),fc=new RegExp("^hsl?\\("+fe+[zt,It,It].join(Qn)+fe+"\\)$"),lc=/^hsla\(\s*(-?\d+(?:\.\d+)?),\s*(-?\d+(?:\.\d+)?)%\s*,\s*(-?\d+(?:\.\d+)?)%\s*,\s*([01]|[01]?\.\d+)\)$/,hc=new RegExp("^lab\\("+fe+[ge,ge,ge].join(rt)+nt+"\\)$"),dc=new RegExp("^lch\\("+fe+[ge,ge,zt].join(rt)+nt+"\\)$"),bc=new RegExp("^oklab\\("+fe+[ge,ge,ge].join(rt)+nt+"\\)$"),pc=new RegExp("^oklch\\("+fe+[ge,ge,zt].join(rt)+nt+"\\)$"),{round:mc}=Math,ot=e=>e.map((t,r)=>r<=2?Be(mc(t),0,255):t),J=(e,t=0,r=100,n=!1)=>(typeof e=="string"&&e.endsWith("%")&&(e=parseFloat(e.substring(0,e.length-1))/100,n?e=t+(e+1)*.5*(r-t):e=t+e*(r-t)),+e),oe=(e,t)=>e==="none"?t:e,eo=e=>{if(e=e.toLowerCase().trim(),e==="transparent")return[0,0,0,0];let t;if(E.format.named)try{return E.format.named(e)}catch{}if((t=e.match(ac))||(t=e.match(cc))){let r=t.slice(1,4);for(let o=0;o<3;o++)r[o]=+J(oe(r[o],0),0,255);r=ot(r);const n=t[4]!==void 0?+J(t[4],0,1):1;return r[3]=n,r}if(t=e.match(ic)){const r=t.slice(1,5);for(let n=0;n<4;n++)r[n]=+J(r[n],0,255);return r}if((t=e.match(uc))||(t=e.match(fc))){const r=t.slice(1,4);r[0]=+oe(r[0].replace("deg",""),0),r[1]=+J(oe(r[1],0),0,100)*.01,r[2]=+J(oe(r[2],0),0,100)*.01;const n=ot(Kn(r)),o=t[4]!==void 0?+J(t[4],0,1):1;return n[3]=o,n}if(t=e.match(lc)){const r=t.slice(1,4);r[1]*=.01,r[2]*=.01;const n=Kn(r);for(let o=0;o<3;o++)n[o]=mc(n[o]);return n[3]=+t[4],n}if(t=e.match(hc)){const r=t.slice(1,4);r[0]=J(oe(r[0],0),0,100),r[1]=J(oe(r[1],0),-125,125,!0),r[2]=J(oe(r[2],0),-125,125,!0);const n=dt();xe("d50");const o=ot(Pn(r));xe(n);const a=t[4]!==void 0?+J(t[4],0,1):1;return o[3]=a,o}if(t=e.match(dc)){const r=t.slice(1,4);r[0]=J(r[0],0,100),r[1]=J(oe(r[1],0),0,150,!1),r[2]=+oe(r[2].replace("deg",""),0);const n=dt();xe("d50");const o=ot(zn(r));xe(n);const a=t[4]!==void 0?+J(t[4],0,1):1;return o[3]=a,o}if(t=e.match(bc)){const r=t.slice(1,4);r[0]=J(oe(r[0],0),0,1),r[1]=J(oe(r[1],0),-.4,.4,!0),r[2]=J(oe(r[2],0),-.4,.4,!0);const n=ot(Dn(r)),o=t[4]!==void 0?+J(t[4],0,1):1;return n[3]=o,n}if(t=e.match(pc)){const r=t.slice(1,4);r[0]=J(oe(r[0],0),0,1),r[1]=J(oe(r[1],0),0,.4,!1),r[2]=+oe(r[2].replace("deg",""),0);const n=ot(sc(r)),o=t[4]!==void 0?+J(t[4],0,1):1;return n[3]=o,n}};eo.test=e=>ac.test(e)||uc.test(e)||hc.test(e)||dc.test(e)||bc.test(e)||pc.test(e)||cc.test(e)||ic.test(e)||fc.test(e)||lc.test(e)||e==="transparent",k.prototype.css=function(e){return hl(this._rgb,e)};const dl=(...e)=>new k(...e,"css");G.css=dl,E.format.css=eo,E.autodetect.push({p:5,test:(e,...t)=>{if(!t.length&&B(e)==="string"&&eo.test(e))return"css"}}),E.format.gl=(...e)=>{const t=T(e,"rgba");return t[0]*=255,t[1]*=255,t[2]*=255,t};const bl=(...e)=>new k(...e,"gl");G.gl=bl,k.prototype.gl=function(){const e=this._rgb;return[e[0]/255,e[1]/255,e[2]/255,e[3]]},k.prototype.hex=function(e){return Ea(this._rgb,e)};const pl=(...e)=>new k(...e,"hex");G.hex=pl,E.format.hex=La,E.autodetect.push({p:4,test:(e,...t)=>{if(!t.length&&B(e)==="string"&&[3,4,5,6,7,8,9].indexOf(e.length)>=0)return"hex"}});const{log:Ft}=Math,gc=e=>{const t=e/100;let r,n,o;return t<66?(r=255,n=t<6?0:-155.25485562709179-.44596950469579133*(n=t-2)+104.49216199393888*Ft(n),o=t<20?0:-254.76935184120902+.8274096064007395*(o=t-10)+115.67994401066147*Ft(o)):(r=351.97690566805693+.114206453784165*(r=t-55)-40.25366309332127*Ft(r),n=325.4494125711974+.07943456536662342*(n=t-50)-28.0852963507957*Ft(n),o=255),[r,n,o,1]},{round:ml}=Math,gl=(...e)=>{const t=T(e,"rgb"),r=t[0],n=t[2];let o=1e3,a=4e4;const s=.4;let c;for(;a-o>s;){c=(a+o)*.5;const i=gc(c);i[2]/i[0]>=n/r?a=c:o=c}return ml(c)};k.prototype.temp=k.prototype.kelvin=k.prototype.temperature=function(){return gl(this._rgb)};const to=(...e)=>new k(...e,"temp");Object.assign(G,{temp:to,kelvin:to,temperature:to}),E.format.temp=E.format.kelvin=E.format.temperature=gc,k.prototype.oklch=function(){return oc(this._rgb)},Object.assign(G,{oklch:(...e)=>new k(...e,"oklch")}),E.format.oklch=sc,E.autodetect.push({p:2,test:(...e)=>{if(e=T(e,"oklch"),B(e)==="array"&&e.length===3)return"oklch"}}),Object.assign(G,{analyze:Ka,average:wf,bezier:Hf,blend:me,brewer:ol,Color:k,colors:We,contrast:Vf,contrastAPCA:Jf,cubehelix:Bf,deltaE:el,distance:tl,input:E,interpolate:Ue,limits:Da,mix:Ue,random:Ff,scale:jt,scales:nl,valid:rl});function vl(e){return+`${Math.ceil(`${e}e+2`)}e-2`}const vc=e=>{const t=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return[Number.parseInt(t[1],16),Number.parseInt(t[2],16),Number.parseInt(t[3],16)]},_c=(e,t,r)=>{const n=e/255,o=t/255,a=r/255,s=Math.min(n,o,a),c=Math.max(n,o,a),i=c-s;let u=0,l=0,f=0;return i===0?u=0:c===n?u=(o-a)/i%6:c===o?u=(a-n)/i+2:u=(n-o)/i+4,u=Math.round(u*60),u<0&&(u+=360),f=(c+s)/2,l=i===0?0:i/(1-Math.abs(2*f-1)),l=+(l*100).toFixed(1),f=+(f*100).toFixed(1),[u,l,Math.round(f)]},_l=(e,t,r,n)=>{const o=r/100,a=t*Math.min(o,1-o)/100,s=d=>{const b=(d+e/30)%12,_=o-a*Math.max(Math.min(b-3,9-b,1),-1);return Math.round(255*_).toString(16).padStart(2,"0").toUpperCase()},c=s(0),i=s(8),u=s(4),f=((d,b,_)=>Math.min(Math.max(d,b),_))(n,0,1),h=Math.round(f*255).toString(16).padStart(2,"0").toUpperCase();return`#${c}${i}${u}${h}`},yl=(e,t,r=1)=>{const n=vc(e),o=vc(t==="white"?"#FFFFFF":t==="black"?"#000000":t),a=n.map((u,l)=>[(u-o[l])/(255-o[l]),(u-o[l])/(0-o[l])]),s=vl(Math.max(...a.flat().filter(u=>/^-?\d+\.?\d*$/.test(u)))),c=n.map((u,l)=>Math.round((u-o[l]+o[l]*s)/s));if(c.includes(Number.NaN)){const u=_c(n[0],n[1],n[2]);return{h:u[0],s:Math.round(u[1]*r),l:u[2],a:1}}const i=_c(c[0],c[1],c[2]);return{h:i[0],s:Math.round(i[1]*r),l:i[2],a:s}},ro={backgroundColor:"gray",colorSpace:"OKLCH",colorSmoothing:!1,formula:"wcag2",output:"HEX",colors:{gray:[K(215,20,90),K(215,8,50),K(215,6,25)],red:[K(358,100,58),K(350,100,30)],orange:[K(32,100,48),K(12,100,30)],yellow:[K(50,100,50),K(25,100,20)],lime:[K(100,68,50),K(115,86,25)],green:[K(163,87,42),K(168,100,25)],cyan:[K(185,80,45),K(200,98,35)],blue:[K(212,98,46),K(222,95,25)],purple:[K(258,94,64),K(265,100,35)],fuchsia:[K(295,56,50),K(285,80,25)],pink:[K(334,90,50),K(330,91,25)]},themes:{light:{ratios:[1.03,1.06,1.12,1.25,1.5,1.75,2.25,3.5,5.25,6.5,8,10.5,13.75,16.75],contrast:1,lightness:100,saturation:100},dark:{ratios:[1.03,1.06,1.12,1.25,1.5,1.75,2.25,3.5,5.25,6.5,8,10.5,13.75,16],contrast:1,lightness:6,saturation:97},lightHc:{ratios:[1.06,1.12,1.25,1.37,1.75,2.25,3.25,4.75,8.87,10,11.75,13.25,16,17],contrast:1,lightness:100,saturation:100},darkHc:{ratios:[1.06,1.12,1.25,1.37,1.75,2.25,3.25,4.75,8.87,10,11.75,13.25,16,17],contrast:1,lightness:6,saturation:97}}};function K(e,t,r){return G.hsl(e,t/100,r/100).hex()}function wl(e,t){const r=e.colorSpace,n=e.colorSmoothing,o=e.themes[t].ratios,a=new qa({name:"gray",colorKeys:e.colors.gray,colorspace:r,ratios:o,smooth:n}),s=new ae({name:"blue",colorKeys:e.colors.blue,colorspace:r,ratios:o,smooth:n}),c=new ae({name:"cyan",colorKeys:e.colors.cyan,colorspace:r,ratios:o,smooth:n}),i=new ae({name:"fuchsia",colorKeys:e.colors.fuchsia,colorspace:r,ratios:o,smooth:n}),u=new ae({name:"green",colorKeys:e.colors.green,colorspace:r,ratios:o,smooth:n}),l=new ae({name:"lime",colorKeys:e.colors.lime,colorspace:r,ratios:o,smooth:n}),f=new ae({name:"orange",colorKeys:e.colors.orange,colorspace:r,ratios:o,smooth:n}),h=new ae({name:"pink",colorKeys:e.colors.pink,colorspace:r,ratios:o,smooth:n}),d=new ae({name:"purple",colorKeys:e.colors.purple,colorspace:r,ratios:o,smooth:n}),b=new ae({name:"red",colorKeys:e.colors.red,colorspace:r,ratios:o,smooth:n}),_=new ae({name:"yellow",colorKeys:e.colors.yellow,colorspace:r,ratios:o,smooth:n}),g={gray:a,red:b,orange:f,yellow:_,lime:l,green:u,cyan:c,blue:s,purple:d,fuchsia:i,pink:h};return e.colors.custom&&(g.custom=new ae({name:"custom",colorKeys:e.colors.custom,colorspace:r,ratios:o,smooth:n})),new wu({colors:Object.values(g),backgroundColor:g[e.backgroundColor],contrast:e.themes[t].contrast,lightness:e.themes[t].lightness,saturation:e.themes[t].saturation,output:e.output,formula:e.formula}).contrastColors}function yc(e){const t={};for(const r of Object.keys(e.themes))t[r]=wl(e,r);return t}function kl(e){ro.colors.custom=[e];const t=yc(ro);return Object.fromEntries(Object.entries(t).map(([r,n])=>{const o=n.find(s=>s&&s.name==="custom"),a=Object.fromEntries(o.values.map(({name:s,value:c})=>[s,c]));for(const[s,c]of Object.entries(a)){const i=yl(c,n[0].background);a[`alpha${s.charAt(0).toUpperCase()+s.slice(1)}`]=_l(i.h,i.s,i.l,i.a)}return[r,a]}))}return Fe.generateCustomColors=kl,Fe.generateThemesJson=yc,Fe.hslToHex=K,Fe.leonardoConfig=ro,Object.defineProperty(Fe,Symbol.toStringTag,{value:"Module"}),Fe})({}); diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/annotations/CoreColorToken.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/annotations/CoreColorToken.kt index a56ecb38a8..ad9e1eab7a 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/annotations/CoreColorToken.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/annotations/CoreColorToken.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/colors/SemanticColorsLightDark.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/colors/SemanticColorsLightDark.kt new file mode 100644 index 0000000000..09064105ea --- /dev/null +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/colors/SemanticColorsLightDark.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.compound.colors + +import io.element.android.compound.tokens.generated.SemanticColors +import io.element.android.compound.tokens.generated.compoundColorsDark +import io.element.android.compound.tokens.generated.compoundColorsLight + +data class SemanticColorsLightDark( + val light: SemanticColors, + val dark: SemanticColors, +) { + companion object { + val default = SemanticColorsLightDark( + light = compoundColorsLight, + dark = compoundColorsDark, + ) + } +} diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/previews/ColorListPreview.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/previews/ColorListPreview.kt index 715da1ebc3..761348804a 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/previews/ColorListPreview.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/previews/ColorListPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/previews/ColorPreview.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/previews/ColorPreview.kt index 3248adeedf..396fd9e080 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/previews/ColorPreview.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/previews/ColorPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/previews/ColorsSchemePreview.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/previews/ColorsSchemePreview.kt index cf660ba236..f44e247dc1 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/previews/ColorsSchemePreview.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/previews/ColorsSchemePreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/previews/CompoundIconsPreview.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/previews/CompoundIconsPreview.kt index 0ee0546ca4..236e3627f4 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/previews/CompoundIconsPreview.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/previews/CompoundIconsPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -41,13 +42,13 @@ import io.element.android.compound.tokens.generated.CompoundIcons import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -@Preview(widthDp = 730, heightDp = 1800) +@Preview(widthDp = 730, heightDp = 1920) @Composable internal fun IconsCompoundPreviewLight() = ElementTheme { IconsCompoundPreview() } -@Preview(widthDp = 730, heightDp = 1800) +@Preview(widthDp = 730, heightDp = 1920) @Composable internal fun IconsCompoundPreviewRtl() = ElementTheme { CompositionLocalProvider( @@ -59,7 +60,7 @@ internal fun IconsCompoundPreviewRtl() = ElementTheme { } } -@Preview(widthDp = 730, heightDp = 1800) +@Preview(widthDp = 730, heightDp = 1920) @Composable internal fun IconsCompoundPreviewDark() = ElementTheme(darkTheme = true) { IconsCompoundPreview() diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/previews/SemanticColorsPreview.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/previews/SemanticColorsPreview.kt index d641ff39c5..3f5fb42149 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/previews/SemanticColorsPreview.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/previews/SemanticColorsPreview.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/previews/Typography.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/previews/Typography.kt index 5d5f31f203..2f2ef942a7 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/previews/Typography.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/previews/Typography.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,13 +17,14 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.compoundTypography @Preview @Composable internal fun TypographyPreview() = ElementTheme { Surface { Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - with(ElementTheme.materialTypography) { + with(compoundTypography) { TypographyTokenPreview(displayLarge, "Display large") TypographyTokenPreview(displayMedium, "Display medium") TypographyTokenPreview(displaySmall, "Display small") @@ -43,6 +45,33 @@ internal fun TypographyPreview() = ElementTheme { } } +@Preview +@Composable +internal fun CompoundTypographyPreview() = ElementTheme { + Surface { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + with(ElementTheme.typography) { + TypographyTokenPreview(fontHeadingXlBold, "fontHeadingXlBold") + TypographyTokenPreview(fontHeadingXlRegular, "fontHeadingXlRegular") + TypographyTokenPreview(fontHeadingLgBold, "fontHeadingLgBold") + TypographyTokenPreview(fontHeadingLgRegular, "fontHeadingLgRegular") + TypographyTokenPreview(fontHeadingMdBold, "fontHeadingMdBold") + TypographyTokenPreview(fontHeadingMdRegular, "fontHeadingMdRegular") + TypographyTokenPreview(fontHeadingSmMedium, "fontHeadingSmMedium") + TypographyTokenPreview(fontHeadingSmRegular, "fontHeadingSmRegular") + TypographyTokenPreview(fontBodyLgMedium, "fontBodyLgMedium") + TypographyTokenPreview(fontBodyLgRegular, "fontBodyLgRegular") + TypographyTokenPreview(fontBodyMdMedium, "fontBodyMdMedium") + TypographyTokenPreview(fontBodyMdRegular, "fontBodyMdRegular") + TypographyTokenPreview(fontBodySmMedium, "fontBodySmMedium") + TypographyTokenPreview(fontBodySmRegular, "fontBodySmRegular") + TypographyTokenPreview(fontBodyXsMedium, "fontBodyXsMedium") + TypographyTokenPreview(fontBodyXsRegular, "fontBodyXsRegular") + } + } + } +} + @Composable private fun TypographyTokenPreview(style: TextStyle, text: String) { Text(text = text, style = style) diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/showkase/CompoundShowkaseRootModule.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/showkase/CompoundShowkaseRootModule.kt index f1c7b3f8d7..4f36d0cc99 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/showkase/CompoundShowkaseRootModule.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/showkase/CompoundShowkaseRootModule.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/AvatarColors.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/AvatarColors.kt index 506e1c8063..763f422a00 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/AvatarColors.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/AvatarColors.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/ElementTheme.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/ElementTheme.kt index 7535de0acd..bb2ae2b62e 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/ElementTheme.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/ElementTheme.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -61,14 +62,6 @@ object ElementTheme { */ val typography: TypographyTokens = TypographyTokens - /** - * Material 3 [Typography] tokens. In Figma, these have the `M3 Typography/` prefix. - */ - val materialTypography: Typography - @Composable - @ReadOnlyComposable - get() = MaterialTheme.typography - /** * Returns whether the theme version used is the light or the dark one. */ diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/ForcedDarkElementTheme.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/ForcedDarkElementTheme.kt index cd168713ae..272245f199 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/ForcedDarkElementTheme.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/ForcedDarkElementTheme.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,6 +17,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb +import io.element.android.compound.colors.SemanticColorsLightDark /** * Can be used to force a composable in dark theme. @@ -23,6 +25,7 @@ import androidx.compose.ui.graphics.toArgb */ @Composable fun ForcedDarkElementTheme( + colors: SemanticColorsLightDark, lightStatusBar: Boolean = false, content: @Composable () -> Unit, ) { @@ -47,5 +50,11 @@ fun ForcedDarkElementTheme( ) } } - ElementTheme(darkTheme = true, lightStatusBar = lightStatusBar, content = content) + ElementTheme( + darkTheme = true, + compoundLight = colors.light, + compoundDark = colors.dark, + lightStatusBar = lightStatusBar, + content = content, + ) } diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/LegacyColors.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/LegacyColors.kt index 6b7814d1e3..503804f0cb 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/LegacyColors.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/LegacyColors.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialColorSchemeDark.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialColorSchemeDark.kt index 7ee6b631b5..6510b61a57 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialColorSchemeDark.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialColorSchemeDark.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialColorSchemeLight.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialColorSchemeLight.kt index b179234429..df8fa572b6 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialColorSchemeLight.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialColorSchemeLight.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialTextPreview.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialTextPreview.kt index 724d440161..792c7fbabf 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialTextPreview.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialTextPreview.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialThemeColors.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialThemeColors.kt index f81b0a94f6..c2a923e926 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialThemeColors.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/MaterialThemeColors.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/Theme.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/Theme.kt index 06668e2952..131b14430c 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/theme/Theme.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/theme/Theme.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/CompoundTypography.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/CompoundTypography.kt index ab4d898ba4..9417a306bf 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/CompoundTypography.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/CompoundTypography.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/CompoundIcons.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/CompoundIcons.kt index 76b3275651..6c30eda7f9 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/CompoundIcons.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/CompoundIcons.kt @@ -1,11 +1,10 @@ /* - * Copyright 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ - /** * !!! WARNING !!! * @@ -13,8 +12,6 @@ * DO NOT EDIT MANUALLY. */ - - @file:Suppress("all") package io.element.android.compound.tokens.generated @@ -55,6 +52,12 @@ object CompoundIcons { @Composable fun Audio(): ImageVector { return ImageVector.vectorResource(R.drawable.ic_compound_audio) } + @Composable fun Backspace(): ImageVector { + return ImageVector.vectorResource(R.drawable.ic_compound_backspace) + } + @Composable fun BackspaceSolid(): ImageVector { + return ImageVector.vectorResource(R.drawable.ic_compound_backspace_solid) + } @Composable fun Block(): ImageVector { return ImageVector.vectorResource(R.drawable.ic_compound_block) } @@ -184,6 +187,9 @@ object CompoundIcons { @Composable fun ErrorSolid(): ImageVector { return ImageVector.vectorResource(R.drawable.ic_compound_error_solid) } + @Composable fun ExitFullScreen(): ImageVector { + return ImageVector.vectorResource(R.drawable.ic_compound_exit_full_screen) + } @Composable fun Expand(): ImageVector { return ImageVector.vectorResource(R.drawable.ic_compound_expand) } @@ -217,6 +223,9 @@ object CompoundIcons { @Composable fun Forward(): ImageVector { return ImageVector.vectorResource(R.drawable.ic_compound_forward) } + @Composable fun FullScreen(): ImageVector { + return ImageVector.vectorResource(R.drawable.ic_compound_full_screen) + } @Composable fun Grid(): ImageVector { return ImageVector.vectorResource(R.drawable.ic_compound_grid) } @@ -295,6 +304,12 @@ object CompoundIcons { @Composable fun Leave(): ImageVector { return ImageVector.vectorResource(R.drawable.ic_compound_leave) } + @Composable fun LeftPanelClose(): ImageVector { + return ImageVector.vectorResource(R.drawable.ic_compound_left_panel_close) + } + @Composable fun LeftPanelOpen(): ImageVector { + return ImageVector.vectorResource(R.drawable.ic_compound_left_panel_open) + } @Composable fun Link(): ImageVector { return ImageVector.vectorResource(R.drawable.ic_compound_link) } @@ -502,6 +517,12 @@ object CompoundIcons { @Composable fun SignOut(): ImageVector { return ImageVector.vectorResource(R.drawable.ic_compound_sign_out) } + @Composable fun Space(): ImageVector { + return ImageVector.vectorResource(R.drawable.ic_compound_space) + } + @Composable fun SpaceSolid(): ImageVector { + return ImageVector.vectorResource(R.drawable.ic_compound_space_solid) + } @Composable fun Spinner(): ImageVector { return ImageVector.vectorResource(R.drawable.ic_compound_spinner) } @@ -511,6 +532,9 @@ object CompoundIcons { @Composable fun SpotlightView(): ImageVector { return ImageVector.vectorResource(R.drawable.ic_compound_spotlight_view) } + @Composable fun Sticker(): ImageVector { + return ImageVector.vectorResource(R.drawable.ic_compound_sticker) + } @Composable fun Strikethrough(): ImageVector { return ImageVector.vectorResource(R.drawable.ic_compound_strikethrough) } @@ -619,12 +643,6 @@ object CompoundIcons { @Composable fun Windows(): ImageVector { return ImageVector.vectorResource(R.drawable.ic_compound_windows) } - @Composable fun Workspace(): ImageVector { - return ImageVector.vectorResource(R.drawable.ic_compound_workspace) - } - @Composable fun WorkspaceSolid(): ImageVector { - return ImageVector.vectorResource(R.drawable.ic_compound_workspace_solid) - } val all @Composable get() = persistentListOf( Admin(), @@ -637,6 +655,8 @@ object CompoundIcons { AskToJoinSolid(), Attachment(), Audio(), + Backspace(), + BackspaceSolid(), Block(), Bold(), Calendar(), @@ -680,6 +700,7 @@ object CompoundIcons { EndCall(), Error(), ErrorSolid(), + ExitFullScreen(), Expand(), Explore(), ExportArchive(), @@ -691,6 +712,7 @@ object CompoundIcons { Files(), Filter(), Forward(), + FullScreen(), Grid(), Group(), Guest(), @@ -717,6 +739,8 @@ object CompoundIcons { Keyboard(), Labs(), Leave(), + LeftPanelClose(), + LeftPanelOpen(), Link(), Linux(), ListBulleted(), @@ -786,9 +810,12 @@ object CompoundIcons { Shield(), Sidebar(), SignOut(), + Space(), + SpaceSolid(), Spinner(), Spotlight(), SpotlightView(), + Sticker(), Strikethrough(), SwitchCameraSolid(), TakePhoto(), @@ -825,8 +852,6 @@ object CompoundIcons { Warning(), WebBrowser(), Windows(), - Workspace(), - WorkspaceSolid(), ) val allResIds get() = persistentListOf( @@ -840,6 +865,8 @@ object CompoundIcons { R.drawable.ic_compound_ask_to_join_solid, R.drawable.ic_compound_attachment, R.drawable.ic_compound_audio, + R.drawable.ic_compound_backspace, + R.drawable.ic_compound_backspace_solid, R.drawable.ic_compound_block, R.drawable.ic_compound_bold, R.drawable.ic_compound_calendar, @@ -883,6 +910,7 @@ object CompoundIcons { R.drawable.ic_compound_end_call, R.drawable.ic_compound_error, R.drawable.ic_compound_error_solid, + R.drawable.ic_compound_exit_full_screen, R.drawable.ic_compound_expand, R.drawable.ic_compound_explore, R.drawable.ic_compound_export_archive, @@ -894,6 +922,7 @@ object CompoundIcons { R.drawable.ic_compound_files, R.drawable.ic_compound_filter, R.drawable.ic_compound_forward, + R.drawable.ic_compound_full_screen, R.drawable.ic_compound_grid, R.drawable.ic_compound_group, R.drawable.ic_compound_guest, @@ -920,6 +949,8 @@ object CompoundIcons { R.drawable.ic_compound_keyboard, R.drawable.ic_compound_labs, R.drawable.ic_compound_leave, + R.drawable.ic_compound_left_panel_close, + R.drawable.ic_compound_left_panel_open, R.drawable.ic_compound_link, R.drawable.ic_compound_linux, R.drawable.ic_compound_list_bulleted, @@ -989,9 +1020,12 @@ object CompoundIcons { R.drawable.ic_compound_shield, R.drawable.ic_compound_sidebar, R.drawable.ic_compound_sign_out, + R.drawable.ic_compound_space, + R.drawable.ic_compound_space_solid, R.drawable.ic_compound_spinner, R.drawable.ic_compound_spotlight, R.drawable.ic_compound_spotlight_view, + R.drawable.ic_compound_sticker, R.drawable.ic_compound_strikethrough, R.drawable.ic_compound_switch_camera_solid, R.drawable.ic_compound_take_photo, @@ -1028,7 +1062,5 @@ object CompoundIcons { R.drawable.ic_compound_warning, R.drawable.ic_compound_web_browser, R.drawable.ic_compound_windows, - R.drawable.ic_compound_workspace, - R.drawable.ic_compound_workspace_solid, ) } diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/DO_NOT_MODIFY.txt b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/DO_NOT_MODIFY.txt index a6f7dd3f6a..4c88fb5a02 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/DO_NOT_MODIFY.txt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/DO_NOT_MODIFY.txt @@ -1 +1 @@ -Files inside this package are generated automatically from the Compound project (https://github.com/vector-im/compound-design-tokens) and will be batch-replaced when new tokens are generated. +Files inside this package are generated automatically from the Compound project (https://github.com/element-hq/compound-design-tokens) and will be batch-replaced when new tokens are generated. diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColors.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColors.kt index 8da51213f8..6136b04d4b 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColors.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColors.kt @@ -1,18 +1,10 @@ /* - * Copyright 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ - -@file:Suppress("all") -package io.element.android.compound.tokens.generated - -import androidx.compose.runtime.Immutable -import androidx.compose.ui.graphics.Color - - /** * !!! WARNING !!! * @@ -20,13 +12,14 @@ import androidx.compose.ui.graphics.Color * DO NOT EDIT MANUALLY. */ +@file:Suppress("all") +package io.element.android.compound.tokens.generated - +import androidx.compose.ui.graphics.Color /** - * This class holds all the semantic tokens of the Compound theme. - */ -@Immutable + * This class holds all the semantic tokens of the Compound theme. + */ data class SemanticColors( /** Background colour for accent or brand actions. State: Hover */ val bgAccentHovered: Color, @@ -50,17 +43,21 @@ data class SemanticColors( val bgActionSecondaryPressed: Color, /** Background colour for secondary actions. State: Rest. */ val bgActionSecondaryRest: Color, + /** Background colour for tertiary actions. State: Hover */ + val bgActionTertiaryHovered: Color, + /** Background colour for tertiary actions. State: Rest */ + val bgActionTertiaryRest: Color, + /** Background colour for tertiary actions. State: Selected */ + val bgActionTertiarySelected: Color, /** Badge accent background colour */ val bgBadgeAccent: Color, /** Badge default background colour */ val bgBadgeDefault: Color, /** Badge info background colour */ val bgBadgeInfo: Color, - /** Default global background for the user interface. -Elevation: Default (Level 0) */ + /** Default global background for the user interface. Elevation: Default (Level 0) */ val bgCanvasDefault: Color, - /** Default global background for the user interface. -Elevation: Level 1. */ + /** Default global background for the user interface. Elevation: Level 1. */ val bgCanvasDefaultLevel1: Color, /** Default background for disabled elements. There's no minimum contrast requirement. */ val bgCanvasDisabled: Color, @@ -86,14 +83,11 @@ Elevation: Level 1. */ val bgDecorative6: Color, /** Subtle background colour for informational elements. State: Rest. */ val bgInfoSubtle: Color, - /** Medium contrast surfaces. -Elevation: Default (Level 2). */ + /** Medium contrast surfaces. Elevation: Default (Level 2). */ val bgSubtlePrimary: Color, - /** Low contrast surfaces. -Elevation: Default (Level 1). */ + /** Low contrast surfaces. Elevation: Default (Level 1). */ val bgSubtleSecondary: Color, - /** Lower contrast surfaces. -Elevation: Level 0. */ + /** Lower contrast surfaces. Elevation: Level 0. */ val bgSubtleSecondaryLevel0: Color, /** Subtle background colour for success state elements. State: Rest. */ val bgSuccessSubtle: Color, diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColorsDark.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColorsDark.kt index 5ccdfaa308..60afe79ae2 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColorsDark.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColorsDark.kt @@ -1,7 +1,7 @@ /* - * Copyright 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,8 +12,6 @@ * DO NOT EDIT MANUALLY. */ - - @file:Suppress("all") package io.element.android.compound.tokens.generated @@ -36,9 +34,12 @@ val compoundColorsDark = SemanticColors( bgActionSecondaryHovered = DarkColorTokens.colorAlphaGray200, bgActionSecondaryPressed = DarkColorTokens.colorAlphaGray300, bgActionSecondaryRest = DarkColorTokens.colorThemeBg, - bgBadgeAccent = DarkColorTokens.colorAlphaGreen300, - bgBadgeDefault = DarkColorTokens.colorAlphaGray300, - bgBadgeInfo = DarkColorTokens.colorAlphaBlue300, + bgActionTertiaryHovered = DarkColorTokens.colorGray300, + bgActionTertiaryRest = DarkColorTokens.colorThemeBg, + bgActionTertiarySelected = DarkColorTokens.colorGray400, + bgBadgeAccent = DarkColorTokens.colorAlphaGreen500, + bgBadgeDefault = DarkColorTokens.colorAlphaGray500, + bgBadgeInfo = DarkColorTokens.colorAlphaBlue500, bgCanvasDefault = DarkColorTokens.colorThemeBg, bgCanvasDefaultLevel1 = DarkColorTokens.colorGray300, bgCanvasDisabled = DarkColorTokens.colorGray200, @@ -88,7 +89,7 @@ val compoundColorsDark = SemanticColors( iconAccentTertiary = DarkColorTokens.colorGreen800, iconCriticalPrimary = DarkColorTokens.colorRed900, iconDisabled = DarkColorTokens.colorGray700, - iconInfoPrimary = DarkColorTokens.colorBlue900, + iconInfoPrimary = DarkColorTokens.colorBlue1100, iconOnSolidPrimary = DarkColorTokens.colorThemeBg, iconPrimary = DarkColorTokens.colorGray1400, iconPrimaryAlpha = DarkColorTokens.colorAlphaGray1400, @@ -111,8 +112,8 @@ val compoundColorsDark = SemanticColors( textDecorative5 = DarkColorTokens.colorPink1100, textDecorative6 = DarkColorTokens.colorOrange1100, textDisabled = DarkColorTokens.colorGray800, - textInfoPrimary = DarkColorTokens.colorBlue900, - textLinkExternal = DarkColorTokens.colorBlue900, + textInfoPrimary = DarkColorTokens.colorBlue1100, + textLinkExternal = DarkColorTokens.colorBlue1100, textOnSolidPrimary = DarkColorTokens.colorThemeBg, textPrimary = DarkColorTokens.colorGray1400, textSecondary = DarkColorTokens.colorGray900, diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColorsDarkHc.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColorsDarkHc.kt index 88d2ef3abe..c718caeeeb 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColorsDarkHc.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColorsDarkHc.kt @@ -1,7 +1,7 @@ /* - * Copyright 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,8 +12,6 @@ * DO NOT EDIT MANUALLY. */ - - @file:Suppress("all") package io.element.android.compound.tokens.generated @@ -36,9 +34,12 @@ val compoundColorsHcDark = SemanticColors( bgActionSecondaryHovered = DarkHcColorTokens.colorAlphaGray200, bgActionSecondaryPressed = DarkHcColorTokens.colorAlphaGray300, bgActionSecondaryRest = DarkHcColorTokens.colorThemeBg, - bgBadgeAccent = DarkHcColorTokens.colorAlphaGreen300, - bgBadgeDefault = DarkHcColorTokens.colorAlphaGray300, - bgBadgeInfo = DarkHcColorTokens.colorAlphaBlue300, + bgActionTertiaryHovered = DarkHcColorTokens.colorGray300, + bgActionTertiaryRest = DarkHcColorTokens.colorThemeBg, + bgActionTertiarySelected = DarkHcColorTokens.colorGray400, + bgBadgeAccent = DarkHcColorTokens.colorAlphaGreen500, + bgBadgeDefault = DarkHcColorTokens.colorAlphaGray500, + bgBadgeInfo = DarkHcColorTokens.colorAlphaBlue500, bgCanvasDefault = DarkHcColorTokens.colorThemeBg, bgCanvasDefaultLevel1 = DarkHcColorTokens.colorGray300, bgCanvasDisabled = DarkHcColorTokens.colorGray200, @@ -88,7 +89,7 @@ val compoundColorsHcDark = SemanticColors( iconAccentTertiary = DarkHcColorTokens.colorGreen800, iconCriticalPrimary = DarkHcColorTokens.colorRed900, iconDisabled = DarkHcColorTokens.colorGray700, - iconInfoPrimary = DarkHcColorTokens.colorBlue900, + iconInfoPrimary = DarkHcColorTokens.colorBlue1100, iconOnSolidPrimary = DarkHcColorTokens.colorThemeBg, iconPrimary = DarkHcColorTokens.colorGray1400, iconPrimaryAlpha = DarkHcColorTokens.colorAlphaGray1400, @@ -111,8 +112,8 @@ val compoundColorsHcDark = SemanticColors( textDecorative5 = DarkHcColorTokens.colorPink1100, textDecorative6 = DarkHcColorTokens.colorOrange1100, textDisabled = DarkHcColorTokens.colorGray800, - textInfoPrimary = DarkHcColorTokens.colorBlue900, - textLinkExternal = DarkHcColorTokens.colorBlue900, + textInfoPrimary = DarkHcColorTokens.colorBlue1100, + textLinkExternal = DarkHcColorTokens.colorBlue1100, textOnSolidPrimary = DarkHcColorTokens.colorThemeBg, textPrimary = DarkHcColorTokens.colorGray1400, textSecondary = DarkHcColorTokens.colorGray900, diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColorsLight.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColorsLight.kt index 03173ad24e..377997ec79 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColorsLight.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColorsLight.kt @@ -1,7 +1,7 @@ /* - * Copyright 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,8 +12,6 @@ * DO NOT EDIT MANUALLY. */ - - @file:Suppress("all") package io.element.android.compound.tokens.generated @@ -36,9 +34,12 @@ val compoundColorsLight = SemanticColors( bgActionSecondaryHovered = LightColorTokens.colorAlphaGray200, bgActionSecondaryPressed = LightColorTokens.colorAlphaGray300, bgActionSecondaryRest = LightColorTokens.colorThemeBg, - bgBadgeAccent = LightColorTokens.colorAlphaGreen300, - bgBadgeDefault = LightColorTokens.colorAlphaGray300, - bgBadgeInfo = LightColorTokens.colorAlphaBlue300, + bgActionTertiaryHovered = LightColorTokens.colorGray300, + bgActionTertiaryRest = LightColorTokens.colorThemeBg, + bgActionTertiarySelected = LightColorTokens.colorGray400, + bgBadgeAccent = LightColorTokens.colorAlphaGreen400, + bgBadgeDefault = LightColorTokens.colorAlphaGray400, + bgBadgeInfo = LightColorTokens.colorAlphaBlue400, bgCanvasDefault = LightColorTokens.colorThemeBg, bgCanvasDefaultLevel1 = LightColorTokens.colorThemeBg, bgCanvasDisabled = LightColorTokens.colorGray200, @@ -88,7 +89,7 @@ val compoundColorsLight = SemanticColors( iconAccentTertiary = LightColorTokens.colorGreen800, iconCriticalPrimary = LightColorTokens.colorRed900, iconDisabled = LightColorTokens.colorGray700, - iconInfoPrimary = LightColorTokens.colorBlue900, + iconInfoPrimary = LightColorTokens.colorBlue1100, iconOnSolidPrimary = LightColorTokens.colorThemeBg, iconPrimary = LightColorTokens.colorGray1400, iconPrimaryAlpha = LightColorTokens.colorAlphaGray1400, @@ -111,8 +112,8 @@ val compoundColorsLight = SemanticColors( textDecorative5 = LightColorTokens.colorPink1100, textDecorative6 = LightColorTokens.colorOrange1100, textDisabled = LightColorTokens.colorGray800, - textInfoPrimary = LightColorTokens.colorBlue900, - textLinkExternal = LightColorTokens.colorBlue900, + textInfoPrimary = LightColorTokens.colorBlue1100, + textLinkExternal = LightColorTokens.colorBlue1100, textOnSolidPrimary = LightColorTokens.colorThemeBg, textPrimary = LightColorTokens.colorGray1400, textSecondary = LightColorTokens.colorGray900, diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColorsLightHc.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColorsLightHc.kt index 00100f7ea8..b32a0dd9f0 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColorsLightHc.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/SemanticColorsLightHc.kt @@ -1,7 +1,7 @@ /* - * Copyright 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,8 +12,6 @@ * DO NOT EDIT MANUALLY. */ - - @file:Suppress("all") package io.element.android.compound.tokens.generated @@ -36,9 +34,12 @@ val compoundColorsHcLight = SemanticColors( bgActionSecondaryHovered = LightHcColorTokens.colorAlphaGray200, bgActionSecondaryPressed = LightHcColorTokens.colorAlphaGray300, bgActionSecondaryRest = LightHcColorTokens.colorThemeBg, - bgBadgeAccent = LightHcColorTokens.colorAlphaGreen300, - bgBadgeDefault = LightHcColorTokens.colorAlphaGray300, - bgBadgeInfo = LightHcColorTokens.colorAlphaBlue300, + bgActionTertiaryHovered = LightHcColorTokens.colorGray300, + bgActionTertiaryRest = LightHcColorTokens.colorThemeBg, + bgActionTertiarySelected = LightHcColorTokens.colorGray400, + bgBadgeAccent = LightHcColorTokens.colorAlphaGreen400, + bgBadgeDefault = LightHcColorTokens.colorAlphaGray400, + bgBadgeInfo = LightHcColorTokens.colorAlphaBlue400, bgCanvasDefault = LightHcColorTokens.colorThemeBg, bgCanvasDefaultLevel1 = LightHcColorTokens.colorThemeBg, bgCanvasDisabled = LightHcColorTokens.colorGray200, @@ -88,7 +89,7 @@ val compoundColorsHcLight = SemanticColors( iconAccentTertiary = LightHcColorTokens.colorGreen800, iconCriticalPrimary = LightHcColorTokens.colorRed900, iconDisabled = LightHcColorTokens.colorGray700, - iconInfoPrimary = LightHcColorTokens.colorBlue900, + iconInfoPrimary = LightHcColorTokens.colorBlue1100, iconOnSolidPrimary = LightHcColorTokens.colorThemeBg, iconPrimary = LightHcColorTokens.colorGray1400, iconPrimaryAlpha = LightHcColorTokens.colorAlphaGray1400, @@ -111,8 +112,8 @@ val compoundColorsHcLight = SemanticColors( textDecorative5 = LightHcColorTokens.colorPink1100, textDecorative6 = LightHcColorTokens.colorOrange1100, textDisabled = LightHcColorTokens.colorGray800, - textInfoPrimary = LightHcColorTokens.colorBlue900, - textLinkExternal = LightHcColorTokens.colorBlue900, + textInfoPrimary = LightHcColorTokens.colorBlue1100, + textLinkExternal = LightHcColorTokens.colorBlue1100, textOnSolidPrimary = LightHcColorTokens.colorThemeBg, textPrimary = LightHcColorTokens.colorGray1400, textSecondary = LightHcColorTokens.colorGray900, diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/TypographyTokens.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/TypographyTokens.kt index a25b79234c..0f450776ef 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/TypographyTokens.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/TypographyTokens.kt @@ -1,11 +1,10 @@ /* - * Copyright 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ - /** * !!! WARNING !!! * @@ -13,8 +12,6 @@ * DO NOT EDIT MANUALLY. */ - - @file:Suppress("all") package io.element.android.compound.tokens.generated diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/internal/DarkColorTokens.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/internal/DarkColorTokens.kt index 1272c0df16..04dbf6b9f2 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/internal/DarkColorTokens.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/internal/DarkColorTokens.kt @@ -1,7 +1,7 @@ /* - * Copyright 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,8 +12,6 @@ * DO NOT EDIT MANUALLY. */ - - @file:Suppress("all") package io.element.android.compound.tokens.generated.internal diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/internal/DarkHcColorTokens.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/internal/DarkHcColorTokens.kt index 31406f6ffc..8b2c04a93a 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/internal/DarkHcColorTokens.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/internal/DarkHcColorTokens.kt @@ -1,7 +1,7 @@ /* - * Copyright 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,8 +12,6 @@ * DO NOT EDIT MANUALLY. */ - - @file:Suppress("all") package io.element.android.compound.tokens.generated.internal diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/internal/LightColorTokens.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/internal/LightColorTokens.kt index 119db9dade..c7561c7ce9 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/internal/LightColorTokens.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/internal/LightColorTokens.kt @@ -1,7 +1,7 @@ /* - * Copyright 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,8 +12,6 @@ * DO NOT EDIT MANUALLY. */ - - @file:Suppress("all") package io.element.android.compound.tokens.generated.internal diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/internal/LightHcColorTokens.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/internal/LightHcColorTokens.kt index 86624bb597..40c91c5b53 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/internal/LightHcColorTokens.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/tokens/generated/internal/LightHcColorTokens.kt @@ -1,7 +1,7 @@ /* - * Copyright 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,8 +12,6 @@ * DO NOT EDIT MANUALLY. */ - - @file:Suppress("all") package io.element.android.compound.tokens.generated.internal diff --git a/libraries/compound/src/main/kotlin/io/element/android/compound/utils/ColorUtils.kt b/libraries/compound/src/main/kotlin/io/element/android/compound/utils/ColorUtils.kt index f0c6c12fb0..778f5d70d2 100644 --- a/libraries/compound/src/main/kotlin/io/element/android/compound/utils/ColorUtils.kt +++ b/libraries/compound/src/main/kotlin/io/element/android/compound/utils/ColorUtils.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/main/res/drawable/ic_compound_admin.xml b/libraries/compound/src/main/res/drawable/ic_compound_admin.xml index 8762195e7f..6cedd3ac01 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_admin.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_admin.xml @@ -3,8 +3,8 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_arrow_down.xml b/libraries/compound/src/main/res/drawable/ic_compound_arrow_down.xml index 6d311ac7a2..ceb5dbf670 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_arrow_down.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_arrow_down.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_arrow_left.xml b/libraries/compound/src/main/res/drawable/ic_compound_arrow_left.xml index 4e1949895f..0c87601fad 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_arrow_left.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_arrow_left.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_arrow_right.xml b/libraries/compound/src/main/res/drawable/ic_compound_arrow_right.xml index 4a42e3c16f..5703b5e079 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_arrow_right.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_arrow_right.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_arrow_up.xml b/libraries/compound/src/main/res/drawable/ic_compound_arrow_up.xml index 7ff26b539a..8591911af3 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_arrow_up.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_arrow_up.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_arrow_up_right.xml b/libraries/compound/src/main/res/drawable/ic_compound_arrow_up_right.xml index ea686b6cff..9f04df4b91 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_arrow_up_right.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_arrow_up_right.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_ask_to_join.xml b/libraries/compound/src/main/res/drawable/ic_compound_ask_to_join.xml index 2c2f779ab6..3d92b4c18a 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_ask_to_join.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_ask_to_join.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_ask_to_join_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_ask_to_join_solid.xml index 1346e2c889..5bd208e44c 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_ask_to_join_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_ask_to_join_solid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_attachment.xml b/libraries/compound/src/main/res/drawable/ic_compound_attachment.xml index 4ddf7f4df7..7d96cfc20b 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_attachment.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_attachment.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_audio.xml b/libraries/compound/src/main/res/drawable/ic_compound_audio.xml index 5fb9365470..adb53a9675 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_audio.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_audio.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_backspace.xml b/libraries/compound/src/main/res/drawable/ic_compound_backspace.xml new file mode 100644 index 0000000000..90464cb595 --- /dev/null +++ b/libraries/compound/src/main/res/drawable/ic_compound_backspace.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_backspace_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_backspace_solid.xml new file mode 100644 index 0000000000..27d5805811 --- /dev/null +++ b/libraries/compound/src/main/res/drawable/ic_compound_backspace_solid.xml @@ -0,0 +1,10 @@ + + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_block.xml b/libraries/compound/src/main/res/drawable/ic_compound_block.xml index 08ef51dce3..4519a8bb9c 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_block.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_block.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_bold.xml b/libraries/compound/src/main/res/drawable/ic_compound_bold.xml index 2546a230cc..ca871203db 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_bold.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_bold.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_calendar.xml b/libraries/compound/src/main/res/drawable/ic_compound_calendar.xml index 72a9fe5868..490317cf85 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_calendar.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_calendar.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_chart.xml b/libraries/compound/src/main/res/drawable/ic_compound_chart.xml index cc1c8fb662..c729cf8599 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_chart.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_chart.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_chat.xml b/libraries/compound/src/main/res/drawable/ic_compound_chat.xml index 3a7e70841b..f27c3d9c1e 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_chat.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_chat.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_chat_new.xml b/libraries/compound/src/main/res/drawable/ic_compound_chat_new.xml index 8bf9f6762f..73e84889f8 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_chat_new.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_chat_new.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_chat_problem.xml b/libraries/compound/src/main/res/drawable/ic_compound_chat_problem.xml index 380358478a..ada411cb95 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_chat_problem.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_chat_problem.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_chat_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_chat_solid.xml index d08def35fe..28b82f2f12 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_chat_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_chat_solid.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_check.xml b/libraries/compound/src/main/res/drawable/ic_compound_check.xml index 78f935c940..8a823ed3d7 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_check.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_check.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_check_circle.xml b/libraries/compound/src/main/res/drawable/ic_compound_check_circle.xml index 8d6147246b..6522854364 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_check_circle.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_check_circle.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_check_circle_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_check_circle_solid.xml index d258a5c97a..7c35f577de 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_check_circle_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_check_circle_solid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_chevron_down.xml b/libraries/compound/src/main/res/drawable/ic_compound_chevron_down.xml index 2ac456e988..1adf6f4126 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_chevron_down.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_chevron_down.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_chevron_left.xml b/libraries/compound/src/main/res/drawable/ic_compound_chevron_left.xml index 50316a4e81..8251b035da 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_chevron_left.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_chevron_left.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_chevron_right.xml b/libraries/compound/src/main/res/drawable/ic_compound_chevron_right.xml index d19a9daa4d..6e22300e68 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_chevron_right.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_chevron_right.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_chevron_up.xml b/libraries/compound/src/main/res/drawable/ic_compound_chevron_up.xml index d84ebade9c..063b1cf179 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_chevron_up.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_chevron_up.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_chevron_up_down.xml b/libraries/compound/src/main/res/drawable/ic_compound_chevron_up_down.xml index 213dfbef9f..6add4ed5ab 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_chevron_up_down.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_chevron_up_down.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_circle.xml b/libraries/compound/src/main/res/drawable/ic_compound_circle.xml index d5a292aa3c..ef828f6627 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_circle.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_circle.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_close.xml b/libraries/compound/src/main/res/drawable/ic_compound_close.xml index ef8a75f08a..c655fbd832 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_close.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_close.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_cloud.xml b/libraries/compound/src/main/res/drawable/ic_compound_cloud.xml index b96fd18250..689d7d770e 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_cloud.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_cloud.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_cloud_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_cloud_solid.xml index 09a91809f3..784aef7d10 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_cloud_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_cloud_solid.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_code.xml b/libraries/compound/src/main/res/drawable/ic_compound_code.xml index c17c73c201..08b2c47fcb 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_code.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_code.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_collapse.xml b/libraries/compound/src/main/res/drawable/ic_compound_collapse.xml index 8cc9302197..55f25455b0 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_collapse.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_collapse.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_company.xml b/libraries/compound/src/main/res/drawable/ic_compound_company.xml index 28a9232c13..d8397efc5b 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_company.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_company.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_compose.xml b/libraries/compound/src/main/res/drawable/ic_compound_compose.xml index eaa9ca0ef1..fff7a7550a 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_compose.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_compose.xml @@ -4,11 +4,11 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_computer.xml b/libraries/compound/src/main/res/drawable/ic_compound_computer.xml index 97748e8ffd..e3483f52e8 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_computer.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_computer.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_copy.xml b/libraries/compound/src/main/res/drawable/ic_compound_copy.xml index 4492c5aca9..b163c0c61e 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_copy.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_copy.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_dark_mode.xml b/libraries/compound/src/main/res/drawable/ic_compound_dark_mode.xml index bc4fa89cd4..74a9123108 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_dark_mode.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_dark_mode.xml @@ -4,8 +4,8 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_delete.xml b/libraries/compound/src/main/res/drawable/ic_compound_delete.xml index dd2e3b50d0..e072ce70c5 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_delete.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_delete.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_devices.xml b/libraries/compound/src/main/res/drawable/ic_compound_devices.xml index a399d675cf..e2bb7c1b33 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_devices.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_devices.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_dial_pad.xml b/libraries/compound/src/main/res/drawable/ic_compound_dial_pad.xml index 1967b8df77..a6cbc94255 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_dial_pad.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_dial_pad.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_document.xml b/libraries/compound/src/main/res/drawable/ic_compound_document.xml index 830a36597a..ad96267f8c 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_document.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_document.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_download.xml b/libraries/compound/src/main/res/drawable/ic_compound_download.xml index bf4c24ec26..04fa025ada 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_download.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_download.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_download_ios.xml b/libraries/compound/src/main/res/drawable/ic_compound_download_ios.xml index 16a45ee188..7d17468555 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_download_ios.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_download_ios.xml @@ -3,10 +3,10 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_drag_grid.xml b/libraries/compound/src/main/res/drawable/ic_compound_drag_grid.xml index 96697b574d..6f2825d45c 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_drag_grid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_drag_grid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_drag_list.xml b/libraries/compound/src/main/res/drawable/ic_compound_drag_list.xml index 57fe86c0e8..f1b4a9818a 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_drag_list.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_drag_list.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_earpiece.xml b/libraries/compound/src/main/res/drawable/ic_compound_earpiece.xml index 66b495b304..d16021dfcf 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_earpiece.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_earpiece.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_edit.xml b/libraries/compound/src/main/res/drawable/ic_compound_edit.xml index de0c0b345a..756c3f0347 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_edit.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_edit.xml @@ -4,8 +4,8 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_edit_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_edit_solid.xml index 33da283f4d..15bae108d0 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_edit_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_edit_solid.xml @@ -4,8 +4,8 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_email.xml b/libraries/compound/src/main/res/drawable/ic_compound_email.xml index 223c9620f3..a63a85bc45 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_email.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_email.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_email_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_email_solid.xml index f5c98f8cfa..f69732b0b3 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_email_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_email_solid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_end_call.xml b/libraries/compound/src/main/res/drawable/ic_compound_end_call.xml index f58a645c6e..129af0a6b7 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_end_call.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_end_call.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_error.xml b/libraries/compound/src/main/res/drawable/ic_compound_error.xml index a4ea2f0016..97422a0cbd 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_error.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_error.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_error_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_error_solid.xml index 97b8ece183..1a3f07fa41 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_error_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_error_solid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_exit_full_screen.xml b/libraries/compound/src/main/res/drawable/ic_compound_exit_full_screen.xml new file mode 100644 index 0000000000..2b806f8992 --- /dev/null +++ b/libraries/compound/src/main/res/drawable/ic_compound_exit_full_screen.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_expand.xml b/libraries/compound/src/main/res/drawable/ic_compound_expand.xml index 88aef8abc1..50520c0689 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_expand.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_expand.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_explore.xml b/libraries/compound/src/main/res/drawable/ic_compound_explore.xml index 9b62efa291..51cf8c32be 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_explore.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_explore.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_export_archive.xml b/libraries/compound/src/main/res/drawable/ic_compound_export_archive.xml index f09775d0be..94ed347175 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_export_archive.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_export_archive.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_extensions.xml b/libraries/compound/src/main/res/drawable/ic_compound_extensions.xml index 1bd8ae7bce..d4c6aaa2f0 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_extensions.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_extensions.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_extensions_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_extensions_solid.xml index 7873a49ed6..1b2a265084 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_extensions_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_extensions_solid.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_favourite.xml b/libraries/compound/src/main/res/drawable/ic_compound_favourite.xml index f7845dc080..2d56178045 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_favourite.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_favourite.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_favourite_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_favourite_solid.xml index 3fc393c572..21a77a1f2e 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_favourite_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_favourite_solid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_file_error.xml b/libraries/compound/src/main/res/drawable/ic_compound_file_error.xml index 13b3571ef8..b1c60efae3 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_file_error.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_file_error.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_files.xml b/libraries/compound/src/main/res/drawable/ic_compound_files.xml index 67a30b6bd8..16b71ffbc2 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_files.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_files.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_filter.xml b/libraries/compound/src/main/res/drawable/ic_compound_filter.xml index 2f2b0e5f10..4fcbec8ffe 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_filter.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_filter.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_forward.xml b/libraries/compound/src/main/res/drawable/ic_compound_forward.xml index e614fabb4e..c489c159f8 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_forward.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_forward.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_full_screen.xml b/libraries/compound/src/main/res/drawable/ic_compound_full_screen.xml new file mode 100644 index 0000000000..8042ed32c9 --- /dev/null +++ b/libraries/compound/src/main/res/drawable/ic_compound_full_screen.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_grid.xml b/libraries/compound/src/main/res/drawable/ic_compound_grid.xml index 223ed40a2f..023f9542b4 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_grid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_grid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_group.xml b/libraries/compound/src/main/res/drawable/ic_compound_group.xml index 4781d1306b..800a941134 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_group.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_group.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_guest.xml b/libraries/compound/src/main/res/drawable/ic_compound_guest.xml index 1bb2d99842..cdba0d5f58 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_guest.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_guest.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_headphones_off_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_headphones_off_solid.xml index af81d29c1d..551f0fc21e 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_headphones_off_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_headphones_off_solid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_headphones_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_headphones_solid.xml index eac3a666d0..e8fdf3afb1 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_headphones_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_headphones_solid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_help.xml b/libraries/compound/src/main/res/drawable/ic_compound_help.xml index 293120e8cd..667f268fa8 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_help.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_help.xml @@ -3,10 +3,10 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_help_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_help_solid.xml index 80332718c2..29b11c6687 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_help_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_help_solid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_history.xml b/libraries/compound/src/main/res/drawable/ic_compound_history.xml index 25d4c68509..10e4f222d3 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_history.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_history.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_home.xml b/libraries/compound/src/main/res/drawable/ic_compound_home.xml index 0feb167199..8cdb4fc202 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_home.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_home.xml @@ -3,8 +3,8 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_home_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_home_solid.xml index 10cca67a37..ce94991fdd 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_home_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_home_solid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_host.xml b/libraries/compound/src/main/res/drawable/ic_compound_host.xml index d45d1c0278..048d4c1c3d 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_host.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_host.xml @@ -4,11 +4,11 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_image.xml b/libraries/compound/src/main/res/drawable/ic_compound_image.xml index 58a3525b3a..55728750a1 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_image.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_image.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_image_error.xml b/libraries/compound/src/main/res/drawable/ic_compound_image_error.xml index 1c8d716166..6f660f76a1 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_image_error.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_image_error.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_indent_decrease.xml b/libraries/compound/src/main/res/drawable/ic_compound_indent_decrease.xml index 5ada985743..5fba2cd371 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_indent_decrease.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_indent_decrease.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_indent_increase.xml b/libraries/compound/src/main/res/drawable/ic_compound_indent_increase.xml index de5df3977a..2cad4690fb 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_indent_increase.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_indent_increase.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_info.xml b/libraries/compound/src/main/res/drawable/ic_compound_info.xml index cf0318bb61..02808d1855 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_info.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_info.xml @@ -3,11 +3,11 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_info_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_info_solid.xml index 9101aacffc..7155a382c7 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_info_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_info_solid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_inline_code.xml b/libraries/compound/src/main/res/drawable/ic_compound_inline_code.xml index 9f78e1f8c2..2b047fac71 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_inline_code.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_inline_code.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_italic.xml b/libraries/compound/src/main/res/drawable/ic_compound_italic.xml index e3fee1f82e..e47a0971f6 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_italic.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_italic.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_key.xml b/libraries/compound/src/main/res/drawable/ic_compound_key.xml index 112c16d78f..0bd5d8d7f1 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_key.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_key.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_key_off.xml b/libraries/compound/src/main/res/drawable/ic_compound_key_off.xml index b160e81c73..ce8ed0df0d 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_key_off.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_key_off.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_key_off_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_key_off_solid.xml index c1961ec390..4dbf8c9eb2 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_key_off_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_key_off_solid.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_key_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_key_solid.xml index 29f9ad1a42..79253ea934 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_key_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_key_solid.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_keyboard.xml b/libraries/compound/src/main/res/drawable/ic_compound_keyboard.xml index 6216c1ccf8..19ebbab54c 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_keyboard.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_keyboard.xml @@ -3,11 +3,11 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_labs.xml b/libraries/compound/src/main/res/drawable/ic_compound_labs.xml index d0bcae3bab..9d64b768e0 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_labs.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_labs.xml @@ -3,14 +3,14 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - - + + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_leave.xml b/libraries/compound/src/main/res/drawable/ic_compound_leave.xml index ad5897d4f2..f1c1869dd4 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_leave.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_leave.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_left_panel_close.xml b/libraries/compound/src/main/res/drawable/ic_compound_left_panel_close.xml new file mode 100644 index 0000000000..60e11c6f4f --- /dev/null +++ b/libraries/compound/src/main/res/drawable/ic_compound_left_panel_close.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_left_panel_open.xml b/libraries/compound/src/main/res/drawable/ic_compound_left_panel_open.xml new file mode 100644 index 0000000000..78c21c0625 --- /dev/null +++ b/libraries/compound/src/main/res/drawable/ic_compound_left_panel_open.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_link.xml b/libraries/compound/src/main/res/drawable/ic_compound_link.xml index 3787cf3981..fc3e50450d 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_link.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_link.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_linux.xml b/libraries/compound/src/main/res/drawable/ic_compound_linux.xml index bb3216e729..9b766dac9e 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_linux.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_linux.xml @@ -3,15 +3,17 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - - - - + + + + + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_list_bulleted.xml b/libraries/compound/src/main/res/drawable/ic_compound_list_bulleted.xml index f7fe7a8256..3e50a2b7a1 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_list_bulleted.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_list_bulleted.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_list_numbered.xml b/libraries/compound/src/main/res/drawable/ic_compound_list_numbered.xml index d3fbeb2d2d..a35008936e 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_list_numbered.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_list_numbered.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_list_view.xml b/libraries/compound/src/main/res/drawable/ic_compound_list_view.xml index ffd3359cfa..8af526ae39 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_list_view.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_list_view.xml @@ -3,14 +3,14 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - - + + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_location_navigator.xml b/libraries/compound/src/main/res/drawable/ic_compound_location_navigator.xml index bffa2d9f9d..54d07dc296 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_location_navigator.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_location_navigator.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_location_navigator_centred.xml b/libraries/compound/src/main/res/drawable/ic_compound_location_navigator_centred.xml index 50f8ac655d..b6afaa6620 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_location_navigator_centred.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_location_navigator_centred.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_location_pin.xml b/libraries/compound/src/main/res/drawable/ic_compound_location_pin.xml index 063076d4ae..08101cd217 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_location_pin.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_location_pin.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_location_pin_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_location_pin_solid.xml index defb2d4e42..a03272a0a8 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_location_pin_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_location_pin_solid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_lock.xml b/libraries/compound/src/main/res/drawable/ic_compound_lock.xml index 9715295d94..2225361a2d 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_lock.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_lock.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_lock_off.xml b/libraries/compound/src/main/res/drawable/ic_compound_lock_off.xml index a19bac4811..3f34eb50b8 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_lock_off.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_lock_off.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_lock_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_lock_solid.xml index 6a5a11b4de..d39e85a9a2 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_lock_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_lock_solid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_mac.xml b/libraries/compound/src/main/res/drawable/ic_compound_mac.xml index 6f72038f43..47a4d690ef 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_mac.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_mac.xml @@ -3,8 +3,8 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_mark_as_read.xml b/libraries/compound/src/main/res/drawable/ic_compound_mark_as_read.xml index 8de67ac4e8..7798596fec 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_mark_as_read.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_mark_as_read.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_mark_as_unread.xml b/libraries/compound/src/main/res/drawable/ic_compound_mark_as_unread.xml index 6b3c53144a..0ec3991f85 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_mark_as_unread.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_mark_as_unread.xml @@ -4,11 +4,11 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_mark_threads_as_read.xml b/libraries/compound/src/main/res/drawable/ic_compound_mark_threads_as_read.xml index 5d6c12a557..82bf433d20 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_mark_threads_as_read.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_mark_threads_as_read.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_marker_read_receipts.xml b/libraries/compound/src/main/res/drawable/ic_compound_marker_read_receipts.xml index c70cfd3e51..6205a6a982 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_marker_read_receipts.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_marker_read_receipts.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_mention.xml b/libraries/compound/src/main/res/drawable/ic_compound_mention.xml index 274ccb3775..0ec8a49e99 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_mention.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_mention.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_menu.xml b/libraries/compound/src/main/res/drawable/ic_compound_menu.xml index c3ee1a2ee1..87f961708c 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_menu.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_menu.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_mic_off.xml b/libraries/compound/src/main/res/drawable/ic_compound_mic_off.xml index e28210c07d..8ce70e59b9 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_mic_off.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_mic_off.xml @@ -4,11 +4,11 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_mic_off_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_mic_off_solid.xml index 686809342a..ca3bd707d2 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_mic_off_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_mic_off_solid.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_mic_on.xml b/libraries/compound/src/main/res/drawable/ic_compound_mic_on.xml index 793b9c8f83..2c6cdd5869 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_mic_on.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_mic_on.xml @@ -3,11 +3,11 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_mic_on_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_mic_on_solid.xml index 026f477e75..8b7027cda5 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_mic_on_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_mic_on_solid.xml @@ -3,10 +3,10 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_minus.xml b/libraries/compound/src/main/res/drawable/ic_compound_minus.xml index 064946b6dc..85f71f8692 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_minus.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_minus.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_mobile.xml b/libraries/compound/src/main/res/drawable/ic_compound_mobile.xml index 8257628868..48c11f62c8 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_mobile.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_mobile.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_notifications.xml b/libraries/compound/src/main/res/drawable/ic_compound_notifications.xml index afd16aa8e9..b7af3f06b6 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_notifications.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_notifications.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_notifications_off.xml b/libraries/compound/src/main/res/drawable/ic_compound_notifications_off.xml index e4fca897a3..6542c704eb 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_notifications_off.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_notifications_off.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_notifications_off_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_notifications_off_solid.xml index 4bc9fdd2ab..502f899de3 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_notifications_off_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_notifications_off_solid.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_notifications_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_notifications_solid.xml index 358a18c83e..1e025f929a 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_notifications_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_notifications_solid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_offline.xml b/libraries/compound/src/main/res/drawable/ic_compound_offline.xml index 322954e4b0..66862b5b6c 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_offline.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_offline.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_overflow_horizontal.xml b/libraries/compound/src/main/res/drawable/ic_compound_overflow_horizontal.xml index c85d1e8da2..5de8de32f5 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_overflow_horizontal.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_overflow_horizontal.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_overflow_vertical.xml b/libraries/compound/src/main/res/drawable/ic_compound_overflow_vertical.xml index 269e28613c..09af569897 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_overflow_vertical.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_overflow_vertical.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_pause.xml b/libraries/compound/src/main/res/drawable/ic_compound_pause.xml index bcd8325107..7ccfb0108d 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_pause.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_pause.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_pause_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_pause_solid.xml index f25a7cbcfc..94545f891e 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_pause_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_pause_solid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_pin.xml b/libraries/compound/src/main/res/drawable/ic_compound_pin.xml index 0aa36f53e9..343ca14ab2 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_pin.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_pin.xml @@ -3,8 +3,8 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_pin_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_pin_solid.xml index 9326b0fd75..4f277512d0 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_pin_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_pin_solid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_play.xml b/libraries/compound/src/main/res/drawable/ic_compound_play.xml index d7b8d3c5e5..372cdfed4c 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_play.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_play.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_play_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_play_solid.xml index fca650ccd8..e20b1e5c0e 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_play_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_play_solid.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_plus.xml b/libraries/compound/src/main/res/drawable/ic_compound_plus.xml index a20a59aac9..c4b3d11824 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_plus.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_plus.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_polls.xml b/libraries/compound/src/main/res/drawable/ic_compound_polls.xml index 8c0e2b5a45..0877d2caf8 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_polls.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_polls.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_polls_end.xml b/libraries/compound/src/main/res/drawable/ic_compound_polls_end.xml index 8cfe2be80e..eab0f9af59 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_polls_end.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_polls_end.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_pop_out.xml b/libraries/compound/src/main/res/drawable/ic_compound_pop_out.xml index 7b5b07b969..0feace457d 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_pop_out.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_pop_out.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_preferences.xml b/libraries/compound/src/main/res/drawable/ic_compound_preferences.xml index fbc271730d..d7089e0eda 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_preferences.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_preferences.xml @@ -3,8 +3,8 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_presence_outline_8_x_8.xml b/libraries/compound/src/main/res/drawable/ic_compound_presence_outline_8_x_8.xml index 3d815c0696..603bc8a12c 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_presence_outline_8_x_8.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_presence_outline_8_x_8.xml @@ -3,10 +3,12 @@ android:height="8dp" android:viewportWidth="8" android:viewportHeight="8"> - - - + + + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_presence_solid_8_x_8.xml b/libraries/compound/src/main/res/drawable/ic_compound_presence_solid_8_x_8.xml index ab82df2441..3be8cc0283 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_presence_solid_8_x_8.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_presence_solid_8_x_8.xml @@ -3,9 +3,11 @@ android:height="8dp" android:viewportWidth="8" android:viewportHeight="8"> - - - + + + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_presence_strikethrough_8_x_8.xml b/libraries/compound/src/main/res/drawable/ic_compound_presence_strikethrough_8_x_8.xml index 1ed8a1e492..6bd7114024 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_presence_strikethrough_8_x_8.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_presence_strikethrough_8_x_8.xml @@ -3,10 +3,12 @@ android:height="8dp" android:viewportWidth="8" android:viewportHeight="8"> - - - + + + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_public.xml b/libraries/compound/src/main/res/drawable/ic_compound_public.xml index 3dfd7046ea..7ed4a2244d 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_public.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_public.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_qr_code.xml b/libraries/compound/src/main/res/drawable/ic_compound_qr_code.xml index 4befbcef95..75b3f9f5e7 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_qr_code.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_qr_code.xml @@ -4,14 +4,14 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - - + + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_quote.xml b/libraries/compound/src/main/res/drawable/ic_compound_quote.xml index 728fe07e43..550d7c818a 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_quote.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_quote.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_raised_hand_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_raised_hand_solid.xml index d345569ead..aa16cae569 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_raised_hand_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_raised_hand_solid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_reaction.xml b/libraries/compound/src/main/res/drawable/ic_compound_reaction.xml index f85f57d002..f0dc9f2a7e 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_reaction.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_reaction.xml @@ -3,10 +3,10 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_reaction_add.xml b/libraries/compound/src/main/res/drawable/ic_compound_reaction_add.xml index 3fe88d4f8a..34716dbd59 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_reaction_add.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_reaction_add.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_reaction_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_reaction_solid.xml index 826ac69830..3604031fb3 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_reaction_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_reaction_solid.xml @@ -3,8 +3,8 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_reply.xml b/libraries/compound/src/main/res/drawable/ic_compound_reply.xml index 45a797deda..a4f7a6d368 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_reply.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_reply.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_restart.xml b/libraries/compound/src/main/res/drawable/ic_compound_restart.xml index 9360979845..6f862bd628 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_restart.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_restart.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_room.xml b/libraries/compound/src/main/res/drawable/ic_compound_room.xml index a0a278eab7..3f57015467 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_room.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_room.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_search.xml b/libraries/compound/src/main/res/drawable/ic_compound_search.xml index 8e1ec94374..76638caf49 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_search.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_search.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_send.xml b/libraries/compound/src/main/res/drawable/ic_compound_send.xml index 8c0d5e1159..a37b499cc6 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_send.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_send.xml @@ -4,8 +4,8 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_send_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_send_solid.xml index 3ac0bc6f62..0d41f4fe1a 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_send_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_send_solid.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_settings.xml b/libraries/compound/src/main/res/drawable/ic_compound_settings.xml index 7a79aa33ec..3606bf2317 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_settings.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_settings.xml @@ -3,10 +3,10 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_settings_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_settings_solid.xml index 7a75b037ba..b7f6bb4dc8 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_settings_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_settings_solid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_share.xml b/libraries/compound/src/main/res/drawable/ic_compound_share.xml index 6abf169f20..58bb31bd11 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_share.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_share.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_share_android.xml b/libraries/compound/src/main/res/drawable/ic_compound_share_android.xml index a92b79a76a..3c82973339 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_share_android.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_share_android.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_share_ios.xml b/libraries/compound/src/main/res/drawable/ic_compound_share_ios.xml index e481978c28..9027271541 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_share_ios.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_share_ios.xml @@ -3,10 +3,10 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_share_screen.xml b/libraries/compound/src/main/res/drawable/ic_compound_share_screen.xml index 89f63b897c..10ef39193b 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_share_screen.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_share_screen.xml @@ -3,10 +3,10 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_share_screen_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_share_screen_solid.xml index ee9a1aa837..255e373765 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_share_screen_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_share_screen_solid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_shield.xml b/libraries/compound/src/main/res/drawable/ic_compound_shield.xml index 7d2ee79157..a75afcb947 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_shield.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_shield.xml @@ -3,8 +3,8 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_sidebar.xml b/libraries/compound/src/main/res/drawable/ic_compound_sidebar.xml index 4bd5a0b3e3..1f5b355e49 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_sidebar.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_sidebar.xml @@ -4,8 +4,8 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_sign_out.xml b/libraries/compound/src/main/res/drawable/ic_compound_sign_out.xml index a6ded2b7a8..641cac9a0d 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_sign_out.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_sign_out.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_space.xml b/libraries/compound/src/main/res/drawable/ic_compound_space.xml new file mode 100644 index 0000000000..53a069c640 --- /dev/null +++ b/libraries/compound/src/main/res/drawable/ic_compound_space.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_space_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_space_solid.xml new file mode 100644 index 0000000000..34730e87ff --- /dev/null +++ b/libraries/compound/src/main/res/drawable/ic_compound_space_solid.xml @@ -0,0 +1,9 @@ + + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_spinner.xml b/libraries/compound/src/main/res/drawable/ic_compound_spinner.xml index 80721fdce8..d3577cba4c 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_spinner.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_spinner.xml @@ -4,8 +4,8 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_spotlight.xml b/libraries/compound/src/main/res/drawable/ic_compound_spotlight.xml index acaad53b3b..df9e43f1fe 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_spotlight.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_spotlight.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_spotlight_view.xml b/libraries/compound/src/main/res/drawable/ic_compound_spotlight_view.xml index 724cdf91cf..5a33b46550 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_spotlight_view.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_spotlight_view.xml @@ -3,8 +3,8 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_sticker.xml b/libraries/compound/src/main/res/drawable/ic_compound_sticker.xml new file mode 100644 index 0000000000..c68fd65d7d --- /dev/null +++ b/libraries/compound/src/main/res/drawable/ic_compound_sticker.xml @@ -0,0 +1,10 @@ + + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_strikethrough.xml b/libraries/compound/src/main/res/drawable/ic_compound_strikethrough.xml index a472c6c97e..010baa2a4e 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_strikethrough.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_strikethrough.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_switch_camera_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_switch_camera_solid.xml index a7695e40ff..8d275c6ae3 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_switch_camera_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_switch_camera_solid.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_take_photo.xml b/libraries/compound/src/main/res/drawable/ic_compound_take_photo.xml index d6dbb4d568..fdd8ebd707 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_take_photo.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_take_photo.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_take_photo_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_take_photo_solid.xml index 2db8a6cdf0..bc3b0708cd 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_take_photo_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_take_photo_solid.xml @@ -3,8 +3,8 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_text_formatting.xml b/libraries/compound/src/main/res/drawable/ic_compound_text_formatting.xml index 4126080fa9..c87c152b34 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_text_formatting.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_text_formatting.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_threads.xml b/libraries/compound/src/main/res/drawable/ic_compound_threads.xml index 4fa1e877d1..6d3fd0c853 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_threads.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_threads.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_threads_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_threads_solid.xml index 4db292231d..b06b67c27e 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_threads_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_threads_solid.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_time.xml b/libraries/compound/src/main/res/drawable/ic_compound_time.xml index b29bb29de7..7fc0404c88 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_time.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_time.xml @@ -3,11 +3,11 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_underline.xml b/libraries/compound/src/main/res/drawable/ic_compound_underline.xml index c90d6bc591..3c8e5b7caa 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_underline.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_underline.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_unknown.xml b/libraries/compound/src/main/res/drawable/ic_compound_unknown.xml index 87afe69e02..606e20b9e9 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_unknown.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_unknown.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_unknown_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_unknown_solid.xml index 89b6a89056..5aec9fa483 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_unknown_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_unknown_solid.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_unpin.xml b/libraries/compound/src/main/res/drawable/ic_compound_unpin.xml index c7aee251f4..361c4be028 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_unpin.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_unpin.xml @@ -4,11 +4,11 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_user.xml b/libraries/compound/src/main/res/drawable/ic_compound_user.xml index 948bda3e4e..b0d51f5cfe 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_user.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_user.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_user_add.xml b/libraries/compound/src/main/res/drawable/ic_compound_user_add.xml index 81d9aaf1bb..9965dadcde 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_user_add.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_user_add.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_user_add_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_user_add_solid.xml index 693db72c06..685d751fdb 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_user_add_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_user_add_solid.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_user_profile.xml b/libraries/compound/src/main/res/drawable/ic_compound_user_profile.xml index 222b9510ea..f66ebd04e7 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_user_profile.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_user_profile.xml @@ -3,13 +3,13 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - - + + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_user_profile_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_user_profile_solid.xml index 5d268642e0..54ada8e1ce 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_user_profile_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_user_profile_solid.xml @@ -3,10 +3,10 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_user_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_user_solid.xml index c281d6f009..64409fcf5c 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_user_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_user_solid.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_verified.xml b/libraries/compound/src/main/res/drawable/ic_compound_verified.xml index f30bec93ff..510e386bb9 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_verified.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_verified.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_video_call.xml b/libraries/compound/src/main/res/drawable/ic_compound_video_call.xml index 4f1c9b1ba7..bee275ab25 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_video_call.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_video_call.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_video_call_declined_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_video_call_declined_solid.xml index a6b699c6bd..b9a8090cce 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_video_call_declined_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_video_call_declined_solid.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_video_call_missed_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_video_call_missed_solid.xml index 57c1502560..43133555f7 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_video_call_missed_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_video_call_missed_solid.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_video_call_off.xml b/libraries/compound/src/main/res/drawable/ic_compound_video_call_off.xml index 6466ec2848..ab9cbf55eb 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_video_call_off.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_video_call_off.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_video_call_off_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_video_call_off_solid.xml index 35a5796577..10dd687230 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_video_call_off_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_video_call_off_solid.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_video_call_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_video_call_solid.xml index ec4cf0902e..dc78198098 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_video_call_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_video_call_solid.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_visibility_off.xml b/libraries/compound/src/main/res/drawable/ic_compound_visibility_off.xml index c4234584a3..815184858b 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_visibility_off.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_visibility_off.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_visibility_on.xml b/libraries/compound/src/main/res/drawable/ic_compound_visibility_on.xml index a66dcfa429..59b23e3928 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_visibility_on.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_visibility_on.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_voice_call.xml b/libraries/compound/src/main/res/drawable/ic_compound_voice_call.xml index 579738d57b..258a541751 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_voice_call.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_voice_call.xml @@ -4,10 +4,12 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - - + + + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_voice_call_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_voice_call_solid.xml index 428e2fa3d1..6aed590ced 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_voice_call_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_voice_call_solid.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_volume_off.xml b/libraries/compound/src/main/res/drawable/ic_compound_volume_off.xml index e240c01b0b..884d3c3196 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_volume_off.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_volume_off.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_volume_off_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_volume_off_solid.xml index 6c801e7a16..b4d75ae32a 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_volume_off_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_volume_off_solid.xml @@ -4,7 +4,7 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_volume_on.xml b/libraries/compound/src/main/res/drawable/ic_compound_volume_on.xml index 453a2578e2..bbc3effd39 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_volume_on.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_volume_on.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_volume_on_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_volume_on_solid.xml index 232571342f..2041eaba02 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_volume_on_solid.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_volume_on_solid.xml @@ -4,10 +4,10 @@ android:autoMirrored="true" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_warning.xml b/libraries/compound/src/main/res/drawable/ic_compound_warning.xml index 53b838ca56..3b196aa863 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_warning.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_warning.xml @@ -3,11 +3,11 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_web_browser.xml b/libraries/compound/src/main/res/drawable/ic_compound_web_browser.xml index baaf15c059..173b24addd 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_web_browser.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_web_browser.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_windows.xml b/libraries/compound/src/main/res/drawable/ic_compound_windows.xml index 37a3915dc8..0aa8b49e32 100644 --- a/libraries/compound/src/main/res/drawable/ic_compound_windows.xml +++ b/libraries/compound/src/main/res/drawable/ic_compound_windows.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/libraries/compound/src/main/res/drawable/ic_compound_workspace.xml b/libraries/compound/src/main/res/drawable/ic_compound_workspace.xml deleted file mode 100644 index 3871fde4b4..0000000000 --- a/libraries/compound/src/main/res/drawable/ic_compound_workspace.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/libraries/compound/src/main/res/drawable/ic_compound_workspace_solid.xml b/libraries/compound/src/main/res/drawable/ic_compound_workspace_solid.xml deleted file mode 100644 index 51c5c9f9cb..0000000000 --- a/libraries/compound/src/main/res/drawable/ic_compound_workspace_solid.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/AvatarColorsTest.kt b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/AvatarColorsTest.kt index 015cd5341c..01c3e787cf 100644 --- a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/AvatarColorsTest.kt +++ b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/AvatarColorsTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/CompoundIconTest.kt b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/CompoundIconTest.kt index 9ae73a6205..6e15233013 100644 --- a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/CompoundIconTest.kt +++ b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/CompoundIconTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/CompoundTypographyTest.kt b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/CompoundTypographyTest.kt index 2d50e6287d..c4822f680e 100644 --- a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/CompoundTypographyTest.kt +++ b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/CompoundTypographyTest.kt @@ -1,24 +1,17 @@ /* - * Copyright 2023, 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.compound.screenshot -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 import com.github.takahirom.roborazzi.captureRoboImage +import io.element.android.compound.previews.CompoundTypographyPreview import io.element.android.compound.screenshot.utils.screenshotFile -import io.element.android.compound.theme.ElementTheme -import io.element.android.compound.tokens.generated.TypographyTokens import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config @@ -31,35 +24,7 @@ class CompoundTypographyTest { @Config(sdk = [35], qualifiers = "h2048dp-xxhdpi") fun screenshots() { captureRoboImage(file = screenshotFile("Compound Typography.png")) { - ElementTheme { - Surface { - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - with(TypographyTokens) { - TypographyTokenPreview(fontHeadingXlBold, "Heading XL Bold") - TypographyTokenPreview(fontHeadingXlRegular, "Heading XL Regular") - TypographyTokenPreview(fontHeadingLgBold, "Heading LG Bold") - TypographyTokenPreview(fontHeadingLgRegular, "Heading LG Regular") - TypographyTokenPreview(fontHeadingMdBold, "Heading MD Bold") - TypographyTokenPreview(fontHeadingMdRegular, "Heading MD Regular") - TypographyTokenPreview(fontHeadingSmMedium, "Heading SM Medium") - TypographyTokenPreview(fontHeadingSmRegular, "Heading SM Regular") - TypographyTokenPreview(fontBodyLgMedium, "Body LG Medium") - TypographyTokenPreview(fontBodyLgRegular, "Body LG Regular") - TypographyTokenPreview(fontBodyMdMedium, "Body MD Medium") - TypographyTokenPreview(fontBodyMdRegular, "Body MD Regular") - TypographyTokenPreview(fontBodySmMedium, "Body SM Medium") - TypographyTokenPreview(fontBodySmRegular, "Body SM Regular") - TypographyTokenPreview(fontBodyXsMedium, "Body XS Medium") - TypographyTokenPreview(fontBodyXsRegular, "Body XS Regular") - } - } - } - } + CompoundTypographyPreview() } } - - @Composable - private fun TypographyTokenPreview(style: TextStyle, text: String) { - Text(text = text, style = style) - } } diff --git a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/ForcedDarkElementThemeTest.kt b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/ForcedDarkElementThemeTest.kt index 341b7cb650..74e6f3010d 100644 --- a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/ForcedDarkElementThemeTest.kt +++ b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/ForcedDarkElementThemeTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -19,6 +20,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 import com.github.takahirom.roborazzi.captureRoboImage +import io.element.android.compound.colors.SemanticColorsLightDark import io.element.android.compound.screenshot.utils.screenshotFile import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ForcedDarkElementTheme @@ -42,7 +44,9 @@ class ForcedDarkElementThemeTest { verticalArrangement = Arrangement.spacedBy(10.dp) ) { Text(text = "Outside") - ForcedDarkElementTheme { + ForcedDarkElementTheme( + colors = SemanticColorsLightDark.default, + ) { Surface { Box(modifier = Modifier.fillMaxSize()) { Text(text = "Inside ForcedDarkElementTheme", modifier = Modifier.align(Alignment.Center)) diff --git a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/LegacyColorsTest.kt b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/LegacyColorsTest.kt index ecfbbe81cd..deb27c6519 100644 --- a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/LegacyColorsTest.kt +++ b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/LegacyColorsTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialColorSchemeTest.kt b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialColorSchemeTest.kt index 7542eaa4fc..282ca0e69a 100644 --- a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialColorSchemeTest.kt +++ b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialColorSchemeTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialTextTest.kt b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialTextTest.kt index 764d4de77c..2aaeed43a5 100644 --- a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialTextTest.kt +++ b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialTextTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialTypographyTest.kt b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialTypographyTest.kt index 4f77a070ed..3d8256e2af 100644 --- a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialTypographyTest.kt +++ b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialTypographyTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialYouThemeTest.kt b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialYouThemeTest.kt index 694d2c9fc6..2fe3167199 100644 --- a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialYouThemeTest.kt +++ b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/MaterialYouThemeTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/SemanticColorsTest.kt b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/SemanticColorsTest.kt index 7e5fabd96a..f36d0d2ee5 100644 --- a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/SemanticColorsTest.kt +++ b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/SemanticColorsTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/utils/ScreenshotUtils.kt b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/utils/ScreenshotUtils.kt index 3d4c9b3824..082a9b248a 100644 --- a/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/utils/ScreenshotUtils.kt +++ b/libraries/compound/src/test/kotlin/io/element/android/compound/screenshot/utils/ScreenshotUtils.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2025 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/compound/src/test/kotlin/io/element/android/compound/theme/ThemeTest.kt b/libraries/compound/src/test/kotlin/io/element/android/compound/theme/ThemeTest.kt index 17cb2425dc..8fd7c5f041 100644 --- a/libraries/compound/src/test/kotlin/io/element/android/compound/theme/ThemeTest.kt +++ b/libraries/compound/src/test/kotlin/io/element/android/compound/theme/ThemeTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/build.gradle.kts b/libraries/core/build.gradle.kts index 25e7cd9ba8..d04efaa40e 100644 --- a/libraries/core/build.gradle.kts +++ b/libraries/core/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { @@ -23,6 +24,7 @@ kotlin { dependencies { implementation(libs.coroutines.core) + implementation(libs.coroutines.test) testImplementation(libs.test.junit) testImplementation(libs.test.truth) } diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/bool/Booleans.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/bool/Booleans.kt index 477e3590cd..703bc924b0 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/bool/Booleans.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/bool/Booleans.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/cache/CircularCache.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/cache/CircularCache.kt index 88e7934245..57edd84ab1 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/cache/CircularCache.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/cache/CircularCache.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ChildScopeOf.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ChildScopeOf.kt index db4afe727c..46831a2698 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ChildScopeOf.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ChildScopeOf.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,6 +14,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.job import kotlinx.coroutines.plus +import kotlinx.coroutines.test.TestScope /** * Create a child scope of the current scope. @@ -27,6 +29,11 @@ fun CoroutineScope.childScope( dispatcher: CoroutineDispatcher, name: String, ): CoroutineScope = run { - val supervisorJob = SupervisorJob(parent = coroutineContext.job) - this + dispatcher + supervisorJob + CoroutineName(name) + if (this is TestScope) { + // Special case for tests: we can't start a coroutine with a different SupervisorJob + this + } else { + val supervisorJob = SupervisorJob(parent = coroutineContext.job) + this + dispatcher + supervisorJob + CoroutineName(name) + } } diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/CoroutineDispatchers.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/CoroutineDispatchers.kt index 606fe20158..6b0ceb2293 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/CoroutineDispatchers.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/CoroutineDispatchers.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/DerivedStateFlow.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/DerivedStateFlow.kt index 7f88212a59..b186e6f2c2 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/DerivedStateFlow.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/DerivedStateFlow.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ErrorFlow.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ErrorFlow.kt index b0cd57ca28..5498d52f9d 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ErrorFlow.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ErrorFlow.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/Flow.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/Flow.kt index bb168d8eb7..64dbe7f20a 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/Flow.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/Flow.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ParallelMap.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ParallelMap.kt index eadcbdb564..109d492ad6 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ParallelMap.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ParallelMap.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/Suspend.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/Suspend.kt index 62c75916fd..a2113c6d8c 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/Suspend.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/Suspend.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/SuspendLazy.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/SuspendLazy.kt index e5442c4940..f6068d27f3 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/SuspendLazy.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/SuspendLazy.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/ByteSize.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/ByteSize.kt new file mode 100644 index 0000000000..f4daf85011 --- /dev/null +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/ByteSize.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.core.data + +enum class ByteUnit(val bitShift: Int) { + BYTES(0), + KB(10), + MB(20), + GB(30) +} + +class ByteSize internal constructor(val value: Long, val unit: ByteUnit) { + fun into(dest: ByteUnit): Long { + if (unit == dest) return value + return value shl unit.bitShift shr dest.bitShift + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ByteSize) return false + + return value == other.value && unit == other.unit + } + + override fun hashCode(): Int { + var result = value.hashCode() + result = 31 * result + unit.hashCode() + return result + } + + override fun toString(): String { + return "$value $unit" + } +} + +val Number.gigaBytes get() = ByteSize(toLong(), ByteUnit.GB) +val Number.megaBytes get() = ByteSize(toLong(), ByteUnit.MB) +val Number.kiloBytes get() = ByteSize(toLong(), ByteUnit.KB) +val Number.bytes get() = ByteSize(toLong(), ByteUnit.BYTES) + +// For the SDK values +val ULong.gigaBytes get() = ByteSize(toLong(), ByteUnit.GB) +val ULong.megaBytes get() = ByteSize(toLong(), ByteUnit.MB) +val ULong.kiloBytes get() = ByteSize(toLong(), ByteUnit.KB) +val ULong.bytes get() = ByteSize(toLong(), ByteUnit.BYTES) diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/FilterUpTo.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/FilterUpTo.kt index 6cf8351935..a691c06f3f 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/FilterUpTo.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/FilterUpTo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/Try.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/Try.kt index 0610fa04e4..34c5eb68d9 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/Try.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/Try.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt index abd6e10d1f..d3a2805df1 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BasicExtensions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BuildMeta.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BuildMeta.kt index d616c634bf..1130702c7f 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BuildMeta.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/BuildMeta.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt index e46f37cffb..614dc6d565 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/hash/Hash.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/hash/Hash.kt index 107525a9ee..4a9af95bd1 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/hash/Hash.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/hash/Hash.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -20,7 +21,7 @@ fun String.md5() = try { digest.digest() .joinToString("") { String.format(locale, "%02X", it) } .lowercase(locale) -} catch (exc: Exception) { +} catch (_: Exception) { // Should not happen, but just in case hashCode().toString() } diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/log/logger/LoggerTag.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/log/logger/LoggerTag.kt index 3954da9a66..1c1d5cdfab 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/log/logger/LoggerTag.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/log/logger/LoggerTag.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt index cc53ffea1b..8781c718f6 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildMeta.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildType.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildType.kt index f16cd81321..04b6f7d9fb 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildType.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/meta/BuildType.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt index 357fce2feb..e7962f8bd6 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,6 +14,7 @@ import io.element.android.libraries.core.bool.orFalse @Suppress("ktlint:standard:property-naming") object MimeTypes { const val Any: String = "*/*" + const val Json = "application/json" const val OctetStream = "application/octet-stream" const val Apk = "application/vnd.android.package-archive" const val Pdf = "application/pdf" diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/preview/PreviewUtil.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/preview/PreviewUtil.kt index aa9f5b5bcb..0bb76179fb 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/preview/PreviewUtil.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/preview/PreviewUtil.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/uri/UrlUtils.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/uri/UrlUtils.kt index d3230ec69b..666ee1d7ab 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/uri/UrlUtils.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/uri/UrlUtils.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,7 @@ fun String.isValidUrl(): Boolean { return try { URI(this).toURL() true - } catch (t: Throwable) { + } catch (_: Throwable) { false } } diff --git a/libraries/core/src/test/kotlin/io/element/android/libraries/core/cache/CircularCacheTest.kt b/libraries/core/src/test/kotlin/io/element/android/libraries/core/cache/CircularCacheTest.kt index 8489f464a6..367acd9c1c 100644 --- a/libraries/core/src/test/kotlin/io/element/android/libraries/core/cache/CircularCacheTest.kt +++ b/libraries/core/src/test/kotlin/io/element/android/libraries/core/cache/CircularCacheTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/test/kotlin/io/element/android/libraries/core/data/ByteSizeTest.kt b/libraries/core/src/test/kotlin/io/element/android/libraries/core/data/ByteSizeTest.kt new file mode 100644 index 0000000000..ad0191da0c --- /dev/null +++ b/libraries/core/src/test/kotlin/io/element/android/libraries/core/data/ByteSizeTest.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.core.data + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class ByteSizeTest { + @Test + fun testSizeConversions() { + // Check bytes to other units + val bytes = 10_000_000.bytes + assertThat(bytes.into(ByteUnit.BYTES)).isEqualTo(bytes.value) + assertThat(bytes.into(ByteUnit.KB)).isEqualTo(bytes.value / 1024L) + assertThat(bytes.into(ByteUnit.MB)).isEqualTo(bytes.value / 1024L / 1024L) + assertThat(bytes.into(ByteUnit.GB)).isEqualTo(bytes.value / 1024L / 1024L / 1024L) + + // Now check for values too small to be converted + assertThat(100.bytes.into(ByteUnit.KB)).isEqualTo(0) + assertThat(100.bytes.into(ByteUnit.MB)).isEqualTo(0) + assertThat(100.bytes.into(ByteUnit.GB)).isEqualTo(0) + + // Check for KBs + val kiloBytes = 10_000.kiloBytes + assertThat(kiloBytes.into(ByteUnit.BYTES)).isEqualTo(kiloBytes.value * 1024L) + assertThat(kiloBytes.into(ByteUnit.KB)).isEqualTo(kiloBytes.value) + assertThat(kiloBytes.into(ByteUnit.MB)).isEqualTo(kiloBytes.value / 1024L) + assertThat(kiloBytes.into(ByteUnit.GB)).isEqualTo(kiloBytes.value / 1024L / 1024L) + + // Check for MBs + val megaBytes = 10_000.megaBytes + assertThat(megaBytes.into(ByteUnit.BYTES)).isEqualTo(megaBytes.value * 1024L * 1024L) + assertThat(megaBytes.into(ByteUnit.KB)).isEqualTo(megaBytes.value * 1024L) + assertThat(megaBytes.into(ByteUnit.MB)).isEqualTo(megaBytes.value) + assertThat(megaBytes.into(ByteUnit.GB)).isEqualTo(megaBytes.value / 1024L) + + // Check for GBs + val gigaBytes = 10.gigaBytes + assertThat(gigaBytes.into(ByteUnit.BYTES)).isEqualTo(gigaBytes.value * 1024L * 1024L * 1024L) + assertThat(gigaBytes.into(ByteUnit.KB)).isEqualTo(gigaBytes.value * 1024L * 1024L) + assertThat(gigaBytes.into(ByteUnit.MB)).isEqualTo(gigaBytes.value * 1024L) + assertThat(gigaBytes.into(ByteUnit.GB)).isEqualTo(gigaBytes.value) + } +} diff --git a/libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/BasicExtensionsTest.kt b/libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/BasicExtensionsTest.kt index b454412fb7..0099a67a79 100644 --- a/libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/BasicExtensionsTest.kt +++ b/libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/BasicExtensionsTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/ResultTest.kt b/libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/ResultTest.kt index 2db3fc9385..6c6340e1c5 100644 --- a/libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/ResultTest.kt +++ b/libraries/core/src/test/kotlin/io/element/android/libraries/core/extensions/ResultTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/cryptography/api/build.gradle.kts b/libraries/cryptography/api/build.gradle.kts index 64e7024bc8..9ce26419d8 100644 --- a/libraries/cryptography/api/build.gradle.kts +++ b/libraries/cryptography/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/AESEncryptionSpecs.kt b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/AESEncryptionSpecs.kt index d349fbe824..ea1309fa3d 100644 --- a/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/AESEncryptionSpecs.kt +++ b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/AESEncryptionSpecs.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/EncryptionDecryptionService.kt b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/EncryptionDecryptionService.kt index 2d217eef05..fc882bc54b 100644 --- a/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/EncryptionDecryptionService.kt +++ b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/EncryptionDecryptionService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/EncryptionResult.kt b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/EncryptionResult.kt index ae5e89993e..776cd2ddea 100644 --- a/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/EncryptionResult.kt +++ b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/EncryptionResult.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/SecretKeyRepository.kt b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/SecretKeyRepository.kt index a2312c5236..ba6c10dbe0 100644 --- a/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/SecretKeyRepository.kt +++ b/libraries/cryptography/api/src/main/kotlin/io/element/android/libraries/cryptography/api/SecretKeyRepository.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/cryptography/impl/build.gradle.kts b/libraries/cryptography/impl/build.gradle.kts index 5e130b64e2..454432de6f 100644 --- a/libraries/cryptography/impl/build.gradle.kts +++ b/libraries/cryptography/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionService.kt b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionService.kt index 7b3d57259d..cf5c2c6335 100644 --- a/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionService.kt +++ b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.cryptography.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.cryptography.api.AESEncryptionSpecs import io.element.android.libraries.cryptography.api.EncryptionDecryptionService import io.element.android.libraries.cryptography.api.EncryptionResult @@ -21,7 +21,6 @@ import javax.crypto.spec.GCMParameterSpec * Default implementation of [EncryptionDecryptionService] using AES encryption. */ @ContributesBinding(AppScope::class) -@Inject class AESEncryptionDecryptionService : EncryptionDecryptionService { override fun createEncryptionCipher(key: SecretKey): Cipher { return Cipher.getInstance(AESEncryptionSpecs.CIPHER_TRANSFORMATION).apply { diff --git a/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/CryptographyModule.kt b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/CryptographyModule.kt index 6990796ef3..e9a9eb7b34 100644 --- a/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/CryptographyModule.kt +++ b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/CryptographyModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/KeyStoreSecretKeyRepository.kt b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/KeyStoreSecretKeyRepository.kt index 9e394f9db2..46572ef047 100644 --- a/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/KeyStoreSecretKeyRepository.kt +++ b/libraries/cryptography/impl/src/main/kotlin/io/element/android/libraries/cryptography/impl/KeyStoreSecretKeyRepository.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,7 +13,6 @@ import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.cryptography.api.AESEncryptionSpecs import io.element.android.libraries.cryptography.api.SecretKeyRepository import timber.log.Timber @@ -26,7 +26,6 @@ import javax.crypto.SecretKey * The generated key uses AES algorithm, with a key size of 128 bits, and the GCM block mode. */ @ContributesBinding(AppScope::class) -@Inject class KeyStoreSecretKeyRepository( private val keyStore: KeyStore, ) : SecretKeyRepository { diff --git a/libraries/cryptography/impl/src/test/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionServiceTest.kt b/libraries/cryptography/impl/src/test/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionServiceTest.kt index 0d0553da17..d449409777 100644 --- a/libraries/cryptography/impl/src/test/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionServiceTest.kt +++ b/libraries/cryptography/impl/src/test/kotlin/io/element/android/libraries/cryptography/impl/AESEncryptionDecryptionServiceTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/cryptography/test/build.gradle.kts b/libraries/cryptography/test/build.gradle.kts index 5564c972f9..eaa621d53a 100644 --- a/libraries/cryptography/test/build.gradle.kts +++ b/libraries/cryptography/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,8 +12,8 @@ plugins { android { namespace = "io.element.android.libraries.cryptography.test" - - dependencies { - api(projects.libraries.cryptography.api) - } +} + +dependencies { + api(projects.libraries.cryptography.api) } diff --git a/libraries/cryptography/test/src/main/kotlin/io/element/android/libraries/cryptography/test/SimpleSecretKeyRepository.kt b/libraries/cryptography/test/src/main/kotlin/io/element/android/libraries/cryptography/test/SimpleSecretKeyRepository.kt index f1e53edea9..0e301553ea 100644 --- a/libraries/cryptography/test/src/main/kotlin/io/element/android/libraries/cryptography/test/SimpleSecretKeyRepository.kt +++ b/libraries/cryptography/test/src/main/kotlin/io/element/android/libraries/cryptography/test/SimpleSecretKeyRepository.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/api/build.gradle.kts b/libraries/dateformatter/api/build.gradle.kts index cebb9d4049..33194e769b 100644 --- a/libraries/dateformatter/api/build.gradle.kts +++ b/libraries/dateformatter/api/build.gradle.kts @@ -1,9 +1,10 @@ import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,8 +14,8 @@ plugins { android { namespace = "io.element.android.libraries.dateformatter.api" - - dependencies { - testCommonDependencies(libs) - } +} + +dependencies { + testCommonDependencies(libs) } diff --git a/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DateFormatter.kt b/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DateFormatter.kt index 19efe03822..1f5c88f008 100644 --- a/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DateFormatter.kt +++ b/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DateFormatter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DurationFormatter.kt b/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DurationFormatter.kt index 04c6df1d02..8ecf01c343 100644 --- a/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DurationFormatter.kt +++ b/libraries/dateformatter/api/src/main/kotlin/io/element/android/libraries/dateformatter/api/DurationFormatter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/api/src/test/kotlin/io/element/android/libraries/dateformatter/api/DurationFormatterTest.kt b/libraries/dateformatter/api/src/test/kotlin/io/element/android/libraries/dateformatter/api/DurationFormatterTest.kt index 200422db6c..e3a5f2bde4 100644 --- a/libraries/dateformatter/api/src/test/kotlin/io/element/android/libraries/dateformatter/api/DurationFormatterTest.kt +++ b/libraries/dateformatter/api/src/test/kotlin/io/element/android/libraries/dateformatter/api/DurationFormatterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/impl/build.gradle.kts b/libraries/dateformatter/impl/build.gradle.kts index 72da2f81f6..c9ddf459d6 100644 --- a/libraries/dateformatter/impl/build.gradle.kts +++ b/libraries/dateformatter/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -30,19 +31,19 @@ android { ) } } - - dependencies { - implementation(projects.libraries.core) - implementation(projects.libraries.designsystem) - implementation(projects.libraries.di) - implementation(projects.libraries.uiStrings) - implementation(projects.services.toolbox.api) - - api(projects.libraries.dateformatter.api) - api(libs.datetime) - - testCommonDependencies(libs, true) - testImplementation(projects.libraries.dateformatter.test) - testImplementation(projects.services.toolbox.test) - } +} + +dependencies { + implementation(projects.libraries.core) + implementation(projects.libraries.designsystem) + implementation(projects.libraries.di) + implementation(projects.libraries.uiStrings) + implementation(projects.services.toolbox.api) + + api(projects.libraries.dateformatter.api) + api(libs.datetime) + + testCommonDependencies(libs, true) + testImplementation(projects.libraries.dateformatter.test) + testImplementation(projects.services.toolbox.test) } diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterDay.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterDay.kt index 8a9ce96463..39d2ba84c6 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterDay.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterDay.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.dateformatter.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.extensions.safeCapitalize interface DateFormatterDay { @@ -20,7 +20,6 @@ interface DateFormatterDay { } @ContributesBinding(AppScope::class) -@Inject class DefaultDateFormatterDay( private val localDateTimeProvider: LocalDateTimeProvider, private val dateFormatters: DateFormatters, diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterFull.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterFull.kt index 38ff91fdc7..11a1532468 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterFull.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterFull.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterMonth.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterMonth.kt index a6002744c4..f39164860c 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterMonth.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterMonth.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterTime.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterTime.kt index de46c1f5c9..eadf0e0e14 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterTime.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterTime.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterTimeOnly.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterTimeOnly.kt index b9e3091a32..2889235fec 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterTimeOnly.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatterTimeOnly.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt index ceac6777ba..cf6b302164 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateFormatters.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateTimeFormatters.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateTimeFormatters.kt index 60a4e7da8f..0a7e683502 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateTimeFormatters.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DateTimeFormatters.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatter.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatter.kt index cd1180caa1..8b2bcc5629 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatter.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,12 +10,10 @@ package io.element.android.libraries.dateformatter.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.dateformatter.api.DateFormatter import io.element.android.libraries.dateformatter.api.DateFormatterMode @ContributesBinding(AppScope::class) -@Inject class DefaultDateFormatter( private val dateFormatterFull: DateFormatterFull, private val dateFormatterMonth: DateFormatterMonth, diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/LocalDateTimeProvider.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/LocalDateTimeProvider.kt index a246ce2293..a205c844fb 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/LocalDateTimeProvider.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/LocalDateTimeProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/LocaleChangeObserver.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/LocaleChangeObserver.kt index 773871d153..bb2b1e016e 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/LocaleChangeObserver.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/LocaleChangeObserver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,7 +15,6 @@ import android.content.IntentFilter import android.os.Build import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.di.annotations.ApplicationContext @@ -28,7 +28,6 @@ interface LocaleChangeListener { @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DefaultLocaleChangeObserver( @ApplicationContext private val context: Context, ) : LocaleChangeObserver { diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/TimezoneProvider.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/TimezoneProvider.kt index 9397710580..77eba1a2d7 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/TimezoneProvider.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/TimezoneProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/di/DateFormatterModule.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/di/DateFormatterModule.kt index ac36cd3caf..c80324c149 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/di/DateFormatterModule.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/di/DateFormatterModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateForPreview.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateForPreview.kt index 5098f8175f..79fe3acc4a 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateForPreview.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateForPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeProvider.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeProvider.kt index eebe7da5b6..48856cad7b 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeProvider.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeViewPreview.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeViewPreview.kt index 79b1022c14..e3cd9563a8 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeViewPreview.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/DateFormatterModeViewPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/Factory.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/Factory.kt index d0c25f8b60..6028a04794 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/Factory.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/Factory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewClock.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewClock.kt index 8151e07262..feecf105ca 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewClock.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewClock.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewStringProvider.kt b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewStringProvider.kt index f2bb2ff518..3cd004f8a8 100644 --- a/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewStringProvider.kt +++ b/libraries/dateformatter/impl/src/main/kotlin/io/element/android/libraries/dateformatter/impl/previews/PreviewStringProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/impl/src/main/res/values-hr/translations.xml b/libraries/dateformatter/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..f70800fcd0 --- /dev/null +++ b/libraries/dateformatter/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,5 @@ + + + "%1$s u %2$s" + "Ovaj mjesec" + diff --git a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterFrTest.kt b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterFrTest.kt index 0e52e1de37..b4800729d5 100644 --- a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterFrTest.kt +++ b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterFrTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterTest.kt b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterTest.kt index 136bbc1293..94b6fe983e 100644 --- a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterTest.kt +++ b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/DefaultDateFormatterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt index 20f36a0dd6..0ec6377c40 100644 --- a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt +++ b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/Factory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/FakeClock.kt b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/FakeClock.kt index c3d1f01d4f..d2305e6f1f 100644 --- a/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/FakeClock.kt +++ b/libraries/dateformatter/impl/src/test/kotlin/io/element/android/libraries/dateformatter/impl/FakeClock.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/dateformatter/test/build.gradle.kts b/libraries/dateformatter/test/build.gradle.kts index 6f3877ea80..0367030ae1 100644 --- a/libraries/dateformatter/test/build.gradle.kts +++ b/libraries/dateformatter/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,9 +12,9 @@ plugins { android { namespace = "io.element.android.libraries.dateformatter.test" - - dependencies { - api(projects.libraries.dateformatter.api) - api(libs.datetime) - } +} + +dependencies { + api(projects.libraries.dateformatter.api) + api(libs.datetime) } diff --git a/libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeDateFormatter.kt b/libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeDateFormatter.kt index 1a918222dd..617fa76dd0 100644 --- a/libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeDateFormatter.kt +++ b/libraries/dateformatter/test/src/main/kotlin/io/element/android/libraries/dateformatter/test/FakeDateFormatter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/deeplink/api/build.gradle.kts b/libraries/deeplink/api/build.gradle.kts index 01d568df52..dded8dc8b8 100644 --- a/libraries/deeplink/api/build.gradle.kts +++ b/libraries/deeplink/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/deeplink/api/src/main/kotlin/io/element/android/libraries/deeplink/api/DeepLinkCreator.kt b/libraries/deeplink/api/src/main/kotlin/io/element/android/libraries/deeplink/api/DeepLinkCreator.kt index 2a29d70bd4..2b46b15e46 100644 --- a/libraries/deeplink/api/src/main/kotlin/io/element/android/libraries/deeplink/api/DeepLinkCreator.kt +++ b/libraries/deeplink/api/src/main/kotlin/io/element/android/libraries/deeplink/api/DeepLinkCreator.kt @@ -1,16 +1,18 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.deeplink.api +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 fun interface DeepLinkCreator { - fun create(sessionId: SessionId, roomId: RoomId?, threadId: ThreadId?): String + fun create(sessionId: SessionId, roomId: RoomId?, threadId: ThreadId?, eventId: EventId?): String } diff --git a/libraries/deeplink/api/src/main/kotlin/io/element/android/libraries/deeplink/api/DeeplinkData.kt b/libraries/deeplink/api/src/main/kotlin/io/element/android/libraries/deeplink/api/DeeplinkData.kt index d15652b3ee..aac1a7ded1 100644 --- a/libraries/deeplink/api/src/main/kotlin/io/element/android/libraries/deeplink/api/DeeplinkData.kt +++ b/libraries/deeplink/api/src/main/kotlin/io/element/android/libraries/deeplink/api/DeeplinkData.kt @@ -1,12 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.deeplink.api +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 @@ -18,6 +20,6 @@ sealed interface DeeplinkData { /** The target is the root of the app, with the given [sessionId]. */ data class Root(override val sessionId: SessionId) : DeeplinkData - /** The target is a room, with the given [sessionId], [roomId] and optionally a [threadId]. */ - data class Room(override val sessionId: SessionId, val roomId: RoomId, val threadId: ThreadId?) : DeeplinkData + /** The target is a room, with the given [sessionId], [roomId] and optionally a [threadId] and [eventId]. */ + data class Room(override val sessionId: SessionId, val roomId: RoomId, val threadId: ThreadId?, val eventId: EventId?) : DeeplinkData } diff --git a/libraries/deeplink/api/src/main/kotlin/io/element/android/libraries/deeplink/api/DeeplinkParser.kt b/libraries/deeplink/api/src/main/kotlin/io/element/android/libraries/deeplink/api/DeeplinkParser.kt index d101bbc2ec..f0f8640a2d 100644 --- a/libraries/deeplink/api/src/main/kotlin/io/element/android/libraries/deeplink/api/DeeplinkParser.kt +++ b/libraries/deeplink/api/src/main/kotlin/io/element/android/libraries/deeplink/api/DeeplinkParser.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/deeplink/api/src/main/kotlin/io/element/android/libraries/deeplink/api/usecase/InviteFriendsUseCase.kt b/libraries/deeplink/api/src/main/kotlin/io/element/android/libraries/deeplink/api/usecase/InviteFriendsUseCase.kt index 7c8910e156..64287f918c 100644 --- a/libraries/deeplink/api/src/main/kotlin/io/element/android/libraries/deeplink/api/usecase/InviteFriendsUseCase.kt +++ b/libraries/deeplink/api/src/main/kotlin/io/element/android/libraries/deeplink/api/usecase/InviteFriendsUseCase.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/deeplink/impl/build.gradle.kts b/libraries/deeplink/impl/build.gradle.kts index 074e144b3a..e07df72d4e 100644 --- a/libraries/deeplink/impl/build.gradle.kts +++ b/libraries/deeplink/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/deeplink/impl/src/main/kotlin/io/element/android/libraries/deeplink/impl/Constants.kt b/libraries/deeplink/impl/src/main/kotlin/io/element/android/libraries/deeplink/impl/Constants.kt index 1cd98a70d3..5bea982f43 100644 --- a/libraries/deeplink/impl/src/main/kotlin/io/element/android/libraries/deeplink/impl/Constants.kt +++ b/libraries/deeplink/impl/src/main/kotlin/io/element/android/libraries/deeplink/impl/Constants.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/deeplink/impl/src/main/kotlin/io/element/android/libraries/deeplink/impl/DefaultDeepLinkCreator.kt b/libraries/deeplink/impl/src/main/kotlin/io/element/android/libraries/deeplink/impl/DefaultDeepLinkCreator.kt index 7c36ecd0b1..95b7ccf116 100644 --- a/libraries/deeplink/impl/src/main/kotlin/io/element/android/libraries/deeplink/impl/DefaultDeepLinkCreator.kt +++ b/libraries/deeplink/impl/src/main/kotlin/io/element/android/libraries/deeplink/impl/DefaultDeepLinkCreator.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,27 +10,32 @@ package io.element.android.libraries.deeplink.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject +import io.element.android.libraries.androidutils.text.urlEncoded import io.element.android.libraries.deeplink.api.DeepLinkCreator +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 @ContributesBinding(AppScope::class) -@Inject class DefaultDeepLinkCreator : DeepLinkCreator { - override fun create(sessionId: SessionId, roomId: RoomId?, threadId: ThreadId?): String { + override fun create(sessionId: SessionId, roomId: RoomId?, threadId: ThreadId?, eventId: EventId?): String { return buildString { append("$SCHEME://$HOST/") - append(sessionId.value) - if (roomId != null) { - append("/") - append(roomId.value) - if (threadId != null) { - append("/") - append(threadId.value) - } - } + append(sessionId.value.urlEncoded()) + append("/") + append(roomId?.value?.urlEncoded().orEmpty()) + append("/") + append(threadId?.value?.urlEncoded().orEmpty()) + append("/") + append(eventId?.value?.urlEncoded().orEmpty()) } + // Remove all possible trailing '/' characters: + // No event id + .removeSuffix("/") + // No thread id + .removeSuffix("/") + // No room id + .removeSuffix("/") } } diff --git a/libraries/deeplink/impl/src/main/kotlin/io/element/android/libraries/deeplink/impl/DefaultDeeplinkParser.kt b/libraries/deeplink/impl/src/main/kotlin/io/element/android/libraries/deeplink/impl/DefaultDeeplinkParser.kt index e6ab87b2b3..8c865b6557 100644 --- a/libraries/deeplink/impl/src/main/kotlin/io/element/android/libraries/deeplink/impl/DefaultDeeplinkParser.kt +++ b/libraries/deeplink/impl/src/main/kotlin/io/element/android/libraries/deeplink/impl/DefaultDeeplinkParser.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,15 +12,15 @@ import android.content.Intent import android.net.Uri import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject +import io.element.android.libraries.androidutils.text.urlDecoded import io.element.android.libraries.deeplink.api.DeeplinkData import io.element.android.libraries.deeplink.api.DeeplinkParser +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 @ContributesBinding(AppScope::class) -@Inject class DefaultDeeplinkParser : DeeplinkParser { override fun getFromIntent(intent: Intent): DeeplinkData? { return intent @@ -31,15 +32,16 @@ class DefaultDeeplinkParser : DeeplinkParser { private fun Uri.toDeeplinkData(): DeeplinkData? { if (scheme != SCHEME) return null if (host != HOST) return null - val pathBits = path.orEmpty().split("/").drop(1) + val pathBits = encodedPath.orEmpty().split("/").drop(1).map { it.urlDecoded() } val sessionId = pathBits.elementAtOrNull(0)?.let(::SessionId) ?: return null return when (val screenPathComponent = pathBits.elementAtOrNull(1)) { null -> DeeplinkData.Root(sessionId) else -> { val roomId = screenPathComponent.let(::RoomId) - val threadId = pathBits.elementAtOrNull(2)?.let(::ThreadId) - DeeplinkData.Room(sessionId, roomId, threadId) + val threadId = pathBits.elementAtOrNull(2)?.takeIf { it.isNotBlank() }?.let(::ThreadId) + val eventId = pathBits.elementAtOrNull(3)?.takeIf { it.isNotBlank() }?.let(::EventId) + DeeplinkData.Room(sessionId, roomId, threadId, eventId) } } } diff --git a/libraries/deeplink/impl/src/main/kotlin/io/element/android/libraries/deeplink/impl/usecase/DefaultInviteFriendsUseCase.kt b/libraries/deeplink/impl/src/main/kotlin/io/element/android/libraries/deeplink/impl/usecase/DefaultInviteFriendsUseCase.kt index 045b199237..d3ba7e75fe 100644 --- a/libraries/deeplink/impl/src/main/kotlin/io/element/android/libraries/deeplink/impl/usecase/DefaultInviteFriendsUseCase.kt +++ b/libraries/deeplink/impl/src/main/kotlin/io/element/android/libraries/deeplink/impl/usecase/DefaultInviteFriendsUseCase.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.deeplink.impl.usecase import android.app.Activity import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.androidutils.system.startSharePlainTextIntent import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.deeplink.api.usecase.InviteFriendsUseCase @@ -22,7 +22,6 @@ import timber.log.Timber import io.element.android.libraries.androidutils.R as AndroidUtilsR @ContributesBinding(SessionScope::class) -@Inject class DefaultInviteFriendsUseCase( private val stringProvider: StringProvider, private val matrixClient: MatrixClient, diff --git a/libraries/deeplink/impl/src/test/kotlin/io/element/android/libraries/deeplink/impl/DefaultDeepLinkCreatorTest.kt b/libraries/deeplink/impl/src/test/kotlin/io/element/android/libraries/deeplink/impl/DefaultDeepLinkCreatorTest.kt index a5c943c525..b27a645802 100644 --- a/libraries/deeplink/impl/src/test/kotlin/io/element/android/libraries/deeplink/impl/DefaultDeepLinkCreatorTest.kt +++ b/libraries/deeplink/impl/src/test/kotlin/io/element/android/libraries/deeplink/impl/DefaultDeepLinkCreatorTest.kt @@ -1,13 +1,19 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.deeplink.impl import com.google.common.truth.Truth.assertThat +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.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_THREAD_ID @@ -17,11 +23,36 @@ class DefaultDeepLinkCreatorTest { @Test fun create() { val sut = DefaultDeepLinkCreator() - assertThat(sut.create(A_SESSION_ID, null, null)) - .isEqualTo("elementx://open/@alice:server.org") - assertThat(sut.create(A_SESSION_ID, A_ROOM_ID, null)) - .isEqualTo("elementx://open/@alice:server.org/!aRoomId:domain") - assertThat(sut.create(A_SESSION_ID, A_ROOM_ID, A_THREAD_ID)) - .isEqualTo("elementx://open/@alice:server.org/!aRoomId:domain/\$aThreadId") + val sessionId = A_SESSION_ID + val roomId = A_ROOM_ID + val threadId = A_THREAD_ID + val eventId = AN_EVENT_ID + assertThat(sut.create(sessionId, null, null, null)) + .isEqualTo("elementx://open/%40alice%3Aserver.org") + assertThat(sut.create(sessionId, roomId, null, null)) + .isEqualTo("elementx://open/%40alice%3Aserver.org/%21aRoomId%3Adomain") + assertThat(sut.create(sessionId, roomId, threadId, null)) + .isEqualTo("elementx://open/%40alice%3Aserver.org/%21aRoomId%3Adomain/%24aThreadId") + assertThat(sut.create(sessionId, roomId, threadId, eventId)) + .isEqualTo("elementx://open/%40alice%3Aserver.org/%21aRoomId%3Adomain/%24aThreadId/%24anEventId") + assertThat(sut.create(sessionId, roomId, null, eventId)) + .isEqualTo("elementx://open/%40alice%3Aserver.org/%21aRoomId%3Adomain//%24anEventId") + } + + @Test + fun `create - with escaped invalid characters`() { + val sut = DefaultDeepLinkCreator() + val sessionId = SessionId("@a/:domain") + val roomId = RoomId("!a/RoomId:domain") + val threadId = ThreadId("\$a/ThreadId") + val eventId = EventId("\$an/EventId") + assertThat(sut.create(sessionId, roomId, null, null)) + .isEqualTo("elementx://open/%40a%2F%3Adomain/%21a%2FRoomId%3Adomain") + assertThat(sut.create(sessionId, roomId, threadId, null)) + .isEqualTo("elementx://open/%40a%2F%3Adomain/%21a%2FRoomId%3Adomain/%24a%2FThreadId") + assertThat(sut.create(sessionId, roomId, threadId, eventId)) + .isEqualTo("elementx://open/%40a%2F%3Adomain/%21a%2FRoomId%3Adomain/%24a%2FThreadId/%24an%2FEventId") + assertThat(sut.create(sessionId, roomId, null, eventId)) + .isEqualTo("elementx://open/%40a%2F%3Adomain/%21a%2FRoomId%3Adomain//%24an%2FEventId") } } diff --git a/libraries/deeplink/impl/src/test/kotlin/io/element/android/libraries/deeplink/impl/DefaultDeeplinkParserTest.kt b/libraries/deeplink/impl/src/test/kotlin/io/element/android/libraries/deeplink/impl/DefaultDeeplinkParserTest.kt index 787c721092..7f563ddd7f 100644 --- a/libraries/deeplink/impl/src/test/kotlin/io/element/android/libraries/deeplink/impl/DefaultDeeplinkParserTest.kt +++ b/libraries/deeplink/impl/src/test/kotlin/io/element/android/libraries/deeplink/impl/DefaultDeeplinkParserTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,6 +12,11 @@ import android.content.Intent import androidx.core.net.toUri import com.google.common.truth.Truth.assertThat import io.element.android.libraries.deeplink.api.DeeplinkData +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.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_THREAD_ID @@ -28,6 +34,12 @@ class DefaultDeeplinkParserTest { "elementx://open/@alice:server.org/!aRoomId:domain" const val A_URI_WITH_ROOM_WITH_THREAD = "elementx://open/@alice:server.org/!aRoomId:domain/\$aThreadId" + const val A_URI_WITH_ROOM_WITH_THREAD_AND_EVENT = + "elementx://open/@alice:server.org/!aRoomId:domain/\$aThreadId/\$anEventId" + const val A_URI_WITH_ROOM_WITH_EVENT_AND_NO_THREAD = + "elementx://open/@alice:server.org/!aRoomId:domain//\$anEventId" + const val A_URI_WITH_ROOM_WITH_THREAD_AND_EVENT_AND_INVALID_CHARACTERS = + "elementx://open/@a%2Flice:server.org/!a%2FRoomId:domain/\$a%2FThreadId/\$an%2FEventId" } @Test @@ -36,9 +48,22 @@ class DefaultDeeplinkParserTest { assertThat(sut.getFromIntent(createIntent(A_URI))) .isEqualTo(DeeplinkData.Root(A_SESSION_ID)) assertThat(sut.getFromIntent(createIntent(A_URI_WITH_ROOM))) - .isEqualTo(DeeplinkData.Room(A_SESSION_ID, A_ROOM_ID, null)) + .isEqualTo(DeeplinkData.Room(A_SESSION_ID, A_ROOM_ID, null, null)) assertThat(sut.getFromIntent(createIntent(A_URI_WITH_ROOM_WITH_THREAD))) - .isEqualTo(DeeplinkData.Room(A_SESSION_ID, A_ROOM_ID, A_THREAD_ID)) + .isEqualTo(DeeplinkData.Room(A_SESSION_ID, A_ROOM_ID, A_THREAD_ID, null)) + assertThat(sut.getFromIntent(createIntent(A_URI_WITH_ROOM_WITH_THREAD_AND_EVENT))) + .isEqualTo(DeeplinkData.Room(A_SESSION_ID, A_ROOM_ID, A_THREAD_ID, AN_EVENT_ID)) + assertThat(sut.getFromIntent(createIntent(A_URI_WITH_ROOM_WITH_EVENT_AND_NO_THREAD))) + .isEqualTo(DeeplinkData.Room(A_SESSION_ID, A_ROOM_ID, null, AN_EVENT_ID)) + assertThat(sut.getFromIntent(createIntent(A_URI_WITH_ROOM_WITH_THREAD_AND_EVENT_AND_INVALID_CHARACTERS))) + .isEqualTo( + DeeplinkData.Room( + sessionId = SessionId("@a/lice:server.org"), + roomId = RoomId("!a/RoomId:domain"), + threadId = ThreadId("\$a/ThreadId"), + eventId = EventId("\$an/EventId"), + ) + ) } @Test diff --git a/libraries/designsystem/build.gradle.kts b/libraries/designsystem/build.gradle.kts index 3983317055..bdb9a32e89 100644 --- a/libraries/designsystem/build.gradle.kts +++ b/libraries/designsystem/build.gradle.kts @@ -1,9 +1,10 @@ import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -25,25 +26,24 @@ android { consumerProguardFiles("consumer-rules.pro") } } - - dependencies { - api(projects.libraries.compound) - - implementation(libs.androidx.compose.material3.windowsizeclass) - implementation(libs.androidx.compose.material3.adaptive) - implementation(libs.coil.compose) - implementation(libs.vanniktech.blurhash) - implementation(projects.features.enterprise.api) - implementation(projects.libraries.androidutils) - implementation(projects.libraries.architecture) - implementation(projects.libraries.core) - implementation(projects.libraries.preferences.api) - implementation(projects.libraries.testtags) - implementation(projects.libraries.uiStrings) - - ksp(libs.showkase.processor) - implementation(libs.showkase) - - testCommonDependencies(libs) - } +} + +dependencies { + api(projects.libraries.compound) + + implementation(libs.androidx.compose.material3.windowsizeclass) + implementation(libs.androidx.compose.material3.adaptive) + implementation(libs.coil.compose) + implementation(libs.vanniktech.blurhash) + implementation(projects.libraries.androidutils) + implementation(projects.libraries.architecture) + implementation(projects.libraries.core) + implementation(projects.libraries.preferences.api) + implementation(projects.libraries.testtags) + implementation(projects.libraries.uiStrings) + + ksp(libs.showkase.processor) + implementation(libs.showkase) + + testCommonDependencies(libs) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ColorUtil.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ColorUtil.kt index debcd57c67..925d66eef2 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ColorUtil.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ColorUtil.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/animation/AlphaAnimation.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/animation/AlphaAnimation.kt index 7b02d48eb3..1da74b30ae 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/animation/AlphaAnimation.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/animation/AlphaAnimation.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/BetaLabel.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/BetaLabel.kt index 2600b83561..9663c18991 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/BetaLabel.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/BetaLabel.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/CounterAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/CounterAtom.kt index 483c9aa346..a051fd4c80 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/CounterAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/CounterAtom.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 5eb099bcd4..85cf642d8f 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/LoadingButtonAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/LoadingButtonAtom.kt new file mode 100644 index 0000000000..666d48d457 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/LoadingButtonAtom.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.designsystem.atomic.atoms + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun LoadingButtonAtom( + modifier: Modifier = Modifier, +) = Button( + modifier = modifier.fillMaxWidth(), + enabled = false, + showProgress = true, + text = stringResource(CommonStrings.common_loading), + onClick = {}, +) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/MatrixBadgeAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/MatrixBadgeAtom.kt index 227e2f27cc..c96fb630e9 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/MatrixBadgeAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/MatrixBadgeAtom.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/PlaceholderAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/PlaceholderAtom.kt index d083943469..4d5a9b8cae 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/PlaceholderAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/PlaceholderAtom.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RedIndicatorAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RedIndicatorAtom.kt index 354eada12a..99100fd298 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RedIndicatorAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RedIndicatorAtom.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewDescriptionAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewDescriptionAtom.kt index 3aece9889e..e4e250f9a6 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewDescriptionAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewDescriptionAtom.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewSubtitleAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewSubtitleAtom.kt index 4fe52f5443..b076bb0e24 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewSubtitleAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewSubtitleAtom.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewTitleAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewTitleAtom.kt index 2c77dc8461..740605fe88 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewTitleAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoomPreviewTitleAtom.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt index 71dbd93ec5..739300220f 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/RoundedIconAtom.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/SelectedIndicatorAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/SelectedIndicatorAtom.kt index 4f30ac6d9b..b2e25af0fb 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/SelectedIndicatorAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/SelectedIndicatorAtom.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/UnreadIndicatorAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/UnreadIndicatorAtom.kt index c9aa5901ec..d2db3aec8e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/UnreadIndicatorAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/UnreadIndicatorAtom.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ButtonColumnMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ButtonColumnMolecule.kt index 2b9a34fadb..eb03eff17b 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ButtonColumnMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ButtonColumnMolecule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ButtonRowMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ButtonRowMolecule.kt index af45356088..502f921365 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ButtonRowMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ButtonRowMolecule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ComposerAlertMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ComposerAlertMolecule.kt index 07e2454dbc..72994fec44 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ComposerAlertMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ComposerAlertMolecule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -24,18 +25,17 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarData -import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarType -import io.element.android.libraries.designsystem.components.avatar.anAvatarData import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.text.toAnnotatedString import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.ButtonSize +import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.designsystem.utils.BooleanProvider import io.element.android.libraries.ui.strings.CommonStrings @Composable @@ -44,20 +44,37 @@ fun ComposerAlertMolecule( content: AnnotatedString, onSubmitClick: () -> Unit, modifier: Modifier = Modifier, - isCritical: Boolean = false, + level: ComposerAlertLevel = ComposerAlertLevel.Default, + showIcon: Boolean = false, submitText: String = stringResource(CommonStrings.action_ok), ) { Column( modifier.fillMaxWidth() ) { - val lineColor = if (isCritical) ElementTheme.colors.borderCriticalSubtle else ElementTheme.colors.borderInfoSubtle + val lineColor = when (level) { + ComposerAlertLevel.Default -> ElementTheme.colors.borderInfoSubtle + ComposerAlertLevel.Info -> ElementTheme.colors.borderInfoSubtle + ComposerAlertLevel.Critical -> ElementTheme.colors.borderCriticalSubtle + } + + val startColor = when (level) { + ComposerAlertLevel.Default -> ElementTheme.colors.bgInfoSubtle + ComposerAlertLevel.Info -> ElementTheme.colors.bgInfoSubtle + ComposerAlertLevel.Critical -> ElementTheme.colors.bgCriticalSubtle + } + + val textColor = when (level) { + ComposerAlertLevel.Default -> ElementTheme.colors.textPrimary + ComposerAlertLevel.Info -> ElementTheme.colors.textInfoPrimary + ComposerAlertLevel.Critical -> ElementTheme.colors.textCriticalPrimary + } + Box( modifier = Modifier .fillMaxWidth() .height(1.dp) .background(lineColor) ) - val startColor = if (isCritical) ElementTheme.colors.bgCriticalSubtle else ElementTheme.colors.bgInfoSubtle val brush = Brush.verticalGradient( listOf(startColor, ElementTheme.colors.bgCanvasDefault), ) @@ -77,16 +94,28 @@ fun ComposerAlertMolecule( avatarData = avatar, avatarType = AvatarType.User, ) + } else if (showIcon) { + val icon = when (level) { + ComposerAlertLevel.Default -> CompoundIcons.Info() + ComposerAlertLevel.Info -> CompoundIcons.Info() + ComposerAlertLevel.Critical -> CompoundIcons.Error() + } + val iconTint = when (level) { + ComposerAlertLevel.Default -> ElementTheme.colors.iconPrimary + ComposerAlertLevel.Info -> ElementTheme.colors.iconInfoPrimary + ComposerAlertLevel.Critical -> ElementTheme.colors.iconCriticalPrimary + } + Icon( + imageVector = icon, + tint = iconTint, + contentDescription = null, + ) } Text( text = content, modifier = Modifier.weight(1f), style = ElementTheme.typography.fontBodyMdRegular, - color = if (isCritical) { - ElementTheme.colors.textCriticalPrimary - } else { - ElementTheme.colors.textPrimary - }, + color = textColor, textAlign = TextAlign.Start, ) } @@ -101,13 +130,22 @@ fun ComposerAlertMolecule( } } +enum class ComposerAlertLevel { + Default, + Info, + Critical +} + @PreviewsDayNight @Composable -internal fun ComposerAlertMoleculePreview(@PreviewParameter(BooleanProvider::class) isCritical: Boolean) = ElementPreview { +internal fun ComposerAlertMoleculePreview( + @PreviewParameter(ComposerAlertMoleculeParamsProvider::class) params: ComposerAlertMoleculeParams, +) = ElementPreview { ComposerAlertMolecule( - avatar = anAvatarData(size = AvatarSize.ComposerAlert), + avatar = params.avatar, content = "Alice’s verified identity has changed. Learn more".toAnnotatedString(), - isCritical = isCritical, + level = params.level, + showIcon = params.showIcon, onSubmitClick = {}, ) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ComposerAlertMoleculeParamsProvider.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ComposerAlertMoleculeParamsProvider.kt new file mode 100644 index 0000000000..09027e0c91 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/ComposerAlertMoleculeParamsProvider.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.designsystem.atomic.molecules + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +import io.element.android.libraries.designsystem.components.avatar.anAvatarData + +internal data class ComposerAlertMoleculeParams( + val level: ComposerAlertLevel, + val avatar: AvatarData? = null, + val showIcon: Boolean = false, +) + +internal class ComposerAlertMoleculeParamsProvider : PreviewParameterProvider { + private val allLevels = sequenceOf( + ComposerAlertLevel.Default, + ComposerAlertLevel.Info, + ComposerAlertLevel.Critical + ) + + override val values: Sequence + get() = allLevels.flatMap { level -> + sequenceOf( + ComposerAlertMoleculeParams(level = level), + ComposerAlertMoleculeParams(level = level, avatar = anAvatarData(size = AvatarSize.ComposerAlert)), + ComposerAlertMoleculeParams(level = level, showIcon = true), + ) + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitlePlaceholdersRowMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitlePlaceholdersRowMolecule.kt index a71962fad9..1d2e8ae3fc 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitlePlaceholdersRowMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitlePlaceholdersRowMolecule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt index e38070e09f..56255d7623 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/IconTitleSubtitleMolecule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/InfoListItemMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/InfoListItemMolecule.kt index 0b3ca7fd8b..46d8029c89 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/InfoListItemMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/InfoListItemMolecule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/InviteButtonsRowMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/InviteButtonsRowMolecule.kt index 85d938d3be..96075dd1f9 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/InviteButtonsRowMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/InviteButtonsRowMolecule.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/MatrixBadgeRowMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/MatrixBadgeRowMolecule.kt index 1cadcf98b3..48f8d798fe 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/MatrixBadgeRowMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/MatrixBadgeRowMolecule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/MembersCountMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/MembersCountMolecule.kt index fa22b8a50a..4df1a5ad6a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/MembersCountMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/MembersCountMolecule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/NumberedListMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/NumberedListMolecule.kt index 1531598022..70ad04a373 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/NumberedListMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/NumberedListMolecule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/TextWithLabelMolecule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/TextWithLabelMolecule.kt index cc195ca686..fdc7ae56a5 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/TextWithLabelMolecule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/molecules/TextWithLabelMolecule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/InfoListOrganism.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/InfoListOrganism.kt index aeb8b5ba65..f52ee2512f 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/InfoListOrganism.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/InfoListOrganism.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/NumberedListOrganism.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/NumberedListOrganism.kt index 2c1ac7479c..df841dad9b 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/NumberedListOrganism.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/NumberedListOrganism.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/RoomPreviewOrganism.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/RoomPreviewOrganism.kt index dac9f00a7c..c8b064a18b 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/RoomPreviewOrganism.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/organisms/RoomPreviewOrganism.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/FlowStepPage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/FlowStepPage.kt index a6af316399..b62f634ece 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/FlowStepPage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/FlowStepPage.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/HeaderFooterPage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/HeaderFooterPage.kt index 5100abad64..f09dc895e9 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/HeaderFooterPage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/HeaderFooterPage.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/OnBoardingPage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/OnBoardingPage.kt index 810ff7b493..db0bb69cc7 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/OnBoardingPage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/OnBoardingPage.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt index 84c51b8ec8..c2ee9800db 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/SunsetPage.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/background/LightGradientBackground.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/background/LightGradientBackground.kt index d33fa9f211..776c52f985 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/background/LightGradientBackground.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/background/LightGradientBackground.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/background/OnboardingBackground.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/background/OnboardingBackground.kt index 73bc7ca841..2a3b790559 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/background/OnboardingBackground.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/background/OnboardingBackground.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/colors/AvatarColorsProvider.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/colors/AvatarColorsProvider.kt index 6d77bd8523..d12bfe3b82 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/colors/AvatarColorsProvider.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/colors/AvatarColorsProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/colors/Gradient.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/colors/Gradient.kt index 342377d0b8..1faed13425 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/colors/Gradient.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/colors/Gradient.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Announcement.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Announcement.kt index ae88faa205..dcd3f8fa21 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Announcement.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Announcement.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Badge.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Badge.kt index 5e2160a01f..315e75e655 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Badge.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Badge.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt index 78a4d32e44..40e84b34c3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/BigIcon.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt index 6c6d6d398e..a70aafff36 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ClickableLinkText.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/EqualWidthColumn.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/EqualWidthColumn.kt index 74dff31060..14fd284067 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/EqualWidthColumn.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/EqualWidthColumn.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ExpandableBottomSheetLayout.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ExpandableBottomSheetLayout.kt index 1caf5c50b7..85307823f6 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ExpandableBottomSheetLayout.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ExpandableBottomSheetLayout.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -76,28 +77,9 @@ fun ExpandableBottomSheetLayout( var calculatedMaxBottomContentHeightPx by remember(maxBottomContentHeightPx) { mutableIntStateOf(maxBottomContentHeightPx) } val animatable = remember { Animatable(0f) } - fun calculatePercentage(currentPos: Int, minPos: Int, maxPos: Int): Float { - val currentProgress = currentPos - minPos - if (currentProgress < 0) { - Timber.e("Invalid current progress: $currentProgress, minPos: $minPos, maxPos: $maxPos") - return 0f - } - val total = (maxPos - minPos).toFloat() - if (total <= 0) { - Timber.e("Invalid total space: $total, minPos: $minPos, maxPos: $maxPos") - return 0f - } - return currentProgress / total - } - LaunchedEffect(animatable.value) { if (animatable.isRunning && animatable.value != animatable.targetValue) { currentBottomContentHeightPx = animatable.value.roundToInt() - state.internalDraggingPercentage = calculatePercentage( - currentPos = currentBottomContentHeightPx, - minPos = minBottomContentHeightPx, - maxPos = calculatedMaxBottomContentHeightPx, - ) } } @@ -121,11 +103,6 @@ fun ExpandableBottomSheetLayout( minBottomContentHeightPx -> ExpandableBottomSheetLayoutState.Position.COLLAPSED else -> ExpandableBottomSheetLayoutState.Position.DRAGGING } - state.internalDraggingPercentage = calculatePercentage( - currentPos = newHeight, - minPos = minBottomContentHeightPx, - maxPos = calculatedMaxBottomContentHeightPx, - ) currentBottomContentHeightPx = newHeight }, onDragEnd = { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ExpandableBottomSheetLayoutState.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ExpandableBottomSheetLayoutState.kt index 71fb2d06be..ae9f9e4cf2 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ExpandableBottomSheetLayoutState.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ExpandableBottomSheetLayoutState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.libraries.designsystem.components import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -31,21 +31,12 @@ fun rememberExpandableBottomSheetLayoutState(): ExpandableBottomSheetLayoutState @Stable class ExpandableBottomSheetLayoutState { internal var internalPosition: Position by mutableStateOf(Position.COLLAPSED) - internal var internalDraggingPercentage: Float by mutableFloatStateOf( - if (internalPosition == Position.EXPANDED) 1f else 0f - ) /** * The current position of the bottom sheet layout. */ val position get() = internalPosition - /** - * The percentage of the bottom sheet layout that is currently being dragged. - * This value ranges from `0f` for [Position.COLLAPSED] to `1f` for [Position.EXPANDED]. - */ - val draggingPercentage = internalDraggingPercentage - /** * The position of the bottom sheet layout. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledCheckbox.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledCheckbox.kt index 3f25a69eb0..5e23306b41 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledCheckbox.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/LabelledCheckbox.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/PinIcon.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/PinIcon.kt index 7bcc216d2f..88287ef44b 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/PinIcon.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/PinIcon.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt index fe18daaaea..571a0b9bc2 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/SimpleModalBottomSheet.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/SimpleModalBottomSheet.kt index 5b87b1171b..34d119508a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/SimpleModalBottomSheet.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/SimpleModalBottomSheet.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/TopAppBarScrollBehaviorLayout.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/TopAppBarScrollBehaviorLayout.kt new file mode 100644 index 0000000000..36e9547575 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/TopAppBarScrollBehaviorLayout.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.designsystem.components + +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Surface +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.material3.contentColorFor +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.UiComposable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.Layout +import io.element.android.compound.theme.ElementTheme + +/** + * A layout that measures its content to set the height offset limit of a [TopAppBarScrollBehavior]. + * It places the content according to the current height offset of the scroll behavior. + * + */ +@ExperimentalMaterial3Api +@Composable +fun TopAppBarScrollBehaviorLayout( + scrollBehavior: TopAppBarScrollBehavior, + modifier: Modifier = Modifier, + backgroundColor: Color = ElementTheme.colors.bgCanvasDefault, + contentColor: Color = contentColorFor(backgroundColor), + content: @Composable @UiComposable () -> Unit, +) { + Surface( + modifier = modifier, + color = backgroundColor, + contentColor = contentColor + ) { + Layout( + content = content, + measurePolicy = { measurables, constraints -> + val placeable = measurables.first().measure(constraints) + val contentHeight = placeable.height.toFloat() + scrollBehavior.state.heightOffsetLimit = -contentHeight + val heightOffset = scrollBehavior.state.heightOffset + val layoutHeight = (contentHeight + heightOffset).toInt() + layout(placeable.width, layoutHeight) { + placeable.place(0, heightOffset.toInt()) + } + } + ) + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionProvider.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionProvider.kt index a1062b697f..2f7439e1c8 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionProvider.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionView.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionView.kt index 296e7aa543..da1b0fc796 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionView.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncFailure.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncFailure.kt index b2050a6d7e..6b303a4782 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncFailure.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncFailure.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncIndicator.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncIndicator.kt index 173980f515..b4880520e5 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncIndicator.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncIndicator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncIndicatorHost.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncIndicatorHost.kt index 1b4c23c0e7..abfbe77789 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncIndicatorHost.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncIndicatorHost.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncIndicatorView.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncIndicatorView.kt index 7193feb213..521ed68794 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncIndicatorView.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncIndicatorView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncLoading.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncLoading.kt index 4345cd35dd..9a2faa4bd8 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncLoading.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncLoading.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt index d3a9ed3107..57dcb60325 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt index 2e711ee6eb..ac7e426e65 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarData.kt @@ -1,17 +1,16 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.designsystem.components.avatar -import androidx.compose.runtime.Immutable import io.element.android.libraries.core.data.tryOrNull import java.text.BreakIterator -@Immutable data class AvatarData( val id: String, val name: String?, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarDataProvider.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarDataProvider.kt index 2bc497f10d..870ffe44a9 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarDataProvider.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarDataProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarRow.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarRow.kt index 8c3ae3aac9..25784aa501 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarRow.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarRow.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarShape.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarShape.kt index c4c2c77766..fc9f31da46 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarShape.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarShape.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt index 31c569c870..0f99349494 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -46,7 +47,7 @@ enum class AvatarSize(val dp: Dp) { InviteSender(16.dp), EditRoomDetails(70.dp), - RoomListManageUser(70.dp), + RoomListManageUser(96.dp), NotificationsOptIn(32.dp), diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarType.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarType.kt index 6d99574a55..f7a8fee888 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarType.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarType.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/DmAvatars.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/DmAvatars.kt index f4d05d3fcd..1cd024542c 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/DmAvatars.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/DmAvatars.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/AvatarCluster.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/AvatarCluster.kt index 1c8ac81e64..bb696c2405 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/AvatarCluster.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/AvatarCluster.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/ImageAvatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/ImageAvatar.kt index db30a94830..ebd81f61b1 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/ImageAvatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/ImageAvatar.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -45,7 +46,7 @@ internal fun ImageAvatar( is AsyncImagePainter.State.Success -> SubcomposeAsyncImageContent() is AsyncImagePainter.State.Error -> { SideEffect { - Timber.Forest.e( + Timber.e( state.result.throwable, "Error loading avatar $state\n${state.result}" ) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/InitialLetterAvatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/InitialLetterAvatar.kt index 6503917b23..05508b8af6 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/InitialLetterAvatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/InitialLetterAvatar.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/InitialOrImageAvatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/InitialOrImageAvatar.kt index 3111048d3b..4e5a4f0e36 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/InitialOrImageAvatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/InitialOrImageAvatar.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/OverlapRatioProvider.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/OverlapRatioProvider.kt index 9a1abcee58..26a76c2fac 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/OverlapRatioProvider.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/OverlapRatioProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/RoomAvatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/RoomAvatar.kt index a9d1e5a95e..e5dc24f5f6 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/RoomAvatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/RoomAvatar.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/SpaceAvatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/SpaceAvatar.kt index b8880744c9..c301a490eb 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/SpaceAvatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/SpaceAvatar.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/TextAvatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/TextAvatar.kt index f44319fece..8867735259 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/TextAvatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/TextAvatar.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/TombstonedRoomAvatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/TombstonedRoomAvatar.kt index b712ff9fb1..413ca3b3f1 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/TombstonedRoomAvatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/TombstonedRoomAvatar.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/UserAvatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/UserAvatar.kt index ad0275593d..c6aa75be0e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/UserAvatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/UserAvatar.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/UserAvatarPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/UserAvatarPreview.kt index d4a629a052..a9c1f95ac6 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/UserAvatarPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/internal/UserAvatarPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/blurhash/BlurHashAsyncImage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/blurhash/BlurHashAsyncImage.kt index c22197f22f..f3da71b000 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/blurhash/BlurHashAsyncImage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/blurhash/BlurHashAsyncImage.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/blurhash/BlurHashBackgroundModifier.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/blurhash/BlurHashBackgroundModifier.kt index ef1f1573cb..a6f04a20d3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/blurhash/BlurHashBackgroundModifier.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/blurhash/BlurHashBackgroundModifier.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/blurhash/BlurHashImage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/blurhash/BlurHashImage.kt index a6c1f9f9b8..7285322026 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/blurhash/BlurHashImage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/blurhash/BlurHashImage.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/BackButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/BackButton.kt index 83e3891a75..52a97baf57 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/BackButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/BackButton.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/ButtonVisuals.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/ButtonVisuals.kt index 4444ff7f1e..9d7a0fec19 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/ButtonVisuals.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/ButtonVisuals.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/GradientFloatingActionButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/GradientFloatingActionButton.kt index d58e181353..ff82f3f4b8 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/GradientFloatingActionButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/GradientFloatingActionButton.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -37,12 +38,10 @@ import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.dp import io.element.android.compound.annotations.CoreColorToken -import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons -import io.element.android.compound.tokens.generated.internal.LightColorTokens +import io.element.android.libraries.designsystem.colors.gradientActionColors import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.designsystem.theme.LocalBuildMeta import io.element.android.libraries.designsystem.theme.components.Icon @OptIn(CoreColorToken::class) @@ -53,26 +52,14 @@ fun GradientFloatingActionButton( shape: Shape = RoundedCornerShape(25), content: @Composable () -> Unit, ) { - val color1 = if (LocalBuildMeta.current.isEnterpriseBuild) { - ElementTheme.colors.textActionAccent - } else { - LightColorTokens.colorGreen700 - } - val color2 = if (LocalBuildMeta.current.isEnterpriseBuild) { - ElementTheme.colors.textActionAccent - } else { - LightColorTokens.colorBlue900 - } + val colors = gradientActionColors() val linearShaderBrush = remember { object : ShaderBrush() { override fun createShader(size: Size): Shader { return LinearGradientShader( from = Offset(size.width, size.height), to = Offset(size.width, 0f), - colors = listOf( - color2, - color1, - ), + colors = colors, ) } } @@ -83,10 +70,7 @@ fun GradientFloatingActionButton( return RadialGradientShader( center = size.center, radius = size.width / 2, - colors = listOf( - color1, - color2, - ) + colors = colors, ) } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/MainActionButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/MainActionButton.kt index 56a0a756c9..9893620bbd 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/MainActionButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/MainActionButton.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/SuperButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/SuperButton.kt index 90dd64d59d..694e739f25 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/SuperButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/SuperButton.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -40,7 +41,6 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.colors.gradientActionColors import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.designsystem.theme.LocalBuildMeta import io.element.android.libraries.designsystem.theme.components.ButtonSize import io.element.android.libraries.designsystem.theme.components.HorizontalDivider import io.element.android.libraries.designsystem.theme.components.lowHorizontalPaddingValue @@ -63,15 +63,7 @@ fun SuperButton( ButtonSize.Small -> PaddingValues(horizontal = 16.dp, vertical = 5.dp) } } - val colors = if (LocalBuildMeta.current.isEnterpriseBuild) { - listOf( - ElementTheme.colors.textActionAccent, - ElementTheme.colors.textActionAccent, - ) - } else { - gradientActionColors() - } - + val colors = gradientActionColors() val shaderBrush = remember(colors) { object : ShaderBrush() { override fun createShader(size: Size): Shader { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/AlertDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/AlertDialog.kt index 1c87ab0924..3c9204ac3e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/AlertDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/AlertDialog.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ConfirmationDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ConfirmationDialog.kt index b15bafa338..9d217da059 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ConfirmationDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ConfirmationDialog.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ErrorDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ErrorDialog.kt index f1a645079a..3c79dd7714 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ErrorDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ErrorDialog.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ErrorDialogWithDoNotShowAgain.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ErrorDialogWithDoNotShowAgain.kt index e4a35c1647..e85725cfc0 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ErrorDialogWithDoNotShowAgain.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ErrorDialogWithDoNotShowAgain.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -56,14 +57,14 @@ fun ErrorDialogWithDoNotShowAgain( Column { Text( text = content, - style = ElementTheme.materialTypography.bodyMedium, + style = ElementTheme.typography.fontBodyMdRegular, ) Spacer(modifier = Modifier.height(8.dp)) Row(verticalAlignment = Alignment.CenterVertically) { Checkbox(checked = doNotShowAgain, onCheckedChange = { doNotShowAgain = it }) Text( text = stringResource(id = CommonStrings.common_do_not_show_this_again), - style = ElementTheme.materialTypography.bodyMedium, + style = ElementTheme.typography.fontBodyMdRegular, ) } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ListDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ListDialog.kt index 944fca1cf4..ce1afae93d 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ListDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ListDialog.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -40,6 +41,7 @@ fun ListDialog( submitText: String = stringResource(CommonStrings.action_ok), enabled: Boolean = true, applyPaddingToContents: Boolean = true, + destructiveSubmit: Boolean = false, listItems: LazyListScope.() -> Unit, ) { val decoratedSubtitle: @Composable (() -> Unit)? = subtitle?.let { @@ -64,6 +66,7 @@ fun ListDialog( enabled = enabled, listItems = listItems, applyPaddingToContents = applyPaddingToContents, + destructiveSubmit = destructiveSubmit, ) } } @@ -78,6 +81,7 @@ private fun ListDialogContent( title: String?, enabled: Boolean, applyPaddingToContents: Boolean, + destructiveSubmit: Boolean, subtitle: @Composable (() -> Unit)? = null, ) { SimpleAlertDialogContent( @@ -89,6 +93,7 @@ private fun ListDialogContent( onSubmitClick = onSubmitClick, enabled = enabled, applyPaddingToContents = applyPaddingToContents, + destructiveSubmit = destructiveSubmit, ) { // No start padding if padding is already applied to the content val horizontalPadding = if (applyPaddingToContents) 0.dp else 8.dp @@ -119,6 +124,7 @@ internal fun ListDialogContentPreview() { cancelText = "Cancel", submitText = "Save", enabled = true, + destructiveSubmit = false, applyPaddingToContents = true, ) } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ListOption.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ListOption.kt index d21a57325f..5b84b609d9 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ListOption.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ListOption.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/MultipleSelectionDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/MultipleSelectionDialog.kt index 81646f9459..6fe899287b 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/MultipleSelectionDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/MultipleSelectionDialog.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/RetryDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/RetryDialog.kt index c496b83286..0b313e1180 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/RetryDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/RetryDialog.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SaveChangesDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SaveChangesDialog.kt new file mode 100644 index 0000000000..dc30b191a0 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SaveChangesDialog.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.designsystem.components.dialogs + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun SaveChangesDialog( + onSaveClick: () -> Unit, + onDiscardClick: () -> Unit, + onDismiss: () -> Unit, + modifier: Modifier = Modifier, + title: String = stringResource(CommonStrings.dialog_unsaved_changes_title), + content: String = stringResource(CommonStrings.dialog_unsaved_changes_description), + submitText: String = stringResource(CommonStrings.action_save), + cancelText: String = stringResource(CommonStrings.action_discard), +) = ConfirmationDialog( + modifier = modifier, + title = title, + content = content, + submitText = submitText, + cancelText = cancelText, + onSubmitClick = onSaveClick, + onCancelClick = onDiscardClick, + onDismiss = onDismiss, +) + +@PreviewsDayNight +@Composable +internal fun SaveChangesDialogPreview() = ElementPreview { + SaveChangesDialog( + onSaveClick = {}, + onDiscardClick = {}, + onDismiss = {} + ) +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SingleSelectionDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SingleSelectionDialog.kt index 23e7b22d85..8a64fce510 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SingleSelectionDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SingleSelectionDialog.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/TextFieldDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/TextFieldDialog.kt index bb31edd313..1ce241d5d9 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/TextFieldDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/TextFieldDialog.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -42,11 +43,13 @@ fun TextFieldDialog( validation: (String?) -> Boolean = { true }, onValidationErrorMessage: String? = null, autoSelectOnDisplay: Boolean = true, - maxLines: Int = 1, + minLines: Int = 1, + maxLines: Int = minLines, content: String? = null, label: String? = null, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, submitText: String = stringResource(CommonStrings.action_ok), + destructiveSubmit: Boolean = false, ) { val focusRequester = remember { FocusRequester() } var textFieldContents by rememberSaveable(stateSaver = TextFieldValue.Saver) { @@ -66,13 +69,14 @@ fun TextFieldDialog( onDismissRequest = onDismissRequest, enabled = canSubmit, submitText = submitText, + destructiveSubmit = destructiveSubmit, modifier = modifier, ) { if (content != null) { item { Text( text = content, - style = ElementTheme.materialTypography.bodyMedium, + style = ElementTheme.typography.fontBodyMdRegular, ) } } @@ -92,6 +96,7 @@ fun TextFieldDialog( onSubmit(textFieldContents.text) } }), + minLines = minLines, maxLines = maxLines, modifier = Modifier .fillMaxWidth() diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/form/TextFieldLocalState.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/form/TextFieldLocalState.kt index ab37ddb277..3911f0e947 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/form/TextFieldLocalState.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/form/TextFieldLocalState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/CheckboxListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/CheckboxListItem.kt index 41cef050ff..4775b91720 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/CheckboxListItem.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/CheckboxListItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt index 5b9efc51c8..e8add0369f 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -84,7 +85,7 @@ sealed interface ListItemContent { data class Text(val text: String) : ListItemContent /** Displays any custom content. */ - data class Custom(val content: @Composable () -> Unit) : ListItemContent + data class Custom(val content: @Composable (enabled: Boolean) -> Unit) : ListItemContent /** Displays a badge. */ data object Badge : ListItemContent @@ -130,7 +131,7 @@ sealed interface ListItemContent { is Counter -> { CounterAtom(count = count) } - is Custom -> content() + is Custom -> content(isItemEnabled) } } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/MultipleSelectionListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/MultipleSelectionListItem.kt index 69a41c4ff3..f314f17008 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/MultipleSelectionListItem.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/MultipleSelectionListItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/RadioButtonListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/RadioButtonListItem.kt index 9cbfe2edda..70fb91e2e6 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/RadioButtonListItem.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/RadioButtonListItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SingleSelectionListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SingleSelectionListItem.kt index 2db45def82..e07cda5e77 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SingleSelectionListItem.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SingleSelectionListItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SwitchListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SwitchListItem.kt index 070e112736..c5016bc5bc 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SwitchListItem.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/SwitchListItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/TextFieldListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/TextFieldListItem.kt index f9d61b6593..e149af5890 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/TextFieldListItem.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/TextFieldListItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -25,7 +26,8 @@ fun TextFieldListItem( onTextChange: (String) -> Unit, modifier: Modifier = Modifier, error: String? = null, - maxLines: Int = 1, + minLines: Int = 1, + maxLines: Int = minLines, label: String? = null, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default, @@ -52,7 +54,8 @@ fun TextFieldListItem( onTextChange: (TextFieldValue) -> Unit, modifier: Modifier = Modifier, error: String? = null, - maxLines: Int = 1, + minLines: Int = 1, + maxLines: Int = minLines, label: String? = null, keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default, @@ -67,6 +70,7 @@ fun TextFieldListItem( keyboardOptions = keyboardOptions, keyboardActions = keyboardActions, maxLines = maxLines, + minLines = minLines, singleLine = maxLines == 1, modifier = modifier, ) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/DrawScopeWaveformExtensions.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/DrawScopeWaveformExtensions.kt index 4c17c10835..14c95640bc 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/DrawScopeWaveformExtensions.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/DrawScopeWaveformExtensions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/FakeWaveformFactory.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/FakeWaveformFactory.kt deleted file mode 100644 index 3264f55754..0000000000 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/FakeWaveformFactory.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.designsystem.components.media - -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList -import kotlin.random.Random - -/** - * Generate a waveform for testing purposes. - * - * The waveform is a list of floats between 0 and 1. - * - * @param length The length of the waveform. - */ -fun createFakeWaveform(length: Int = 1000): ImmutableList { - val random = Random(seed = 2) - return List(length) { random.nextFloat() } - .toImmutableList() -} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveFormSamples.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveFormSamples.kt new file mode 100644 index 0000000000..ddbbb9d10d --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveFormSamples.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.designsystem.components.media + +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList + +object WaveFormSamples { + val allRangeWaveForm = List(100) { it.toFloat() / 100 }.toImmutableList() + + @Suppress("ktlint:standard:argument-list-wrapping") + val realisticWaveForm = persistentListOf( + 0.000f, 0.000f, 0.000f, 0.003f, 0.354f, + 0.353f, 0.365f, 0.790f, 0.787f, 0.167f, + 0.333f, 0.975f, 0.000f, 0.102f, 0.003f, + 0.531f, 0.584f, 0.317f, 0.140f, 0.475f, + 0.496f, 0.561f, 0.042f, 0.263f, 0.169f, + 0.829f, 0.349f, 0.010f, 0.000f, 0.000f, + 1.000f, 0.334f, 0.321f, 0.011f, 0.000f, + 0.000f, 0.003f, + ) + + val longRealisticWaveForm = List(4) { realisticWaveForm }.flatten().toImmutableList() +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveformPlaybackView.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveformPlaybackView.kt index 5fe722d447..f91cf96a95 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveformPlaybackView.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveformPlaybackView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -49,7 +50,7 @@ private const val DEFAULT_GRAPHICS_LAYER_ALPHA: Float = 0.99F * * @param playbackProgress The current playback progress, between 0 and 1. * @param showCursor Whether to show the cursor or not. - * @param waveform The waveform to display. Use [createFakeWaveform] to generate a fake waveform. + * @param waveform The waveform to display. * @param onSeek Callback when the user seeks the waveform. Called with a value between 0 and 1. * @param modifier The modifier to be applied to the view. * @param seekEnabled Whether the user can seek the waveform or not. @@ -187,14 +188,14 @@ internal fun WaveformPlaybackViewPreview() = ElementPreview { showCursor = false, playbackProgress = 0.5f, onSeek = {}, - waveform = aWaveForm().toImmutableList(), + waveform = WaveFormSamples.realisticWaveForm, ) WaveformPlaybackView( modifier = Modifier.height(34.dp), showCursor = true, playbackProgress = 0.5f, onSeek = {}, - waveform = List(1024) { it / 1024f }.toImmutableList(), + waveform = WaveFormSamples.allRangeWaveForm, ) } } @@ -217,45 +218,3 @@ private fun ImmutableList.normalisedData(maxSamplesCount: Int): Immutable return result.toImmutableList() } - -fun aWaveForm(): List { - return listOf( - 0.000f, - 0.000f, - 0.000f, - 0.003f, - 0.354f, - 0.353f, - 0.365f, - 0.790f, - 0.787f, - 0.167f, - 0.333f, - 0.975f, - 0.000f, - 0.102f, - 0.003f, - 0.531f, - 0.584f, - 0.317f, - 0.140f, - 0.475f, - 0.496f, - 0.561f, - 0.042f, - 0.263f, - 0.169f, - 0.829f, - 0.349f, - 0.010f, - 0.000f, - 0.000f, - 1.000f, - 0.334f, - 0.321f, - 0.011f, - 0.000f, - 0.000f, - 0.003f, - ) -} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCategory.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCategory.kt index 1a993899e8..452c9d8d3c 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCategory.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCategory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCheckbox.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCheckbox.kt index f70b87e812..6b4a8e1e0c 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCheckbox.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceCheckbox.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -42,7 +43,6 @@ fun PreferenceCheckbox( leadingContent = preferenceIcon( icon = icon, iconResourceId = iconResourceId, - enabled = enabled, showIconAreaIfNoIcon = showIconAreaIfNoIcon, ), headlineContent = { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDivider.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDivider.kt index a401d02d2e..139aea33e1 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDivider.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDivider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDropdown.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDropdown.kt index 7be47338ca..5fcbcab475 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDropdown.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceDropdown.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,13 +11,12 @@ package io.element.android.libraries.designsystem.components.preferences import androidx.annotation.DrawableRes +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.ExposedDropdownMenuDefaults -import androidx.compose.material3.MenuAnchorType import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -25,17 +25,22 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.preferences.components.preferenceIcon import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup +import io.element.android.libraries.designsystem.theme.components.DropdownMenu import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem +import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.designsystem.toEnabledColor +import io.element.android.libraries.designsystem.toIconSecondaryEnabledColor import io.element.android.libraries.designsystem.toSecondaryEnabledColor import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -59,7 +64,6 @@ fun PreferenceDropdown( leadingContent = preferenceIcon( icon = icon, iconResourceId = iconResourceId, - enabled = enabled, showIconAreaIfNoIcon = showIconAreaIfNoIcon, ), headlineContent = { @@ -67,7 +71,6 @@ fun PreferenceDropdown( style = ElementTheme.typography.fontBodyLgRegular, modifier = Modifier.fillMaxWidth(), text = title, - color = enabled.toEnabledColor(), ) }, supportingContent = supportingText?.let { @@ -75,21 +78,23 @@ fun PreferenceDropdown( Text( style = ElementTheme.typography.fontBodyMdRegular, text = it, - color = enabled.toSecondaryEnabledColor(), ) } }, trailingContent = ListItemContent.Custom( - content = { + content = { enabled -> DropdownTrailingContent( selectedOption = selectedOption, options = options, onSelectOption = onSelectOption, expanded = isDropdownExpanded, onExpandedChange = { isDropdownExpanded = it }, + enabled = enabled, + modifier = Modifier.fillMaxSize(0.3f) ) } ), + enabled = enabled, onClick = { isDropdownExpanded = true }.takeIf { !isDropdownExpanded }, ) } @@ -112,38 +117,51 @@ private fun DropdownTrailingContent( expanded: Boolean, onExpandedChange: (Boolean) -> Unit, onSelectOption: (T) -> Unit, + enabled: Boolean, modifier: Modifier = Modifier, ) { - ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = onExpandedChange, + Row( modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.End, ) { - Row( - modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = selectedOption?.getText().orEmpty(), - maxLines = 1, - style = ElementTheme.typography.fontBodyMdRegular, - color = ElementTheme.colors.textSecondary, - ) - ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) - } - ExposedDropdownMenu( + Text( + text = selectedOption?.getText().orEmpty(), + maxLines = 1, + style = ElementTheme.typography.fontBodyMdRegular, + color = enabled.toSecondaryEnabledColor(), + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.End, + modifier = Modifier.weight(1f), + ) + Icon( + imageVector = CompoundIcons.ChevronDown(), + contentDescription = null, + tint = enabled.toIconSecondaryEnabledColor(), + ) + DropdownMenu( expanded = expanded, + minWidth = 0.dp, onDismissRequest = { onExpandedChange(false) }, - matchTextFieldWidth = false, ) { options.forEach { option -> DropdownMenuItem( + enabled = enabled, text = { Text( text = option.getText(), style = ElementTheme.typography.fontBodyMdRegular ) }, + trailingIcon = { + if (option == selectedOption) { + Icon( + imageVector = CompoundIcons.Check(), + contentDescription = null, + tint = ElementTheme.colors.iconAccentPrimary, + ) + } + }, onClick = { onSelectOption(option) onExpandedChange(false) @@ -189,5 +207,14 @@ internal fun PreferenceDropdownPreview() = ElementThemedPreview { options = options, onSelectOption = {}, ) + PreferenceDropdown( + title = "Dropdown", + supportingText = "Options for dropdown", + icon = CompoundIcons.Threads(), + selectedOption = options.first(), + options = options, + onSelectOption = {}, + enabled = false + ) } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferencePage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferencePage.kt index b81c3e6b86..24477b4339 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferencePage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferencePage.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceRow.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceRow.kt index d52f1c3a5a..efc36544b9 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceRow.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceRow.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSlide.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSlide.kt index 3513221cb9..671eb5bf3f 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSlide.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSlide.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -43,7 +44,6 @@ fun PreferenceSlide( leadingContent = preferenceIcon( icon = icon, iconResourceId = iconResourceId, - enabled = enabled, showIconAreaIfNoIcon = showIconAreaIfNoIcon, ), headlineContent = { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSwitch.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSwitch.kt index ed71fa5389..4545dbdf3e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSwitch.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceSwitch.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -41,7 +42,6 @@ fun PreferenceSwitch( leadingContent = preferenceIcon( icon = icon, iconResourceId = iconResourceId, - enabled = enabled, showIconAreaIfNoIcon = showIconAreaIfNoIcon, ), headlineContent = { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceTextField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceTextField.kt index 98f324f88a..c67b4c857f 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceTextField.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceTextField.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/components/ImageVectorProvider.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/components/ImageVectorProvider.kt index 7e3052ca16..c1bf2b9112 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/components/ImageVectorProvider.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/components/ImageVectorProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/components/PreferenceIcon.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/components/PreferenceIcon.kt index 2e4fff1e92..59e818c18a 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/components/PreferenceIcon.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/components/PreferenceIcon.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -33,11 +34,10 @@ fun preferenceIcon( @DrawableRes iconResourceId: Int? = null, showIconBadge: Boolean = false, tintColor: Color? = null, - enabled: Boolean = true, showIconAreaIfNoIcon: Boolean = false, ): ListItemContent.Custom? { return if (icon != null || iconResourceId != null || showIconAreaIfNoIcon) { - ListItemContent.Custom { + ListItemContent.Custom { enabled -> PreferenceIcon( icon = icon, iconResourceId = iconResourceId, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/ElementTooltipDefaults.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/ElementTooltipDefaults.kt index 0182a4b108..c6c244b2d6 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/ElementTooltipDefaults.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/ElementTooltipDefaults.kt @@ -1,13 +1,15 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.designsystem.components.tooltip import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -40,8 +42,9 @@ object ElementTooltipDefaults { windowPadding: Dp = 12.dp, ): PopupPositionProvider { val windowPaddingPx = with(LocalDensity.current) { windowPadding.roundToPx() } - val plainTooltipPositionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider( - spacingBetweenTooltipAndAnchor = spacingBetweenTooltipAndAnchor, + val plainTooltipPositionProvider = TooltipDefaults.rememberTooltipPositionProvider( + positioning = TooltipAnchorPosition.Above, + spacingBetweenTooltipAndAnchor = spacingBetweenTooltipAndAnchor ) return remember(windowPaddingPx, plainTooltipPositionProvider) { object : PopupPositionProvider { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/PlainTooltip.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/PlainTooltip.kt index e497f5eb37..f0fb57fc49 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/PlainTooltip.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/PlainTooltip.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/TooltipBox.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/TooltipBox.kt index e623f79186..3bca8b8b8b 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/TooltipBox.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/tooltip/TooltipBox.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/CompoundDrawables.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/CompoundDrawables.kt index d10eed1312..4aeba02ee4 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/CompoundDrawables.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/CompoundDrawables.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt index ff98ab5b1e..f0a6fc847b 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsList.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,4 +16,5 @@ internal val iconsOther = listOf( R.drawable.ic_notification, R.drawable.ic_stop, R.drawable.pin, + R.drawable.ic_winner, ) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsPreview.kt index e2da9bdcc0..f40c4ea024 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/icons/IconsPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,11 +19,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme -import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon @@ -30,53 +28,12 @@ import io.element.android.libraries.designsystem.theme.components.Text import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -internal class CompoundIconChunkProvider : PreviewParameterProvider { - override val values: Sequence - get() { - val chunks = CompoundIcons.allResIds.chunked(36) - return chunks.mapIndexed { index, chunk -> - IconChunk(index = index + 1, total = chunks.size, icons = chunk.toImmutableList()) - } - .asSequence() - } -} - -internal class OtherIconChunkProvider : PreviewParameterProvider { - override val values: Sequence - get() { - val chunks = iconsOther.chunked(36) - return chunks.mapIndexed { index, chunk -> - IconChunk(index = index + 1, total = chunks.size, icons = chunk.toImmutableList()) - } - .asSequence() - } -} - -internal data class IconChunk( - val index: Int, - val total: Int, - val icons: ImmutableList, -) - @PreviewsDayNight @Composable -internal fun IconsCompoundPreview(@PreviewParameter(CompoundIconChunkProvider::class) chunk: IconChunk) = ElementPreview { +internal fun IconsOtherPreview() = ElementPreview { IconsPreview( - title = "R.drawable.ic_compound_* ${chunk.index}/${chunk.total}", - iconsList = chunk.icons, - iconNameTransform = { name -> - name.removePrefix("ic_compound_") - .replace("_", " ") - } - ) -} - -@PreviewsDayNight -@Composable -internal fun IconsOtherPreview(@PreviewParameter(OtherIconChunkProvider::class) iconChunk: IconChunk) = ElementPreview { - IconsPreview( - title = "R.drawable.ic_* ${iconChunk.index}/${iconChunk.total}", - iconsList = iconChunk.icons, + title = "Other icons", + iconsList = iconsOther.toImmutableList(), iconNameTransform = { name -> name.removePrefix("ic_") .replace("_", " ") diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/ApplyIf.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/ApplyIf.kt index abd1de9f90..0d1bece173 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/ApplyIf.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/ApplyIf.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Blur.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Blur.kt index 9c7145e33b..43c07ad79e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Blur.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Blur.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/ClearFocusOnTap.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/ClearFocusOnTap.kt index c338b36d26..c9ab328f02 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/ClearFocusOnTap.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/ClearFocusOnTap.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Clickable.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Clickable.kt index 5789a4525d..7350c54de1 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Clickable.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Clickable.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CornerBorder.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CornerBorder.kt index eb4e6a8050..ad42616411 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CornerBorder.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CornerBorder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/FadingEdge.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/FadingEdge.kt index b5ba35c807..72167a13da 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/FadingEdge.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/FadingEdge.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Gradient.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Gradient.kt index 5d6f91e4b4..115c567391 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Gradient.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Gradient.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,13 +16,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.colors.gradientSubtleColors import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.designsystem.theme.LocalBuildMeta /** * Ref: https://www.figma.com/design/kcnHxunG1LDWXsJhaNuiHz/ER-145--Workspaces-V1?node-id=1141-24692 @@ -30,35 +28,15 @@ import io.element.android.libraries.designsystem.theme.LocalBuildMeta @Composable fun Modifier.backgroundVerticalGradient( isVisible: Boolean = true, - isEnterpriseBuild: Boolean = LocalBuildMeta.current.isEnterpriseBuild, ): Modifier { if (!isVisible) return this return background( brush = Brush.verticalGradient( - colorStops = subtleColorStops(isEnterpriseBuild), + colors = gradientSubtleColors(), ), ) } -@Composable -fun subtleColorStops( - isEnterpriseBuild: Boolean = LocalBuildMeta.current.isEnterpriseBuild, -): Array> { - return buildList { - if (isEnterpriseBuild) { - // For enterprise builds, ensure that we are theming the gradient - add(0f to ElementTheme.colors.textActionAccent.copy(alpha = 0.5f)) - add(0.75f to ElementTheme.colors.bgCanvasDefault) - add(1f to Color.Transparent) - } else { - val colors = gradientSubtleColors() - colors.forEachIndexed { index, color -> - add(index.toFloat() / (colors.size - 1) to color) - } - } - }.toTypedArray() -} - @PreviewsDayNight @Composable internal fun BackgroundVerticalGradientPreview() = ElementPreview { @@ -70,19 +48,6 @@ internal fun BackgroundVerticalGradientPreview() = ElementPreview { ) } -@PreviewsDayNight -@Composable -internal fun BackgroundVerticalGradientEnterprisePreview() = ElementPreview { - Box( - modifier = Modifier - .fillMaxWidth() - .height(height = 100.dp) - .backgroundVerticalGradient( - isEnterpriseBuild = true, - ) - ) -} - @PreviewsDayNight @Composable internal fun BackgroundVerticalGradientDisabledPreview() = ElementPreview { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Keyboard.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Keyboard.kt index c8c8b8769e..8d961ecb82 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Keyboard.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/Keyboard.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/OnTabOrEnterKeyFocusNext.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/OnTabOrEnterKeyFocusNext.kt index dfe23c85d7..08cf1a052e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/OnTabOrEnterKeyFocusNext.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/OnTabOrEnterKeyFocusNext.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/RoundedBackground.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/RoundedBackground.kt index ac444257bf..e315f69fa3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/RoundedBackground.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/RoundedBackground.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/SquareSizeModifier.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/SquareSizeModifier.kt index 6c2e3884d6..a7dae1eaf6 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/SquareSizeModifier.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/SquareSizeModifier.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreview.kt index 788b8fee55..b0ef41e154 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewDark.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewDark.kt index b1142c0174..c054b318f3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewDark.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewDark.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewLight.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewLight.kt index 266fca534a..1c2bdf3cef 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewLight.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementPreviewLight.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementThemedPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementThemedPreview.kt index 7ab7d323e3..7b29757843 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementThemedPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/ElementThemedPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewGroup.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewGroup.kt index aa2b4dd761..0f40021bb3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewGroup.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewGroup.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewWithLargeHeight.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewWithLargeHeight.kt index fed605792c..163ee21590 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewWithLargeHeight.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewWithLargeHeight.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewsDayNight.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewsDayNight.kt index 28964ebbae..4b084b1e21 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewsDayNight.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/PreviewsDayNight.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 index b605283c2c..7e687e9b89 100644 --- 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,12 +12,12 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SheetState import androidx.compose.material3.SheetValue import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalDensity @OptIn(ExperimentalMaterial3Api::class) @Composable fun sheetStateForPreview() = SheetState( skipPartiallyExpanded = true, + positionalThreshold = { 0.5f }, + velocityThreshold = { 400f }, initialValue = SheetValue.Expanded, - density = LocalDensity.current, ) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/WithFontScale.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/WithFontScale.kt index 4c333f2f8a..5967b660dd 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/WithFontScale.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/preview/WithFontScale.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/HorizontalRuler.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/HorizontalRuler.kt index 82469b8f23..f88a5a643f 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/HorizontalRuler.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/HorizontalRuler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/VerticalRuler.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/VerticalRuler.kt index 8c922ba8c8..171a2901e5 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/VerticalRuler.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/VerticalRuler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/WithRulers.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/WithRulers.kt index 234d07cb25..1248c761e7 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/WithRulers.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/ruler/WithRulers.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/showkase/DesignSystemShowkaseRootModule.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/showkase/DesignSystemShowkaseRootModule.kt index 0475bd417f..abb420a0a0 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/showkase/DesignSystemShowkaseRootModule.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/showkase/DesignSystemShowkaseRootModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/swipe/SwipeableActionsState.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/swipe/SwipeableActionsState.kt index 35ac426005..418c1d1b63 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/swipe/SwipeableActionsState.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/swipe/SwipeableActionsState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt index 79f70cc2f5..43fd80ab43 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/AnnotatedStrings.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/DpScale.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/DpScale.kt index 856160d7bf..3fe720e3c7 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/DpScale.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/DpScale.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/StringWithLink.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/StringWithLink.kt new file mode 100644 index 0000000000..d82ea9e817 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/StringWithLink.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.designsystem.text + +import androidx.annotation.StringRes +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.LinkAnnotation +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextDecoration +import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun stringWithLink( + @StringRes textRes: Int, + url: String, + onLinkClick: (String) -> Unit, + @StringRes linkTextRes: Int = CommonStrings.action_learn_more, +) = buildAnnotatedString { + val learnMoreStr = stringResource(linkTextRes) + val fullText = stringResource(textRes, learnMoreStr) + append(fullText) + val learnMoreStartIndex = fullText.lastIndexOf(learnMoreStr) + addStyle( + style = SpanStyle( + textDecoration = TextDecoration.Underline, + fontWeight = FontWeight.Bold, + color = ElementTheme.colors.textPrimary + ), + start = learnMoreStartIndex, + end = learnMoreStartIndex + learnMoreStr.length, + ) + addLink( + url = LinkAnnotation.Url( + url = url, + linkInteractionListener = { + onLinkClick(url) + } + ), + start = learnMoreStartIndex, + end = learnMoreStartIndex + learnMoreStr.length, + ) +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/TextSyleToTypeface.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/TextSyleToTypeface.kt index 6608103909..6261c88e66 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/TextSyleToTypeface.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/TextSyleToTypeface.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/UnitConverters.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/UnitConverters.kt index eaf6712830..d0c7074402 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/UnitConverters.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/UnitConverters.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt index 8f6224eca9..06827fb218 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorAliases.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt index bb0fcd971b..7aa0ab79b9 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementThemeApp.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -19,7 +20,7 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.Theme import io.element.android.compound.theme.isDark import io.element.android.compound.theme.mapToTheme -import io.element.android.features.enterprise.api.EnterpriseService +import io.element.android.compound.tokens.generated.SemanticColors import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.preferences.api.store.AppPreferencesStore @@ -53,7 +54,8 @@ val LocalBuildMeta = staticCompositionLocalOf { @Composable fun ElementThemeApp( appPreferencesStore: AppPreferencesStore, - enterpriseService: EnterpriseService, + compoundLight: SemanticColors, + compoundDark: SemanticColors, buildMeta: BuildMeta, content: @Composable () -> Unit, ) { @@ -70,8 +72,6 @@ fun ElementThemeApp( } ) } - val compoundLight = remember { enterpriseService.semanticColorsLight() } - val compoundDark = remember { enterpriseService.semanticColorsDark() } CompositionLocalProvider( LocalBuildMeta provides buildMeta, ) { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementTypography.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementTypography.kt index 3e32b09fc0..6c3296d194 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementTypography.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementTypography.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/TypographyAliases.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/TypographyAliases.kt index e83a701acd..750e4d0d05 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/TypographyAliases.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/TypographyAliases.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt index 7811c26c4a..c9c553b7b9 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/AlertDialogContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -64,7 +65,7 @@ internal fun SimpleAlertDialogContent( content = { Text( text = content, - style = ElementTheme.materialTypography.bodyMedium, + style = ElementTheme.typography.fontBodyMdRegular, ) }, submitText = submitText, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/BottomSheetDragHandle.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/BottomSheetDragHandle.kt index 596c2480b2..3f8ca0144b 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/BottomSheetDragHandle.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/BottomSheetDragHandle.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/BottomSheetScaffold.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/BottomSheetScaffold.kt index d4db8923a0..721350e883 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/BottomSheetScaffold.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/BottomSheetScaffold.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt index 8b1ca52515..676d34ee3e 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Checkbox.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Checkbox.kt index c76afdbfed..a825738518 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Checkbox.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Checkbox.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/CircularProgressIndicator.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/CircularProgressIndicator.kt index f9e238e432..9bca6e5699 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/CircularProgressIndicator.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/CircularProgressIndicator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/DropdownMenu.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/DropdownMenu.kt index 6dc681b0e1..9a3e359a95 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/DropdownMenu.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/DropdownMenu.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,8 +11,10 @@ package io.element.android.libraries.designsystem.theme.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.window.PopupProperties @@ -24,22 +27,24 @@ fun DropdownMenu( expanded: Boolean, onDismissRequest: () -> Unit, modifier: Modifier = Modifier, - // By default add a 16.dp offset to the menu - offset: DpOffset = DpOffset(x = 16.dp, y = 0.dp), + offset: DpOffset = DpOffset(x = 0.dp, y = 0.dp), properties: PopupProperties = PopupProperties(focusable = true), + minWidth: Dp = DropdownMenuDefaults.minWidth, content: @Composable ColumnScope.() -> Unit ) { - // Note: the internal shape corner radius should be 8dp, but there is a 4p value hardcoded in the internal Surface component androidx.compose.material3.DropdownMenu( expanded = expanded, onDismissRequest = onDismissRequest, modifier = modifier - .background(color = ElementTheme.colors.bgCanvasDefault) - .widthIn(min = minMenuWidth), + .background(color = ElementTheme.colors.bgCanvasDefaultLevel1) + .widthIn(min = minWidth), + shape = RoundedCornerShape(8.dp), offset = offset, properties = properties, content = content ) } -private val minMenuWidth = 200.dp +object DropdownMenuDefaults { + val minWidth = 200.dp +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/DropdownMenuItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/DropdownMenuItem.kt index 41990c4cf4..31c3f3c49b 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/DropdownMenuItem.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/DropdownMenuItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/FilledTextField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/FilledTextField.kt index d2aa0bbac2..cbd25c1aeb 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/FilledTextField.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/FilledTextField.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/FloatingActionButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/FloatingActionButton.kt index aa32c27dd9..38eada2370 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/FloatingActionButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/FloatingActionButton.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/HorizontalDivider.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/HorizontalDivider.kt index 2e1ec4dc51..b4c7eddf31 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/HorizontalDivider.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/HorizontalDivider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt index f4487b34fa..f409761247 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Icon.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconButton.kt index 51797086fa..023d798fd3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconButton.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconColorButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconColorButton.kt index cc863bcf8d..1425d59bc3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconColorButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconColorButton.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt index 2c9a030439..af14f3511c 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/IconToggleButton.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/LinearProgressIndicator.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/LinearProgressIndicator.kt index 6336ff73ca..04f08166b7 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/LinearProgressIndicator.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/LinearProgressIndicator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt index 78195d4f98..0da086725d 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -113,7 +114,7 @@ fun ListItem( val decoratedHeadlineContent: @Composable () -> Unit = { CompositionLocalProvider( - LocalTextStyle provides ElementTheme.materialTypography.bodyLarge, + LocalTextStyle provides ElementTheme.typography.fontBodyLgRegular, LocalContentColor provides headlineColor, ) { headlineContent() @@ -122,7 +123,7 @@ fun ListItem( val decoratedSupportingContent: (@Composable () -> Unit)? = supportingContent?.let { content -> { CompositionLocalProvider( - LocalTextStyle provides ElementTheme.materialTypography.bodyMedium, + LocalTextStyle provides ElementTheme.typography.fontBodyMdRegular, LocalContentColor provides supportingColor, ) { content() diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSectionHeader.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSectionHeader.kt index d03cc4fe27..403ed6da97 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSectionHeader.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSectionHeader.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSupportingText.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSupportingText.kt index 558cbea653..4be53ddb39 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSupportingText.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ListSupportingText.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/MediumTopAppBar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/MediumTopAppBar.kt index 3b52158a37..a49a3595f7 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/MediumTopAppBar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/MediumTopAppBar.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -32,7 +33,7 @@ fun MediumTopAppBar( navigationIcon: @Composable () -> Unit = {}, actions: @Composable RowScope.() -> Unit = {}, windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, - colors: TopAppBarColors = TopAppBarDefaults.mediumTopAppBarColors(), + colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(), scrollBehavior: TopAppBarScrollBehavior? = null ) { androidx.compose.material3.MediumTopAppBar( 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 b362c729dc..3f70aab727 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBar.kt index 9d67f4ec05..b983d107ef 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBar.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarIcon.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarIcon.kt index 2c2890f854..7c6332bd51 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarIcon.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarIcon.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarItem.kt index 8c42b932dd..40407602a8 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarItem.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarItem.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarText.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarText.kt index 826c99f743..d84fddd0b4 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarText.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarText.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt index eb4f428622..dfb4758f65 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/RadioButton.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Scaffold.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Scaffold.kt index 64d5672b38..16d5f624cf 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Scaffold.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Scaffold.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt index bfceb2263e..c1fd7bc6f0 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,13 +14,13 @@ import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.SearchBar import androidx.compose.material3.SearchBarColors import androidx.compose.material3.SearchBarDefaults -import androidx.compose.material3.TextFieldColors import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable @@ -44,6 +45,9 @@ import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.ui.strings.CommonStrings +/** + * Ref: https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=1992-8350 + */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun SearchBar( @@ -62,14 +66,12 @@ fun SearchBar( interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, inactiveBarColors: SearchBarColors = ElementSearchBarDefaults.inactiveColors(), activeBarColors: SearchBarColors = ElementSearchBarDefaults.activeColors(), - inactiveTextInputColors: TextFieldColors = ElementSearchBarDefaults.inactiveInputFieldColors(), - activeTextInputColors: TextFieldColors = ElementSearchBarDefaults.activeInputFieldColors(), contentPrefix: @Composable ColumnScope.() -> Unit = {}, contentSuffix: @Composable ColumnScope.() -> Unit = {}, resultHandler: @Composable ColumnScope.(T) -> Unit = {}, ) { val focusManager = LocalFocusManager.current - + val colors = if (active) activeBarColors else inactiveBarColors val updatedOnQueryChange by rememberUpdatedState(onQueryChange) LaunchedEffect(active) { if (!active) { @@ -106,28 +108,25 @@ fun SearchBar( } } } - !active -> { { Icon( imageVector = CompoundIcons.Search(), contentDescription = stringResource(CommonStrings.action_search), - tint = ElementTheme.colors.iconTertiary, ) } } - else -> null }, interactionSource = interactionSource, - colors = if (active) activeTextInputColors else inactiveTextInputColors, + colors = colors.inputFieldColors, ) }, expanded = active, onExpandedChange = onActiveChange, modifier = modifier.padding(horizontal = if (!active) 16.dp else 0.dp), shape = shape, - colors = if (active) activeBarColors else inactiveBarColors, + colors = colors, tonalElevation = tonalElevation, windowInsets = windowInsets, content = { @@ -162,35 +161,43 @@ object ElementSearchBarDefaults { @OptIn(ExperimentalMaterial3Api::class) @Composable fun inactiveColors() = SearchBarDefaults.colors( - containerColor = ElementTheme.materialColors.surfaceVariant, - dividerColor = ElementTheme.materialColors.outline, + containerColor = Color.Transparent, + dividerColor = ElementTheme.colors.borderInteractivePrimary, + inputFieldColors = inactiveInputFieldColors(), ) @Composable fun inactiveInputFieldColors() = TextFieldDefaults.colors( unfocusedPlaceholderColor = ElementTheme.colors.textDisabled, focusedPlaceholderColor = ElementTheme.colors.textDisabled, - unfocusedLeadingIconColor = ElementTheme.materialColors.primary, - focusedLeadingIconColor = ElementTheme.materialColors.primary, - unfocusedTrailingIconColor = ElementTheme.materialColors.primary, - focusedTrailingIconColor = ElementTheme.materialColors.primary, + unfocusedTrailingIconColor = ElementTheme.colors.iconDisabled, + focusedTrailingIconColor = ElementTheme.colors.iconDisabled, + focusedContainerColor = ElementTheme.colors.bgSubtleSecondary, + unfocusedContainerColor = ElementTheme.colors.bgSubtleSecondary, + disabledContainerColor = ElementTheme.colors.bgSubtleSecondary, + errorContainerColor = ElementTheme.colors.bgSubtleSecondary, ) @OptIn(ExperimentalMaterial3Api::class) @Composable fun activeColors() = SearchBarDefaults.colors( containerColor = Color.Transparent, - dividerColor = ElementTheme.materialColors.outline, + dividerColor = ElementTheme.colors.borderInteractivePrimary, + inputFieldColors = activeInputFieldColors(), ) @Composable fun activeInputFieldColors() = TextFieldDefaults.colors( - unfocusedPlaceholderColor = ElementTheme.colors.textDisabled, - focusedPlaceholderColor = ElementTheme.colors.textDisabled, - unfocusedLeadingIconColor = ElementTheme.materialColors.primary, - focusedLeadingIconColor = ElementTheme.materialColors.primary, - unfocusedTrailingIconColor = ElementTheme.materialColors.primary, - focusedTrailingIconColor = ElementTheme.materialColors.primary, + unfocusedPlaceholderColor = ElementTheme.colors.textSecondary, + focusedPlaceholderColor = ElementTheme.colors.textSecondary, + unfocusedLeadingIconColor = ElementTheme.colors.iconPrimary, + focusedLeadingIconColor = ElementTheme.colors.iconPrimary, + unfocusedTrailingIconColor = ElementTheme.colors.iconTertiary, + focusedTrailingIconColor = ElementTheme.colors.iconTertiary, + focusedContainerColor = Color.Transparent, + unfocusedContainerColor = Color.Transparent, + disabledContainerColor = Color.Transparent, + errorContainerColor = Color.Transparent, ) } @@ -294,6 +301,7 @@ private fun ContentToPreview( resultHandler: @Composable ColumnScope.(String) -> Unit = {}, ) { SearchBar( + modifier = Modifier.heightIn(max = 200.dp), query = query, active = active, resultState = resultState, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchField.kt new file mode 100644 index 0000000000..fc84abe827 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchField.kt @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.designsystem.theme.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Arrangement.spacedBy +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage +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.ui.strings.CommonStrings + +/** + * https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=1985-3223 + */ +@Composable +fun SearchField( + value: String, + onValueChange: (String) -> Unit, + modifier: Modifier = Modifier, + placeholder: String? = null, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + val focusManager = LocalFocusManager.current + val isFocused by interactionSource.collectIsFocusedAsState() + BasicTextField( + value = value, + onValueChange = onValueChange, + modifier = modifier, + textStyle = textFieldStyle(), + singleLine = true, + interactionSource = interactionSource, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Search, + ), + keyboardActions = KeyboardActions( + onSearch = { + focusManager.clearFocus() + } + ), + cursorBrush = SolidColor(ElementTheme.colors.textActionAccent), + ) { innerTextField -> + DecorationBox( + isFocused = isFocused, + placeholder = placeholder, + isTextEmpty = value.isEmpty(), + innerTextField = innerTextField, + onClear = { onValueChange("") }, + ) + } +} + +@Composable +fun SearchField( + value: TextFieldValue, + onValueChange: (TextFieldValue) -> Unit, + modifier: Modifier = Modifier, + placeholder: String? = null, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + val focusManager = LocalFocusManager.current + val isFocused by interactionSource.collectIsFocusedAsState() + BasicTextField( + value = value, + onValueChange = onValueChange, + modifier = modifier, + textStyle = textFieldStyle(), + singleLine = true, + interactionSource = interactionSource, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Search, + ), + keyboardActions = KeyboardActions( + onSearch = { + focusManager.clearFocus() + } + ), + cursorBrush = SolidColor(ElementTheme.colors.textActionAccent), + ) { innerTextField -> + DecorationBox( + isFocused = isFocused, + placeholder = placeholder, + isTextEmpty = value.text.isEmpty(), + innerTextField = innerTextField, + onClear = { TextFieldValue() } + ) + } +} + +@Composable +private fun DecorationBox( + isFocused: Boolean, + placeholder: String?, + isTextEmpty: Boolean, + onClear: () -> Unit, + innerTextField: @Composable () -> Unit, +) { + SearchFieldContainer( + isFocused = isFocused, + ) { + Row(modifier = Modifier.padding(start = 16.dp), verticalAlignment = Alignment.CenterVertically) { + Box(modifier = Modifier.weight(1f)) { + if (placeholder != null && isTextEmpty) { + Text( + text = placeholder, + color = ElementTheme.colors.textSecondary, + style = ElementTheme.typography.fontBodyLgRegular, + ) + } + innerTextField() + } + Spacer(modifier = Modifier.width(16.dp)) + val showClearIcon = isFocused && !isTextEmpty + IconButton(onClick = onClear, enabled = showClearIcon) { + if (showClearIcon) { + Icon( + modifier = Modifier.background(ElementTheme.colors.iconSecondary, CircleShape), + imageVector = CompoundIcons.Close(), + contentDescription = stringResource(CommonStrings.action_clear), + tint = ElementTheme.colors.iconOnSolidPrimary, + ) + } else { + Icon( + imageVector = CompoundIcons.Search(), + contentDescription = null, + tint = ElementTheme.colors.iconTertiary, + ) + } + } + } + } +} + +@Composable +private fun SearchFieldContainer( + isFocused: Boolean, + modifier: Modifier = Modifier, + content: @Composable () -> Unit +) { + Surface( + modifier = modifier, + shape = RoundedCornerShape(99.dp), + border = BorderStroke( + width = 1.dp, + color = if (isFocused) { + ElementTheme.colors.borderInteractiveHovered + } else { + ElementTheme.colors.borderInteractiveSecondary + } + ), + color = ElementTheme.colors.bgSubtleSecondary, + content = content + ) +} + +@Composable +private fun textFieldStyle(): TextStyle { + return ElementTheme.typography.fontBodyLgRegular.copy( + color = ElementTheme.colors.textPrimary + ) +} + +@Preview(group = PreviewGroup.Search, heightDp = 1000) +@Composable +internal fun SearchFieldsLightPreview() = ElementPreviewLight { ContentToPreview() } + +@Preview(group = PreviewGroup.Search, heightDp = 1000) +@Composable +internal fun SearchFieldsDarkPreview() = ElementPreviewDark { ContentToPreview() } + +@Composable +@ExcludeFromCoverage +private fun ContentToPreview() { + Column( + modifier = Modifier.padding(8.dp), + verticalArrangement = spacedBy(8.dp) + ) { + SearchField( + onValueChange = {}, + placeholder = "Search", + value = "", + ) + SearchField( + onValueChange = {}, + placeholder = "Search", + value = "Search term", + ) + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SegmentedButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SegmentedButton.kt index 914c29a905..7fd195bd49 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SegmentedButton.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SegmentedButton.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Slider.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Slider.kt index 91a4870f37..7b6e6f3062 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Slider.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Slider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Snackbar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Snackbar.kt index 7a6e8f2b91..8c246a6593 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Snackbar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Snackbar.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Surface.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Surface.kt index 86a92eca99..134435f76c 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Surface.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Surface.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Switch.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Switch.kt index 2694634878..f4bbdcec72 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Switch.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Switch.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Text.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Text.kt index 1270e62e5d..e7f2a666f4 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Text.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Text.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt index d51e9745c2..5e0a81ba03 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TopAppBar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TopAppBar.kt index 3087f8f5f0..d6c876d243 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TopAppBar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TopAppBar.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/DatePickerPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/DatePickerPreview.kt index dbfb0b29a6..b5dfe42587 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/DatePickerPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/DatePickerPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/MenuPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/MenuPreview.kt index 6ae35532ea..f0bc0efbbf 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/MenuPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/MenuPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/TimePickerPreview.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/TimePickerPreview.kt index 57ecb9c967..f4562f1058 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/TimePickerPreview.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/previews/TimePickerPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/AnnotatedString.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/AnnotatedString.kt index 5002ab3c70..977262e341 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/AnnotatedString.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/AnnotatedString.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/BooleanProvider.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/BooleanProvider.kt index b64dbd4667..c50e20dd66 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/BooleanProvider.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/BooleanProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/CommonDrawables.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/CommonDrawables.kt index 5e533d3631..5a95f91378 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/CommonDrawables.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/CommonDrawables.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/DelayedVisibility.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/DelayedVisibility.kt new file mode 100644 index 0000000000..ce5a43a7a3 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/DelayedVisibility.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.designsystem.utils + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.movableContentOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalInspectionMode +import kotlinx.coroutines.delay +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds + +/** + * Displays the content of [block] after a delay of [duration]. + */ +@Composable +fun DelayedVisibility( + duration: Duration = 300.milliseconds, + block: @Composable () -> Unit, +) { + // Technically this shouldn't be needed because `LocalInspectionMode` won't change, but let's make the linter happy + val movableBlock = remember { movableContentOf { block() } } + if (LocalInspectionMode.current) { + // Just allow the contents to be displayed in the previews/screenshot tests + movableBlock() + } else { + var shouldDisplay by remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + delay(duration) + shouldDisplay = true + } + AnimatedVisibility(shouldDisplay) { + movableBlock() + } + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/DrawScope.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/DrawScope.kt index 4db7082720..ea6d6539da 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/DrawScope.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/DrawScope.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/Extensions.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/Extensions.kt index 27defe4bcd..b6b84f4190 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/Extensions.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/Extensions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceMaxBrightness.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceMaxBrightness.kt new file mode 100644 index 0000000000..b0c9e8cea7 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceMaxBrightness.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.designsystem.utils + +import androidx.activity.compose.LocalActivity +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import io.element.android.libraries.androidutils.system.setFullBrightness + +@Composable +fun ForceMaxBrightness() { + val activity = LocalActivity.current ?: return + DisposableEffect(Unit) { + activity.setFullBrightness(true) + onDispose { + activity.setFullBrightness(false) + } + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientation.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientation.kt index 37e6e1c93e..fb07ee1219 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientation.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientation.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientationInMobileDevices.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientationInMobileDevices.kt index 72de702a66..1445c38d4c 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientationInMobileDevices.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/ForceOrientationInMobileDevices.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/HideKeyboardWhenDisposed.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/HideKeyboardWhenDisposed.kt index 1ac7906914..1c9213e71c 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/HideKeyboardWhenDisposed.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/HideKeyboardWhenDisposed.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/KeepScreenOn.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/KeepScreenOn.kt index bd49bb696b..689154eab0 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/KeepScreenOn.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/KeepScreenOn.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/LazyListState.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/LazyListState.kt index c20cb5077f..f252c69b8f 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/LazyListState.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/LazyListState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/LocalUiTestMode.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/LocalUiTestMode.kt index 5c1288f0e7..6ea781dc03 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/LocalUiTestMode.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/LocalUiTestMode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/OnLifecycleEvent.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/OnLifecycleEvent.kt index 2e4d0018c8..1ad235d068 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/OnLifecycleEvent.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/OnLifecycleEvent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/OpenUrlInTabView.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/OpenUrlInTabView.kt index 9770c6849d..5ea182d0d6 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/OpenUrlInTabView.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/OpenUrlInTabView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/WindowInsetsExtension.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/WindowInsetsExtension.kt index a3a471875b..f7d2635cb3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/WindowInsetsExtension.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/WindowInsetsExtension.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarDispatcher.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarDispatcher.kt index 52fe5979e6..56bb070228 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarDispatcher.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarDispatcher.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarHost.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarHost.kt index ea41d3f4d7..a4cab70ccd 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarHost.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarHost.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarMessage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarMessage.kt index fb9be76f98..18b1fb650c 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarMessage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/snackbar/SnackbarMessage.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/designsystem/src/main/res/drawable/ic_winner.xml b/libraries/designsystem/src/main/res/drawable/ic_winner.xml index 33587d1849..6393f878ff 100644 --- a/libraries/designsystem/src/main/res/drawable/ic_winner.xml +++ b/libraries/designsystem/src/main/res/drawable/ic_winner.xml @@ -1,7 +1,8 @@ content.process(senderDisambiguatedDisplayName, isDmRoom, isOutgoing) RedactedContent -> { val message = sp.getString(CommonStrings.common_message_removed) @@ -79,7 +106,7 @@ class DefaultRoomLastMessageFormatter( roomMembershipContentFormatter.format(content, senderDisambiguatedDisplayName, isOutgoing) } is ProfileChangeContent -> { - profileChangeContentFormatter.format(content, event.sender, senderDisambiguatedDisplayName, isOutgoing) + profileChangeContentFormatter.format(content, senderId, senderDisambiguatedDisplayName, isOutgoing) } is StateContent -> { stateContentFormatter.format(content, senderDisambiguatedDisplayName, isOutgoing, RenderingMode.RoomList) diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt index 6cbe734733..9657f87bd0 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/DefaultTimelineEventFormatter.kt @@ -1,14 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.eventformatter.impl import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.SessionScope import io.element.android.libraries.eventformatter.api.TimelineEventFormatter @@ -34,7 +34,6 @@ import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.toolbox.api.strings.StringProvider @ContributesBinding(SessionScope::class) -@Inject class DefaultTimelineEventFormatter( private val sp: StringProvider, private val buildMeta: BuildMeta, diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/PrefixWith.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/PrefixWith.kt index c3ccd98d84..51fdccf256 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/PrefixWith.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/PrefixWith.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/ProfileChangeContentFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/ProfileChangeContentFormatter.kt index 4bf73325af..aa08fb9d8c 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/ProfileChangeContentFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/ProfileChangeContentFormatter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/RoomMembershipContentFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/RoomMembershipContentFormatter.kt index 7fb68ac167..9434d7e4d0 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/RoomMembershipContentFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/RoomMembershipContentFormatter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt index 6c6302f38b..f9d38fd8a7 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/StateContentFormatter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -153,7 +154,7 @@ class StateContentFormatter( "RoomHistoryVisibility" } } - OtherState.RoomJoinRules -> when (renderingMode) { + is OtherState.RoomJoinRules -> when (renderingMode) { RenderingMode.RoomList -> { Timber.v("Filtering timeline item for room state change: $content") null diff --git a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/mode/RenderingMode.kt b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/mode/RenderingMode.kt index 22c2ce5d96..22bde7db1c 100644 --- a/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/mode/RenderingMode.kt +++ b/libraries/eventformatter/impl/src/main/kotlin/io/element/android/libraries/eventformatter/impl/mode/RenderingMode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/eventformatter/impl/src/main/res/values-hr/translations.xml b/libraries/eventformatter/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..50ca735d50 --- /dev/null +++ b/libraries/eventformatter/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,73 @@ + + + "(promijenjen je i avatar)" + "%1$s promijenio/la je svoj avatar" + "Promijenili ste svoj avatar" + "%1$s degradiran/a je u člana" + "%1$s degradiran/a je u moderatora" + "%1$s promijenio/la je svoje ime za prikaz s %2$s na %3$s" + "Promijenili ste svoje ime za prikaz s %1$s na %2$s" + "%1$s uklonio/la je svoje ime za prikaz (bilo je %2$s)" + "Uklonili ste svoje ime za prikaz (bilo je %1$s)" + "%1$s postavio/la je svoje ime za prikaz na %2$s" + "Postavili ste svoje ime za prikaz na %1$s" + "%1$s promaknut/a je u administratora" + "%1$s promaknut/a je u moderatora" + "%1$s promijenio/la je avatar sobe" + "Promijenili ste avatar sobe" + "%1$s uklonio/la je avatar sobe" + "Uklonili ste avatar sobe" + "%1$s zabranio/la je pristup za %2$s" + "Zabranili ste pristup korisniku %1$s" + "Zabranili ste pristup korisniku %1$s: %2$s" + "%1$s zabranio/la je pristup korisniku %2$s: %3$s" + "%1$s stvorio/la je sobu" + "Stvorili ste sobu" + "%1$s je pozvao/la %2$s" + "%1$s prihvatio/la je poziv" + "Prihvatili ste poziv" + "Pozvali ste %1$s" + "%1$s vas je pozvao/la" + "%1$s pridružio/la se sobi" + "Pridružili ste se sobi" + "%1$s zatražio/la je pridruživanje sobi" + "Korisniku %1$s odobren je pristup sobi %2$s" + "Dopustili ste korisniku %1$s da se pridruži" + "Zatražili ste pridruživanje sobi" + "%1$s odbio/la je zahtjev za pridruživanje korisnika %2$s" + "Odbili ste zahtjev za pridruživanje korisnika %1$s" + "%1$s odbio/la je vaš zahtjev za pridruživanje" + "Korisnik %1$s više nije zainteresiran za pridruživanje" + "Otkazali ste zahtjev za pridruživanje" + "%1$s napustio/la je sobu" + "Napustili ste sobu" + "%1$s promijenio/la je naziv sobe u: %2$s" + "Promijenili ste naziv sobe u: %1$s" + "%1$s uklonio/la je naziv sobe" + "Uklonili ste naziv sobe" + "%1$s nije izvršio/la nikakve promjene" + "Niste izvršili nikakve promjene" + "%1$s promijenio/la je prikvačene poruke" + "Promijenili ste prikvačene poruke" + "%1$s prikvačio/la je poruku" + "Prikvačili ste poruku" + "%1$s otkvačio/la je poruku" + "Otkvačili ste poruku" + "%1$s odbio/la je poziv" + "Odbili ste poziv" + "%1$s uklonio/la je %2$s" + "Uklonili ste %1$s" + "Uklonili ste korisnika %1$s: %2$s" + "%1$s uklonio/la je korisnika %2$s: %3$s" + "%1$s poslao/la je pozivnicu za pridruživanje sobi korisniku %2$s" + "Poslali ste pozivnicu za pridruživanje sobi korisniku %1$s" + "%1$s povukao/la je poziv za pridruživanje sobi korisniku %2$s" + "Povukli ste poziv za pridruživanje sobi korisniku %1$s" + "%1$s promijenio/la je temu na: %2$s" + "Promijenili ste temu na: %1$s" + "%1$s uklonio/la je temu sobe" + "Uklonili ste temu sobe" + "%1$s uklonio/la je zabranu pristupa za %2$sban" + "Uklonili ste zabranu pristupa za %1$s" + "%1$s napravio/la je nepoznatu promjenu u svom članstvu" + diff --git a/libraries/eventformatter/impl/src/main/res/values-ru/translations.xml b/libraries/eventformatter/impl/src/main/res/values-ru/translations.xml index d0f4a39773..b551539feb 100644 --- a/libraries/eventformatter/impl/src/main/res/values-ru/translations.xml +++ b/libraries/eventformatter/impl/src/main/res/values-ru/translations.xml @@ -30,7 +30,7 @@ "Пользователь %1$s пригласил вас" "%1$s присоединился к комнате" "Вы присоединились к комнате" - "%1$s запросил присоединение" + "%1$s хочет присоединиться" "%1$s разрешил %2$s присоединиться" "Вы разрешили %1$s присоединиться" "Вы запросили присоединение" diff --git a/libraries/eventformatter/impl/src/main/res/values-uz/translations.xml b/libraries/eventformatter/impl/src/main/res/values-uz/translations.xml index 88a8186fec..11835dc605 100644 --- a/libraries/eventformatter/impl/src/main/res/values-uz/translations.xml +++ b/libraries/eventformatter/impl/src/main/res/values-uz/translations.xml @@ -19,6 +19,8 @@ "Siz xonani avatarini o\'chirib tashladingiz" "%1$staqiqlangan%2$s" "Siz taqiqlangansiz%1$s" + "Siz %1$s: %2$sni blokladingiz" + "%1$s %2$s:%3$sni blokladi" "%1$sxonani yaratdi" "Siz xonani yaratdingiz" "%1$staklif qilingan%2$s" @@ -55,6 +57,8 @@ "Siz taklifni rad etdingiz" "%1$s o\'chirildi %2$s" "Siz o\'chirildingiz %1$s" + "Siz olib tashladingiz %1$s :%2$s" + "%1$s %2$s:%3$sni olib tashladi" "%1$s taklifnoma yubordi %2$sga xonaga qo\'shilish uchun" "Siz taklifnoma yubordingiz %1$s ga xonaga qo\'shilishi uchun" "%1$s taklifni %2$s ga xonaga qo\'shilish uchun bekor qildi" diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt index 442357f386..b58bbb4b25 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultPinnedMessagesBannerFormatterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -43,7 +44,7 @@ import io.element.android.libraries.matrix.test.media.aMediaSource import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.test.timeline.aPollContent import io.element.android.libraries.matrix.test.timeline.aProfileChangeMessageContent -import io.element.android.libraries.matrix.test.timeline.aProfileTimelineDetails +import io.element.android.libraries.matrix.test.timeline.aProfileDetails import io.element.android.libraries.matrix.test.timeline.aStickerContent import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import io.element.android.libraries.matrix.test.timeline.item.event.aRoomMembershipContent @@ -102,7 +103,11 @@ class DefaultPinnedMessagesBannerFormatterTest { fun `Unable to decrypt content`() { val expected = "Waiting for this message" val senderName = "Someone" - val message = createRoomEvent(false, senderName, UnableToDecryptContent(UnableToDecryptContent.Data.Unknown)) + val message = createRoomEvent( + sentByYou = false, + senderDisplayName = senderName, + content = UnableToDecryptContent(data = UnableToDecryptContent.Data.Unknown, threadInfo = null) + ) val result = formatter.format(message) assertThat(result).isEqualTo(expected) } @@ -600,7 +605,7 @@ class DefaultPinnedMessagesBannerFormatterTest { OtherState.RoomCanonicalAlias, OtherState.RoomGuestAccess, OtherState.RoomHistoryVisibility, - OtherState.RoomJoinRules, + OtherState.RoomJoinRules(null), OtherState.RoomPinnedEvents(OtherState.RoomPinnedEvents.Change.CHANGED), OtherState.RoomUserPowerLevels(emptyMap()), OtherState.RoomServerAcl, @@ -777,7 +782,7 @@ class DefaultPinnedMessagesBannerFormatterTest { content: EventContent, ): EventTimelineItem { val sender = if (sentByYou) A_USER_ID else someoneElseId - val profile = aProfileTimelineDetails(senderDisplayName) + val profile = aProfileDetails(senderDisplayName) return anEventTimelineItem( content = content, senderProfile = profile, diff --git a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultBaseRoomLastMessageFormatterTest.kt b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLatestEventFormatterTest.kt similarity index 79% rename from libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultBaseRoomLastMessageFormatterTest.kt rename to libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLatestEventFormatterTest.kt index 8fb4ac0bf4..0da3134098 100644 --- a/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultBaseRoomLastMessageFormatterTest.kt +++ b/libraries/eventformatter/impl/src/test/kotlin/io/element/android/libraries/eventformatter/impl/DefaultRoomLatestEventFormatterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,10 +15,10 @@ import com.google.common.truth.Truth.assertWithMessage import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.matrix.api.roomlist.LatestEventValue import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType import io.element.android.libraries.matrix.api.timeline.item.event.EventContent -import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType @@ -42,11 +43,11 @@ import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.media.aMediaSource import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser +import io.element.android.libraries.matrix.test.room.aRemoteLatestEvent import io.element.android.libraries.matrix.test.timeline.aPollContent import io.element.android.libraries.matrix.test.timeline.aProfileChangeMessageContent -import io.element.android.libraries.matrix.test.timeline.aProfileTimelineDetails +import io.element.android.libraries.matrix.test.timeline.aProfileDetails import io.element.android.libraries.matrix.test.timeline.aStickerContent -import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import io.element.android.libraries.matrix.test.timeline.item.event.aRoomMembershipContent import io.element.android.services.toolbox.impl.strings.AndroidStringProvider import org.junit.Before @@ -58,17 +59,17 @@ import org.robolectric.annotation.Config @Suppress("LargeClass") @RunWith(RobolectricTestRunner::class) -class DefaultBaseRoomLastMessageFormatterTest { +class DefaultRoomLatestEventFormatterTest { private lateinit var context: Context private lateinit var fakeMatrixClient: FakeMatrixClient - private lateinit var formatter: DefaultRoomLastMessageFormatter + private lateinit var formatter: DefaultRoomLatestEventFormatter @Before fun setup() { context = RuntimeEnvironment.getApplication() as Context fakeMatrixClient = FakeMatrixClient() val stringProvider = AndroidStringProvider(context.resources) - formatter = DefaultRoomLastMessageFormatter( + formatter = DefaultRoomLatestEventFormatter( sp = AndroidStringProvider(context.resources), roomMembershipContentFormatter = RoomMembershipContentFormatter(fakeMatrixClient, stringProvider), profileChangeContentFormatter = ProfileChangeContentFormatter(stringProvider), @@ -83,7 +84,7 @@ class DefaultBaseRoomLastMessageFormatterTest { val expected = "Message removed" val senderName = "Someone" sequenceOf(false, true).forEach { isDm -> - val message = createRoomEvent(false, senderName, RedactedContent) + val message = createLatestEvent(false, senderName, RedactedContent) val result = formatter.format(message, isDm) if (isDm) { assertThat(result).isEqualTo(expected) @@ -99,7 +100,7 @@ class DefaultBaseRoomLastMessageFormatterTest { fun `Sticker content`() { val body = "a sticker body" val info = ImageInfo(null, null, null, null, null, null, null) - val message = createRoomEvent(false, null, aStickerContent(body, info, aMediaSource(url = "url"))) + val message = createLatestEvent(false, null, aStickerContent(body, info, aMediaSource(url = "url"))) val result = formatter.format(message, false) val expectedBody = someoneElseId.value + ": Sticker (a sticker body)" assertThat(result.toString()).isEqualTo(expectedBody) @@ -111,7 +112,11 @@ class DefaultBaseRoomLastMessageFormatterTest { val expected = "Waiting for this message" val senderName = "Someone" sequenceOf(false, true).forEach { isDm -> - val message = createRoomEvent(false, senderName, UnableToDecryptContent(UnableToDecryptContent.Data.Unknown)) + val message = createLatestEvent( + sentByYou = false, + senderDisplayName = senderName, + content = UnableToDecryptContent(data = UnableToDecryptContent.Data.Unknown, threadInfo = null), + ) val result = formatter.format(message, isDm) if (isDm) { assertThat(result).isEqualTo(expected) @@ -133,7 +138,7 @@ class DefaultBaseRoomLastMessageFormatterTest { FailedToParseStateContent("", "", ""), UnknownContent, ).forEach { type -> - val message = createRoomEvent(false, senderName, type) + val message = createLatestEvent(false, senderName, type) val result = formatter.format(message, isDm) if (isDm) { assertWithMessage("$type was not properly handled").that(result).isEqualTo(expected) @@ -197,7 +202,7 @@ class DefaultBaseRoomLastMessageFormatterTest { sequenceOf(false, true).forEach { isDm -> sharedContentMessagesTypes.forEach { type -> val content = createMessageContent(type) - val message = createRoomEvent(sentByYou = sentByYou, senderDisplayName = senderName, content = content) + val message = createLatestEvent(sentByYou = sentByYou, senderDisplayName = senderName, content = content) val result = formatter.format(message, isDmRoom = isDm) if (isDm) { resultsInDm.add(type to result) @@ -293,11 +298,11 @@ class DefaultBaseRoomLastMessageFormatterTest { val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.JOINED) val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.JOINED) - val youJoinedRoomEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) + val youJoinedRoomEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youJoinedRoom = formatter.format(youJoinedRoomEvent, false) assertThat(youJoinedRoom).isEqualTo("You joined the room") - val someoneJoinedRoomEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) + val someoneJoinedRoomEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) val someoneJoinedRoom = formatter.format(someoneJoinedRoomEvent, false) assertThat(someoneJoinedRoom).isEqualTo("$otherName joined the room") } @@ -309,11 +314,11 @@ class DefaultBaseRoomLastMessageFormatterTest { val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.LEFT) val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.LEFT) - val youLeftRoomEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) + val youLeftRoomEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youLeftRoom = formatter.format(youLeftRoomEvent, false) assertThat(youLeftRoom).isEqualTo("You left the room") - val someoneLeftRoomEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) + val someoneLeftRoomEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) val someoneLeftRoom = formatter.format(someoneLeftRoomEvent, false) assertThat(someoneLeftRoom).isEqualTo("$otherName left the room") } @@ -328,19 +333,19 @@ class DefaultBaseRoomLastMessageFormatterTest { val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.BANNED) val someoneKickedContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED_AND_BANNED) - val youBannedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) + val youBannedEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youBanned = formatter.format(youBannedEvent, false) assertThat(youBanned).isEqualTo("You banned $third") - val youKickBannedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youKickedContent) + val youKickBannedEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = youKickedContent) val youKickedBanned = formatter.format(youKickBannedEvent, false) assertThat(youKickedBanned).isEqualTo("You banned $third") - val someoneBannedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) + val someoneBannedEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) val someoneBanned = formatter.format(someoneBannedEvent, false) assertThat(someoneBanned).isEqualTo("$otherName banned $third") - val someoneKickBannedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneKickedContent) + val someoneKickBannedEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = someoneKickedContent) val someoneKickBanned = formatter.format(someoneKickBannedEvent, false) assertThat(someoneKickBanned).isEqualTo("$otherName banned $third") } @@ -355,19 +360,19 @@ class DefaultBaseRoomLastMessageFormatterTest { val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.BANNED, A_REASON) val someoneKickedContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED_AND_BANNED, A_REASON) - val youBannedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) + val youBannedEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youBanned = formatter.format(youBannedEvent, false) assertThat(youBanned).isEqualTo("You banned $third: $A_REASON") - val youKickBannedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youKickedContent) + val youKickBannedEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = youKickedContent) val youKickedBanned = formatter.format(youKickBannedEvent, false) assertThat(youKickedBanned).isEqualTo("You banned $third: $A_REASON") - val someoneBannedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) + val someoneBannedEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) val someoneBanned = formatter.format(someoneBannedEvent, false) assertThat(someoneBanned).isEqualTo("$otherName banned $third: $A_REASON") - val someoneKickBannedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneKickedContent) + val someoneKickBannedEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = someoneKickedContent) val someoneKickBanned = formatter.format(someoneKickBannedEvent, false) assertThat(someoneKickBanned).isEqualTo("$otherName banned $third: $A_REASON") } @@ -380,11 +385,11 @@ class DefaultBaseRoomLastMessageFormatterTest { val youContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.UNBANNED) val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.UNBANNED) - val youUnbannedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) + val youUnbannedEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youUnbanned = formatter.format(youUnbannedEvent, false) assertThat(youUnbanned).isEqualTo("You unbanned $third") - val someoneUnbannedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) + val someoneUnbannedEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) val someoneUnbanned = formatter.format(someoneUnbannedEvent, false) assertThat(someoneUnbanned).isEqualTo("$otherName unbanned $third") } @@ -397,11 +402,11 @@ class DefaultBaseRoomLastMessageFormatterTest { val youContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED) val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED) - val youKickedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) + val youKickedEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youKicked = formatter.format(youKickedEvent, false) assertThat(youKicked).isEqualTo("You removed $third") - val someoneKickedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) + val someoneKickedEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) val someoneKicked = formatter.format(someoneKickedEvent, false) assertThat(someoneKicked).isEqualTo("$otherName removed $third") } @@ -414,11 +419,11 @@ class DefaultBaseRoomLastMessageFormatterTest { val youContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED, A_REASON) val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KICKED, A_REASON) - val youKickedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) + val youKickedEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youKicked = formatter.format(youKickedEvent, false) assertThat(youKicked).isEqualTo("You removed $third: $A_REASON") - val someoneKickedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) + val someoneKickedEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) val someoneKicked = formatter.format(someoneKickedEvent, false) assertThat(someoneKicked).isEqualTo("$otherName removed $third: $A_REASON") } @@ -431,15 +436,15 @@ class DefaultBaseRoomLastMessageFormatterTest { val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.INVITED) val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.INVITED) - val youWereInvitedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = youContent) + val youWereInvitedEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = youContent) val youWereInvited = formatter.format(youWereInvitedEvent, false) assertThat(youWereInvited).isEqualTo("$otherName invited you") - val youInvitedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = someoneContent) + val youInvitedEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = someoneContent) val youInvited = formatter.format(youInvitedEvent, false) assertThat(youInvited).isEqualTo("You invited $third") - val someoneInvitedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) + val someoneInvitedEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) val someoneInvited = formatter.format(someoneInvitedEvent, false) assertThat(someoneInvited).isEqualTo("$otherName invited $third") } @@ -451,11 +456,11 @@ class DefaultBaseRoomLastMessageFormatterTest { val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.INVITATION_ACCEPTED) val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.INVITATION_ACCEPTED) - val youAcceptedInviteEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) + val youAcceptedInviteEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youAcceptedInvite = formatter.format(youAcceptedInviteEvent, false) assertThat(youAcceptedInvite).isEqualTo("You accepted the invite") - val someoneAcceptedInviteEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) + val someoneAcceptedInviteEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) val someoneAcceptedInvite = formatter.format(someoneAcceptedInviteEvent, false) assertThat(someoneAcceptedInvite).isEqualTo("$otherName accepted the invite") } @@ -467,11 +472,11 @@ class DefaultBaseRoomLastMessageFormatterTest { val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.INVITATION_REJECTED) val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.INVITATION_REJECTED) - val youRejectedInviteEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) + val youRejectedInviteEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youRejectedInvite = formatter.format(youRejectedInviteEvent, false) assertThat(youRejectedInvite).isEqualTo("You rejected the invitation") - val someoneRejectedInviteEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) + val someoneRejectedInviteEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) val someoneRejectedInvite = formatter.format(someoneRejectedInviteEvent, false) assertThat(someoneRejectedInvite).isEqualTo("$otherName rejected the invitation") } @@ -483,11 +488,11 @@ class DefaultBaseRoomLastMessageFormatterTest { val third = "Someone" val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.INVITATION_REVOKED) - val youRevokedInviteEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = someoneContent) + val youRevokedInviteEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = someoneContent) val youRevokedInvite = formatter.format(youRevokedInviteEvent, false) assertThat(youRevokedInvite).isEqualTo("You revoked the invitation for $third to join the room") - val someoneRevokedInviteEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) + val someoneRevokedInviteEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) val someoneRevokedInvite = formatter.format(someoneRevokedInviteEvent, false) assertThat(someoneRevokedInvite).isEqualTo("$otherName revoked the invitation for $third to join the room") } @@ -499,11 +504,11 @@ class DefaultBaseRoomLastMessageFormatterTest { val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.KNOCKED) val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.KNOCKED) - val youKnockedEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) + val youKnockedEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youKnocked = formatter.format(youKnockedEvent, false) assertThat(youKnocked).isEqualTo("You requested to join") - val someoneKnockedEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) + val someoneKnockedEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) val someoneKnocked = formatter.format(someoneKnockedEvent, false) assertThat(someoneKnocked).isEqualTo("$otherName is requesting to join") } @@ -515,11 +520,11 @@ class DefaultBaseRoomLastMessageFormatterTest { val third = "Someone" val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KNOCK_ACCEPTED) - val youAcceptedKnockEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = someoneContent) + val youAcceptedKnockEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = someoneContent) val youAcceptedKnock = formatter.format(youAcceptedKnockEvent, false) assertThat(youAcceptedKnock).isEqualTo("You allowed $third to join") - val someoneAcceptedKnockEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) + val someoneAcceptedKnockEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) val someoneAcceptedKnock = formatter.format(someoneAcceptedKnockEvent, false) assertThat(someoneAcceptedKnock).isEqualTo("$otherName granted access to $third") } @@ -531,11 +536,11 @@ class DefaultBaseRoomLastMessageFormatterTest { val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.KNOCK_RETRACTED) val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), null, MembershipChange.KNOCK_RETRACTED) - val youRetractedKnockEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) + val youRetractedKnockEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youRetractedKnock = formatter.format(youRetractedKnockEvent, false) assertThat(youRetractedKnock).isEqualTo("You cancelled your request to join") - val someoneRetractedKnockEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) + val someoneRetractedKnockEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) val someoneRetractedKnock = formatter.format(someoneRetractedKnockEvent, false) assertThat(someoneRetractedKnock).isEqualTo("$otherName is no longer interested in joining") } @@ -548,15 +553,15 @@ class DefaultBaseRoomLastMessageFormatterTest { val youContent = aRoomMembershipContent(A_USER_ID, third, MembershipChange.KNOCK_DENIED) val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), third, MembershipChange.KNOCK_DENIED) - val youDeniedKnockEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = someoneContent) + val youDeniedKnockEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = someoneContent) val youDeniedKnock = formatter.format(youDeniedKnockEvent, false) assertThat(youDeniedKnock).isEqualTo("You rejected $third's request to join") - val someoneDeniedKnockEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) + val someoneDeniedKnockEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) val someoneDeniedKnock = formatter.format(someoneDeniedKnockEvent, false) assertThat(someoneDeniedKnock).isEqualTo("$otherName rejected $third's request to join") - val someoneDeniedYourKnockEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = youContent) + val someoneDeniedYourKnockEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = youContent) val someoneDeniedYourKnock = formatter.format(someoneDeniedYourKnockEvent, false) assertThat(someoneDeniedYourKnock).isEqualTo("$otherName rejected your request to join") } @@ -568,11 +573,11 @@ class DefaultBaseRoomLastMessageFormatterTest { val youContent = aRoomMembershipContent(A_USER_ID, null, MembershipChange.NONE) val someoneContent = aRoomMembershipContent(UserId("@someone_else:domain"), otherName, MembershipChange.NONE) - val youNoneRoomEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = youContent) + val youNoneRoomEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = youContent) val youNoneRoom = formatter.format(youNoneRoomEvent, false) assertThat(youNoneRoom).isEqualTo("You made no changes") - val someoneNoneRoomEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) + val someoneNoneRoomEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = someoneContent) val someoneNoneRoom = formatter.format(someoneNoneRoomEvent, false) assertThat(someoneNoneRoom).isEqualTo("$otherName made no changes") } @@ -584,7 +589,7 @@ class DefaultBaseRoomLastMessageFormatterTest { val results = otherChanges.map { change -> val content = aRoomMembershipContent(A_USER_ID, null, change) - val event = createRoomEvent(sentByYou = false, senderDisplayName = "Someone", content = content) + val event = createLatestEvent(sentByYou = false, senderDisplayName = "Someone", content = content) val result = formatter.format(event, false) change to result } @@ -603,19 +608,19 @@ class DefaultBaseRoomLastMessageFormatterTest { val changedContent = StateContent("", OtherState.RoomAvatar("new_avatar")) val removedContent = StateContent("", OtherState.RoomAvatar(null)) - val youChangedRoomAvatarEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = changedContent) + val youChangedRoomAvatarEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = changedContent) val youChangedRoomAvatar = formatter.format(youChangedRoomAvatarEvent, false) assertThat(youChangedRoomAvatar).isEqualTo("You changed the room avatar") - val someoneChangedRoomAvatarEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = changedContent) + val someoneChangedRoomAvatarEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = changedContent) val someoneChangedRoomAvatar = formatter.format(someoneChangedRoomAvatarEvent, false) assertThat(someoneChangedRoomAvatar).isEqualTo("$otherName changed the room avatar") - val youRemovedRoomAvatarEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = removedContent) + val youRemovedRoomAvatarEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = removedContent) val youRemovedRoomAvatar = formatter.format(youRemovedRoomAvatarEvent, false) assertThat(youRemovedRoomAvatar).isEqualTo("You removed the room avatar") - val someoneRemovedRoomAvatarEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = removedContent) + val someoneRemovedRoomAvatarEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = removedContent) val someoneRemovedRoomAvatar = formatter.format(someoneRemovedRoomAvatarEvent, false) assertThat(someoneRemovedRoomAvatar).isEqualTo("$otherName removed the room avatar") } @@ -626,11 +631,11 @@ class DefaultBaseRoomLastMessageFormatterTest { val otherName = "Other" val content = StateContent("", OtherState.RoomCreate) - val youCreatedRoomMessage = createRoomEvent(sentByYou = true, senderDisplayName = null, content = content) + val youCreatedRoomMessage = createLatestEvent(sentByYou = true, senderDisplayName = null, content = content) val youCreatedRoom = formatter.format(youCreatedRoomMessage, false) assertThat(youCreatedRoom).isEqualTo("You created the room") - val someoneCreatedRoomEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = content) + val someoneCreatedRoomEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = content) val someoneCreatedRoom = formatter.format(someoneCreatedRoomEvent, false) assertThat(someoneCreatedRoom).isEqualTo("$otherName created the room") } @@ -641,11 +646,11 @@ class DefaultBaseRoomLastMessageFormatterTest { val otherName = "Other" val content = StateContent("", OtherState.RoomEncryption) - val youCreatedRoomMessage = createRoomEvent(sentByYou = true, senderDisplayName = null, content = content) + val youCreatedRoomMessage = createLatestEvent(sentByYou = true, senderDisplayName = null, content = content) val youCreatedRoom = formatter.format(youCreatedRoomMessage, false) assertThat(youCreatedRoom).isEqualTo("Encryption enabled") - val someoneCreatedRoomEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = content) + val someoneCreatedRoomEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = content) val someoneCreatedRoom = formatter.format(someoneCreatedRoomEvent, false) assertThat(someoneCreatedRoom).isEqualTo("Encryption enabled") } @@ -658,19 +663,19 @@ class DefaultBaseRoomLastMessageFormatterTest { val changedContent = StateContent("", OtherState.RoomName(newName)) val removedContent = StateContent("", OtherState.RoomName(null)) - val youChangedRoomNameEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = changedContent) + val youChangedRoomNameEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = changedContent) val youChangedRoomName = formatter.format(youChangedRoomNameEvent, false) assertThat(youChangedRoomName).isEqualTo("You changed the room name to: $newName") - val someoneChangedRoomNameEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = changedContent) + val someoneChangedRoomNameEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = changedContent) val someoneChangedRoomName = formatter.format(someoneChangedRoomNameEvent, false) assertThat(someoneChangedRoomName).isEqualTo("$otherName changed the room name to: $newName") - val youRemovedRoomNameEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = removedContent) + val youRemovedRoomNameEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = removedContent) val youRemovedRoomName = formatter.format(youRemovedRoomNameEvent, false) assertThat(youRemovedRoomName).isEqualTo("You removed the room name") - val someoneRemovedRoomNameEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = removedContent) + val someoneRemovedRoomNameEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = removedContent) val someoneRemovedRoomName = formatter.format(someoneRemovedRoomNameEvent, false) assertThat(someoneRemovedRoomName).isEqualTo("$otherName removed the room name") } @@ -683,19 +688,19 @@ class DefaultBaseRoomLastMessageFormatterTest { val changedContent = StateContent("", OtherState.RoomThirdPartyInvite(inviteeName)) val removedContent = StateContent("", OtherState.RoomThirdPartyInvite(null)) - val youInvitedSomeoneEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = changedContent) + val youInvitedSomeoneEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = changedContent) val youInvitedSomeone = formatter.format(youInvitedSomeoneEvent, false) assertThat(youInvitedSomeone).isEqualTo("You sent an invitation to $inviteeName to join the room") - val someoneInvitedSomeoneEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = changedContent) + val someoneInvitedSomeoneEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = changedContent) val someoneInvitedSomeone = formatter.format(someoneInvitedSomeoneEvent, false) assertThat(someoneInvitedSomeone).isEqualTo("$otherName sent an invitation to $inviteeName to join the room") - val youInvitedNoOneEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = removedContent) + val youInvitedNoOneEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = removedContent) val youInvitedNoOne = formatter.format(youInvitedNoOneEvent, false) assertThat(youInvitedNoOne).isNull() - val someoneInvitedNoOneEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = removedContent) + val someoneInvitedNoOneEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = removedContent) val someoneInvitedNoOne = formatter.format(someoneInvitedNoOneEvent, false) assertThat(someoneInvitedNoOne).isNull() } @@ -709,27 +714,27 @@ class DefaultBaseRoomLastMessageFormatterTest { val removedContent = StateContent("", OtherState.RoomTopic(null)) val blankContent = StateContent("", OtherState.RoomTopic("")) - val youChangedRoomTopicEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = changedContent) + val youChangedRoomTopicEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = changedContent) val youChangedRoomTopic = formatter.format(youChangedRoomTopicEvent, false) assertThat(youChangedRoomTopic).isEqualTo("You changed the topic to: $roomTopic") - val someoneChangedRoomTopicEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = changedContent) + val someoneChangedRoomTopicEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = changedContent) val someoneChangedRoomTopic = formatter.format(someoneChangedRoomTopicEvent, false) assertThat(someoneChangedRoomTopic).isEqualTo("$otherName changed the topic to: $roomTopic") - val youRemovedRoomTopicEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = removedContent) + val youRemovedRoomTopicEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = removedContent) val youRemovedRoomTopic = formatter.format(youRemovedRoomTopicEvent, false) assertThat(youRemovedRoomTopic).isEqualTo("You removed the room topic") - val someoneRemovedRoomTopicEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = removedContent) + val someoneRemovedRoomTopicEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = removedContent) val someoneRemovedRoomTopic = formatter.format(someoneRemovedRoomTopicEvent, false) assertThat(someoneRemovedRoomTopic).isEqualTo("$otherName removed the room topic") - val youSetBlankRoomTopicEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = blankContent) + val youSetBlankRoomTopicEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = blankContent) val youSetBlankRoomTopic = formatter.format(youSetBlankRoomTopicEvent, false) assertThat(youSetBlankRoomTopic).isEqualTo("You removed the room topic") - val someoneSetBlankRoomTopicEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = blankContent) + val someoneSetBlankRoomTopicEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = blankContent) val someoneSetBlankRoomTopic = formatter.format(someoneSetBlankRoomTopicEvent, false) assertThat(someoneSetBlankRoomTopic).isEqualTo("$otherName removed the room topic") } @@ -745,7 +750,7 @@ class DefaultBaseRoomLastMessageFormatterTest { OtherState.RoomCanonicalAlias, OtherState.RoomGuestAccess, OtherState.RoomHistoryVisibility, - OtherState.RoomJoinRules, + OtherState.RoomJoinRules(null), OtherState.RoomPinnedEvents(OtherState.RoomPinnedEvents.Change.CHANGED), OtherState.RoomUserPowerLevels(emptyMap()), OtherState.RoomServerAcl, @@ -757,7 +762,7 @@ class DefaultBaseRoomLastMessageFormatterTest { val results = otherStates.map { state -> val content = StateContent("", state) - val event = createRoomEvent(sentByYou = false, senderDisplayName = "Someone", content = content) + val event = createLatestEvent(sentByYou = false, senderDisplayName = "Someone", content = content) val result = formatter.format(event, false) state to result } @@ -779,35 +784,35 @@ class DefaultBaseRoomLastMessageFormatterTest { val invalidContent = aProfileChangeMessageContent(avatarUrl = null, prevAvatarUrl = null) val sameContent = aProfileChangeMessageContent(avatarUrl = "same_avatar_url", prevAvatarUrl = "same_avatar_url") - val youChangedAvatarEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = changedContent) + val youChangedAvatarEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = changedContent) val youChangedAvatar = formatter.format(youChangedAvatarEvent, false) assertThat(youChangedAvatar).isEqualTo("You changed your avatar") - val someoneChangeAvatarEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = changedContent) + val someoneChangeAvatarEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = changedContent) val someoneChangeAvatar = formatter.format(someoneChangeAvatarEvent, false) assertThat(someoneChangeAvatar).isEqualTo("$otherName changed their avatar") - val youSetAvatarEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = setContent) + val youSetAvatarEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = setContent) val youSetAvatar = formatter.format(youSetAvatarEvent, false) assertThat(youSetAvatar).isEqualTo("You changed your avatar") - val someoneSetAvatarEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = setContent) + val someoneSetAvatarEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = setContent) val someoneSetAvatar = formatter.format(someoneSetAvatarEvent, false) assertThat(someoneSetAvatar).isEqualTo("$otherName changed their avatar") - val youRemovedAvatarEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = removedContent) + val youRemovedAvatarEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = removedContent) val youRemovedAvatar = formatter.format(youRemovedAvatarEvent, false) assertThat(youRemovedAvatar).isEqualTo("You changed your avatar") - val someoneRemovedAvatarEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = removedContent) + val someoneRemovedAvatarEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = removedContent) val someoneRemovedAvatar = formatter.format(someoneRemovedAvatarEvent, false) assertThat(someoneRemovedAvatar).isEqualTo("$otherName changed their avatar") - val unchangedEvent = createRoomEvent(sentByYou = true, senderDisplayName = otherName, content = sameContent) + val unchangedEvent = createLatestEvent(sentByYou = true, senderDisplayName = otherName, content = sameContent) val unchangedResult = formatter.format(unchangedEvent, false) assertThat(unchangedResult).isNull() - val invalidEvent = createRoomEvent(sentByYou = true, senderDisplayName = otherName, content = invalidContent) + val invalidEvent = createLatestEvent(sentByYou = true, senderDisplayName = otherName, content = invalidContent) val invalidResult = formatter.format(invalidEvent, false) assertThat(invalidResult).isNull() } @@ -824,35 +829,35 @@ class DefaultBaseRoomLastMessageFormatterTest { val sameContent = aProfileChangeMessageContent(displayName = newDisplayName, prevDisplayName = newDisplayName) val invalidContent = aProfileChangeMessageContent(displayName = null, prevDisplayName = null) - val youChangedDisplayNameEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = changedContent) + val youChangedDisplayNameEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = changedContent) val youChangedDisplayName = formatter.format(youChangedDisplayNameEvent, false) assertThat(youChangedDisplayName).isEqualTo("You changed your display name from $oldDisplayName to $newDisplayName") - val someoneChangedDisplayNameEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = changedContent) + val someoneChangedDisplayNameEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = changedContent) val someoneChangedDisplayName = formatter.format(someoneChangedDisplayNameEvent, false) assertThat(someoneChangedDisplayName).isEqualTo("$someoneElseId changed their display name from $oldDisplayName to $newDisplayName") - val youSetDisplayNameEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = setContent) + val youSetDisplayNameEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = setContent) val youSetDisplayName = formatter.format(youSetDisplayNameEvent, false) assertThat(youSetDisplayName).isEqualTo("You set your display name to $newDisplayName") - val someoneSetDisplayNameEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = setContent) + val someoneSetDisplayNameEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = setContent) val someoneSetDisplayName = formatter.format(someoneSetDisplayNameEvent, false) assertThat(someoneSetDisplayName).isEqualTo("$someoneElseId set their display name to $newDisplayName") - val youRemovedDisplayNameEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = removedContent) + val youRemovedDisplayNameEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = removedContent) val youRemovedDisplayName = formatter.format(youRemovedDisplayNameEvent, false) assertThat(youRemovedDisplayName).isEqualTo("You removed your display name (it was $oldDisplayName)") - val someoneRemovedDisplayNameEvent = createRoomEvent(sentByYou = false, senderDisplayName = otherName, content = removedContent) + val someoneRemovedDisplayNameEvent = createLatestEvent(sentByYou = false, senderDisplayName = otherName, content = removedContent) val someoneRemovedDisplayName = formatter.format(someoneRemovedDisplayNameEvent, false) assertThat(someoneRemovedDisplayName).isEqualTo("$someoneElseId removed their display name (it was $oldDisplayName)") - val unchangedEvent = createRoomEvent(sentByYou = true, senderDisplayName = otherName, content = sameContent) + val unchangedEvent = createLatestEvent(sentByYou = true, senderDisplayName = otherName, content = sameContent) val unchangedResult = formatter.format(unchangedEvent, false) assertThat(unchangedResult).isNull() - val invalidEvent = createRoomEvent(sentByYou = true, senderDisplayName = otherName, content = invalidContent) + val invalidEvent = createLatestEvent(sentByYou = true, senderDisplayName = otherName, content = invalidContent) val invalidResult = formatter.format(invalidEvent, false) assertThat(invalidResult).isNull() } @@ -881,15 +886,15 @@ class DefaultBaseRoomLastMessageFormatterTest { prevAvatarUrl = "same_avatar_url", ) - val youChangedBothEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = changedContent) + val youChangedBothEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = changedContent) val youChangedBoth = formatter.format(youChangedBothEvent, false) assertThat(youChangedBoth).isEqualTo("You changed your display name from $oldDisplayName to $newDisplayName\n(avatar was changed too)") - val invalidContentEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = invalidContent) + val invalidContentEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = invalidContent) val invalidMessage = formatter.format(invalidContentEvent, false) assertThat(invalidMessage).isNull() - val sameContentEvent = createRoomEvent(sentByYou = true, senderDisplayName = null, content = sameContent) + val sameContentEvent = createLatestEvent(sentByYou = true, senderDisplayName = null, content = sameContent) val sameMessage = formatter.format(sameContentEvent, false) assertThat(sameMessage).isNull() } @@ -903,10 +908,10 @@ class DefaultBaseRoomLastMessageFormatterTest { fun `Computes last message for poll in DM`() { val pollContent = aPollContent() - val mineContentEvent = createRoomEvent(sentByYou = true, senderDisplayName = "Alice", content = pollContent) + val mineContentEvent = createLatestEvent(sentByYou = true, senderDisplayName = "Alice", content = pollContent) assertThat(formatter.format(mineContentEvent, true)).isEqualTo("Poll: Do you like polls?") - val contentEvent = createRoomEvent(sentByYou = false, senderDisplayName = "Bob", content = pollContent) + val contentEvent = createLatestEvent(sentByYou = false, senderDisplayName = "Bob", content = pollContent) assertThat(formatter.format(contentEvent, true)).isEqualTo("Poll: Do you like polls?") } @@ -915,26 +920,26 @@ class DefaultBaseRoomLastMessageFormatterTest { fun `Computes last message for poll in room`() { val pollContent = aPollContent() - val mineContentEvent = createRoomEvent(sentByYou = true, senderDisplayName = "Alice", content = pollContent) + val mineContentEvent = createLatestEvent(sentByYou = true, senderDisplayName = "Alice", content = pollContent) assertThat(formatter.format(mineContentEvent, false).toString()).isEqualTo("You: Poll: Do you like polls?") - val contentEvent = createRoomEvent(sentByYou = false, senderDisplayName = "Bob", content = pollContent) + val contentEvent = createLatestEvent(sentByYou = false, senderDisplayName = "Bob", content = pollContent) assertThat(formatter.format(contentEvent, false).toString()).isEqualTo("Bob: Poll: Do you like polls?") } // endregion - private fun createRoomEvent( + private fun createLatestEvent( sentByYou: Boolean, senderDisplayName: String?, content: EventContent, - ): EventTimelineItem { + ): LatestEventValue.Remote { val sender = if (sentByYou) A_USER_ID else someoneElseId - val profile = aProfileTimelineDetails(senderDisplayName) - return anEventTimelineItem( - content = content, + val profile = aProfileDetails(senderDisplayName) + return aRemoteLatestEvent( + senderId = sender, senderProfile = profile, - sender = sender, + content = content, isOwn = sentByYou, ) } diff --git a/libraries/eventformatter/test/build.gradle.kts b/libraries/eventformatter/test/build.gradle.kts index 1d0df1e245..7a72cf0575 100644 --- a/libraries/eventformatter/test/build.gradle.kts +++ b/libraries/eventformatter/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakePinnedMessagesBannerFormatter.kt b/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakePinnedMessagesBannerFormatter.kt index 3313976d25..19698a7630 100644 --- a/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakePinnedMessagesBannerFormatter.kt +++ b/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakePinnedMessagesBannerFormatter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLastMessageFormatter.kt b/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLastMessageFormatter.kt deleted file mode 100644 index 9739b55526..0000000000 --- a/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLastMessageFormatter.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.eventformatter.test - -import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter -import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem - -class FakeRoomLastMessageFormatter : RoomLastMessageFormatter { - private var result: CharSequence? = null - - override fun format(event: EventTimelineItem, isDmRoom: Boolean): CharSequence? { - return result - } - - fun givenFormatResult(result: CharSequence?) { - this.result = result - } -} diff --git a/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLatestEventFormatter.kt b/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLatestEventFormatter.kt new file mode 100644 index 0000000000..5e1a056a6c --- /dev/null +++ b/libraries/eventformatter/test/src/main/kotlin/io/element/android/libraries/eventformatter/test/FakeRoomLatestEventFormatter.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.eventformatter.test + +import io.element.android.libraries.eventformatter.api.RoomLatestEventFormatter +import io.element.android.libraries.matrix.api.roomlist.LatestEventValue + +class FakeRoomLatestEventFormatter : RoomLatestEventFormatter { + private var result: CharSequence? = null + + override fun format(latestEvent: LatestEventValue.Local, isDmRoom: Boolean): CharSequence? { + return result + } + + override fun format(latestEvent: LatestEventValue.Remote, isDmRoom: Boolean): CharSequence? { + return result + } + + fun givenFormatResult(result: CharSequence?) { + this.result = result + } +} diff --git a/libraries/featureflag/api/build.gradle.kts b/libraries/featureflag/api/build.gradle.kts index 9664ac9f09..b34e082cd3 100644 --- a/libraries/featureflag/api/build.gradle.kts +++ b/libraries/featureflag/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/Feature.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/Feature.kt index 18a9ea1bc3..395fd04c6c 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/Feature.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/Feature.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlagService.kt b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlagService.kt index 1358909771..6a8cb2fd9d 100644 --- a/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlagService.kt +++ b/libraries/featureflag/api/src/main/kotlin/io/element/android/libraries/featureflag/api/FeatureFlagService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -35,7 +36,12 @@ interface FeatureFlagService { suspend fun setFeatureEnabled(feature: Feature, enabled: Boolean): Boolean /** - * @return the list of available (not finished) features that can be toggled. + * @return the list of available features that can be toggled. + * @param includeFinishedFeatures whether to include finished features, default is false + * @param isInLabs whether the user is in labs (to include lab features), default is false */ - fun getAvailableFeatures(): List + fun getAvailableFeatures( + includeFinishedFeatures: Boolean = false, + isInLabs: Boolean = false, + ): List } 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 cb908dbe5e..f7227a9ac9 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -73,6 +74,13 @@ enum class FeatureFlags( key = "feature.space", title = "Spaces", defaultValue = { true }, + isFinished = true, + ), + SpaceSettings( + key = "feature.spaceSettings", + title = "Space settings", + description = "Allow managing space settings such as details, permissions and privacy.", + defaultValue = { false }, isFinished = false, ), PrintLogsToLogcat( @@ -109,4 +117,19 @@ enum class FeatureFlags( defaultValue = { false }, isFinished = false, ), + SyncNotificationsWithWorkManager( + key = "feature.sync_notifications_with_workmanager", + title = "Sync notifications with WorkManager", + description = "Use WorkManager to schedule notification sync tasks when a push is received." + + " This should improve reliability and battery usage.", + defaultValue = { true }, + isFinished = false, + ), + QrCodeLogin( + key = "feature.qr_code_login", + title = "QR Code Login", + description = "Allow logging in on other devices using a QR code.", + defaultValue = { false }, + isFinished = false, + ), } diff --git a/libraries/featureflag/impl/build.gradle.kts b/libraries/featureflag/impl/build.gradle.kts index c54f95a293..63d968467a 100644 --- a/libraries/featureflag/impl/build.gradle.kts +++ b/libraries/featureflag/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -31,4 +32,5 @@ dependencies { testCommonDependencies(libs) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.featureflag.test) } diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt index 01c1e8a258..f7361b69b1 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeatureFlagService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,40 +10,42 @@ package io.element.android.libraries.featureflag.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.featureflag.api.Feature import io.element.android.libraries.featureflag.api.FeatureFlagService -import io.element.android.libraries.featureflag.api.FeatureFlags import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) -@Inject class DefaultFeatureFlagService( private val providers: Set<@JvmSuppressWildcards FeatureFlagProvider>, private val buildMeta: BuildMeta, + private val featuresProvider: FeaturesProvider, ) : FeatureFlagService { override fun isFeatureEnabledFlow(feature: Feature): Flow { return providers.filter { it.hasFeature(feature) } - .sortedByDescending(FeatureFlagProvider::priority) - .firstOrNull() + .maxByOrNull(FeatureFlagProvider::priority) ?.isFeatureEnabledFlow(feature) ?: flowOf(feature.defaultValue(buildMeta)) } override suspend fun setFeatureEnabled(feature: Feature, enabled: Boolean): Boolean { return providers.filterIsInstance() - .sortedBy(FeatureFlagProvider::priority) - .firstOrNull() + .maxByOrNull(FeatureFlagProvider::priority) ?.setFeatureEnabled(feature, enabled) ?.let { true } ?: false } - override fun getAvailableFeatures(): List { - return FeatureFlags.entries.filter { !it.isFinished } + override fun getAvailableFeatures( + includeFinishedFeatures: Boolean, + isInLabs: Boolean, + ): List { + return featuresProvider.provide().filter { flag -> + (includeFinishedFeatures || !flag.isFinished) && + flag.isInLabs == isInLabs + } } } diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/FeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/FeatureFlagProvider.kt index e0ddbb52cb..e3172fc8a0 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/FeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/FeatureFlagProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/FeaturesProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/FeaturesProvider.kt new file mode 100644 index 0000000000..e24ae66783 --- /dev/null +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/FeaturesProvider.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.featureflag.impl + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.featureflag.api.Feature +import io.element.android.libraries.featureflag.api.FeatureFlags + +fun interface FeaturesProvider { + fun provide(): List +} + +@ContributesBinding(AppScope::class) +class DefaultFeaturesProvider : FeaturesProvider { + override fun provide(): List = FeatureFlags.entries +} diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/MutableFeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/MutableFeatureFlagProvider.kt index 0eede7ac4a..48ed2c3701 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/MutableFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/MutableFeatureFlagProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/PreferencesFeatureFlagProvider.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/PreferencesFeatureFlagProvider.kt index f477d2ea0d..679bd9ce6f 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/PreferencesFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/PreferencesFeatureFlagProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt index 137d2d3ab1..de3f8aa519 100644 --- a/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt +++ b/libraries/featureflag/impl/src/main/kotlin/io/element/android/libraries/featureflag/impl/di/FeatureFlagModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 097e318609..00bc687bb9 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,26 +10,47 @@ package io.element.android.libraries.featureflag.impl import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.featureflag.api.Feature +import io.element.android.libraries.featureflag.test.FakeFeature import io.element.android.libraries.matrix.test.core.aBuildMeta import kotlinx.coroutines.test.runTest import org.junit.Test class DefaultFeatureFlagServiceTest { + private val aFeature = FakeFeature( + key = "test_feature", + title = "Test Feature", + ) + @Test fun `given service without provider when feature is checked then it returns the default value`() = runTest { + val featureWithDefaultToFalse = FakeFeature( + key = "test_feature", + title = "Test Feature", + defaultValue = { false } + ) + val featureWithDefaultToTrue = FakeFeature( + key = "test_feature_2", + title = "Test Feature 2", + defaultValue = { true } + ) val buildMeta = aBuildMeta() - val featureFlagService = DefaultFeatureFlagService(emptySet(), buildMeta) - featureFlagService.isFeatureEnabledFlow(FeatureFlags.Space).test { - assertThat(awaitItem()).isEqualTo(FeatureFlags.Space.defaultValue(buildMeta)) + val featureFlagService = createDefaultFeatureFlagService(buildMeta = buildMeta) + featureFlagService.isFeatureEnabledFlow(featureWithDefaultToFalse).test { + assertThat(awaitItem()).isFalse() + cancelAndIgnoreRemainingEvents() + } + featureFlagService.isFeatureEnabledFlow(featureWithDefaultToTrue).test { + assertThat(awaitItem()).isTrue() cancelAndIgnoreRemainingEvents() } } @Test fun `given service without provider when set enabled feature is called then it returns false`() = runTest { - val featureFlagService = DefaultFeatureFlagService(emptySet(), aBuildMeta()) - val result = featureFlagService.setFeatureEnabled(FeatureFlags.Space, true) + val featureFlagService = createDefaultFeatureFlagService() + val result = featureFlagService.setFeatureEnabled(aFeature, true) assertThat(result).isFalse() } @@ -36,8 +58,11 @@ class DefaultFeatureFlagServiceTest { fun `given service with a runtime provider when set enabled feature is called then it returns true`() = runTest { val buildMeta = aBuildMeta() val featureFlagProvider = FakeMutableFeatureFlagProvider(0, buildMeta) - val featureFlagService = DefaultFeatureFlagService(setOf(featureFlagProvider), buildMeta) - val result = featureFlagService.setFeatureEnabled(FeatureFlags.Space, true) + val featureFlagService = createDefaultFeatureFlagService( + providers = setOf(featureFlagProvider), + buildMeta = buildMeta, + ) + val result = featureFlagService.setFeatureEnabled(aFeature, true) assertThat(result).isTrue() } @@ -45,11 +70,14 @@ class DefaultFeatureFlagServiceTest { fun `given service with a runtime provider and feature enabled when feature is checked then it returns the correct value`() = runTest { val buildMeta = aBuildMeta() val featureFlagProvider = FakeMutableFeatureFlagProvider(0, buildMeta) - val featureFlagService = DefaultFeatureFlagService(setOf(featureFlagProvider), buildMeta) - featureFlagService.setFeatureEnabled(FeatureFlags.Space, true) - featureFlagService.isFeatureEnabledFlow(FeatureFlags.Space).test { + val featureFlagService = createDefaultFeatureFlagService( + providers = setOf(featureFlagProvider), + buildMeta = buildMeta + ) + featureFlagService.setFeatureEnabled(aFeature, true) + featureFlagService.isFeatureEnabledFlow(aFeature).test { assertThat(awaitItem()).isTrue() - featureFlagService.setFeatureEnabled(FeatureFlags.Space, false) + featureFlagService.setFeatureEnabled(aFeature, false) assertThat(awaitItem()).isFalse() } } @@ -59,11 +87,84 @@ class DefaultFeatureFlagServiceTest { val buildMeta = aBuildMeta() val lowPriorityFeatureFlagProvider = FakeMutableFeatureFlagProvider(LOW_PRIORITY, buildMeta) val highPriorityFeatureFlagProvider = FakeMutableFeatureFlagProvider(HIGH_PRIORITY, buildMeta) - val featureFlagService = DefaultFeatureFlagService(setOf(lowPriorityFeatureFlagProvider, highPriorityFeatureFlagProvider), buildMeta) - lowPriorityFeatureFlagProvider.setFeatureEnabled(FeatureFlags.Space, false) - highPriorityFeatureFlagProvider.setFeatureEnabled(FeatureFlags.Space, true) - featureFlagService.isFeatureEnabledFlow(FeatureFlags.Space).test { + val featureFlagService = createDefaultFeatureFlagService( + providers = setOf(lowPriorityFeatureFlagProvider, highPriorityFeatureFlagProvider), + buildMeta = buildMeta + ) + lowPriorityFeatureFlagProvider.setFeatureEnabled(aFeature, false) + highPriorityFeatureFlagProvider.setFeatureEnabled(aFeature, true) + featureFlagService.isFeatureEnabledFlow(aFeature).test { assertThat(awaitItem()).isTrue() } } + + @Test + fun `getAvailableFeatures should return expected features`() { + val aFinishedLabFeature = FakeFeature( + key = "finished_lab_feature", + title = "Finished Lab Feature", + isFinished = true, + isInLabs = true, + ) + val aFinishedDevFeature = FakeFeature( + key = "finished_dev_feature", + title = "Finished Dev Feature", + isFinished = true, + isInLabs = false, + ) + val anUnfinishedLabFeature = FakeFeature( + key = "unfinished_lab_feature", + title = "Unfinished Lab Feature", + isFinished = false, + isInLabs = true, + ) + val anUnfinishedDevFeature = FakeFeature( + key = "unfinished_dev_feature", + title = "Unfinished Dev Feature", + isFinished = false, + isInLabs = false, + ) + val featureFlagService = createDefaultFeatureFlagService( + features = listOf( + aFinishedLabFeature, + aFinishedDevFeature, + anUnfinishedLabFeature, + anUnfinishedDevFeature, + ), + ) + assertThat( + featureFlagService.getAvailableFeatures( + includeFinishedFeatures = false, + isInLabs = true, + ) + ).containsExactly(anUnfinishedLabFeature) + assertThat( + featureFlagService.getAvailableFeatures( + includeFinishedFeatures = true, + isInLabs = true, + ) + ).containsExactly(aFinishedLabFeature, anUnfinishedLabFeature) + assertThat( + featureFlagService.getAvailableFeatures( + includeFinishedFeatures = false, + isInLabs = false, + ) + ).containsExactly(anUnfinishedDevFeature) + assertThat( + featureFlagService.getAvailableFeatures( + includeFinishedFeatures = true, + isInLabs = false, + ) + ).containsExactly(aFinishedDevFeature, anUnfinishedDevFeature) + } } + +private fun createDefaultFeatureFlagService( + providers: Set = emptySet(), + buildMeta: BuildMeta = aBuildMeta(), + features: List = emptyList(), +) = DefaultFeatureFlagService( + providers = providers, + buildMeta = buildMeta, + featuresProvider = { features } +) diff --git a/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeaturesProviderTest.kt b/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeaturesProviderTest.kt new file mode 100644 index 0000000000..66d772920a --- /dev/null +++ b/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/DefaultFeaturesProviderTest.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.featureflag.impl + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.featureflag.api.FeatureFlags +import org.junit.Test + +class DefaultFeaturesProviderTest { + @Test + fun `provide should return all features`() { + val provider = DefaultFeaturesProvider() + val features = provider.provide() + assertThat(features.size).isEqualTo(FeatureFlags.entries.size) + FeatureFlags.entries.forEach { + assertThat(features.contains(it)).isTrue() + } + } +} diff --git a/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/FakeMutableFeatureFlagProvider.kt b/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/FakeMutableFeatureFlagProvider.kt index 1018c2ea19..13f1f30d43 100644 --- a/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/FakeMutableFeatureFlagProvider.kt +++ b/libraries/featureflag/impl/src/test/kotlin/io/element/android/libraries/featureflag/impl/FakeMutableFeatureFlagProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/featureflag/test/build.gradle.kts b/libraries/featureflag/test/build.gradle.kts index e2920a07b7..2572131b22 100644 --- a/libraries/featureflag/test/build.gradle.kts +++ b/libraries/featureflag/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,11 +12,11 @@ plugins { android { namespace = "io.element.android.libraries.featureflag.test" - - dependencies { - api(projects.libraries.featureflag.api) - implementation(projects.libraries.core) - implementation(projects.libraries.matrix.test) - implementation(libs.coroutines.core) - } +} + +dependencies { + api(projects.libraries.featureflag.api) + implementation(projects.libraries.core) + implementation(projects.libraries.matrix.test) + implementation(libs.coroutines.core) } diff --git a/libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeature.kt b/libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeature.kt index a141fbdd75..c8ba9f4b25 100644 --- a/libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeature.kt +++ b/libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeature.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeatureFlagService.kt b/libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeatureFlagService.kt index 49fc095ed4..eee8ee4559 100644 --- a/libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeatureFlagService.kt +++ b/libraries/featureflag/test/src/main/java/io/element/android/libraries/featureflag/test/FakeFeatureFlagService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,7 +18,7 @@ import kotlinx.coroutines.flow.MutableStateFlow class FakeFeatureFlagService( initialState: Map = emptyMap(), private val buildMeta: BuildMeta = aBuildMeta(), - var providedAvailableFeatures: List = emptyList(), + private val getAvailableFeaturesResult: (Boolean, Boolean) -> List = { _, _ -> emptyList() }, ) : FeatureFlagService { private val enabledFeatures = initialState .mapValues { MutableStateFlow(it.value) } @@ -33,7 +34,10 @@ class FakeFeatureFlagService( return enabledFeatures.getOrPut(feature.key) { MutableStateFlow(feature.defaultValue(buildMeta)) } } - override fun getAvailableFeatures(): List { - return providedAvailableFeatures + override fun getAvailableFeatures( + includeFinishedFeatures: Boolean, + isInLabs: Boolean, + ): List { + return getAvailableFeaturesResult(includeFinishedFeatures, isInLabs) } } diff --git a/libraries/featureflag/ui/build.gradle.kts b/libraries/featureflag/ui/build.gradle.kts index 32fd4f6eab..085c6193ec 100644 --- a/libraries/featureflag/ui/build.gradle.kts +++ b/libraries/featureflag/ui/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/FeatureListView.kt b/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/FeatureListView.kt index fb9e85b4f9..bd6d883440 100644 --- a/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/FeatureListView.kt +++ b/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/FeatureListView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/model/FeatureUiModel.kt b/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/model/FeatureUiModel.kt index f37e096c5b..d3ec3151ae 100644 --- a/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/model/FeatureUiModel.kt +++ b/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/model/FeatureUiModel.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/model/FeatureUiModelProvider.kt b/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/model/FeatureUiModelProvider.kt index 66697b447f..63bc118f7c 100644 --- a/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/model/FeatureUiModelProvider.kt +++ b/libraries/featureflag/ui/src/main/kotlin/io/element/android/libraries/featureflag/ui/model/FeatureUiModelProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/fullscreenintent/api/build.gradle.kts b/libraries/fullscreenintent/api/build.gradle.kts index 4bd0a50cb7..8d6be454ea 100644 --- a/libraries/fullscreenintent/api/build.gradle.kts +++ b/libraries/fullscreenintent/api/build.gradle.kts @@ -1,12 +1,13 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { - id("io.element.android-library") + id("io.element.android-compose-library") } android { diff --git a/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsEvents.kt b/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsEvents.kt index e0b8433749..1c5bc4b6a0 100644 --- a/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsEvents.kt +++ b/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsState.kt b/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsState.kt index 5b3f84f977..5b1ddb91ed 100644 --- a/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsState.kt +++ b/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsStateProvider.kt b/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsStateProvider.kt index 25ab2ab522..7248a24414 100644 --- a/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsStateProvider.kt +++ b/libraries/fullscreenintent/api/src/main/kotlin/io/element/android/libraries/fullscreenintent/api/FullScreenIntentPermissionsStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/fullscreenintent/impl/build.gradle.kts b/libraries/fullscreenintent/impl/build.gradle.kts index 2ccb7f80b8..57c4f5b836 100644 --- a/libraries/fullscreenintent/impl/build.gradle.kts +++ b/libraries/fullscreenintent/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/fullscreenintent/impl/src/main/kotlin/io/element/android/libraries/fullscreenintent/impl/FullScreenIntentPermissionsPresenter.kt b/libraries/fullscreenintent/impl/src/main/kotlin/io/element/android/libraries/fullscreenintent/impl/FullScreenIntentPermissionsPresenter.kt index b05aad303c..d4a3e75d1a 100644 --- a/libraries/fullscreenintent/impl/src/main/kotlin/io/element/android/libraries/fullscreenintent/impl/FullScreenIntentPermissionsPresenter.kt +++ b/libraries/fullscreenintent/impl/src/main/kotlin/io/element/android/libraries/fullscreenintent/impl/FullScreenIntentPermissionsPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -63,7 +64,7 @@ class FullScreenIntentPermissionsPresenter( val isGranted = notificationManagerCompat.canUseFullScreenIntent() val isBannerDismissed by isFullScreenIntentBannerDismissed.collectAsState(initial = true) - fun handleEvents(event: FullScreenIntentPermissionsEvents) { + fun handleEvent(event: FullScreenIntentPermissionsEvents) { when (event) { FullScreenIntentPermissionsEvents.Dismiss -> coroutineScope.launch { dismissFullScreenIntentBanner() @@ -75,7 +76,7 @@ class FullScreenIntentPermissionsPresenter( return FullScreenIntentPermissionsState( permissionGranted = isGranted, shouldDisplayBanner = !isBannerDismissed && !isGranted, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } @@ -87,7 +88,7 @@ class FullScreenIntentPermissionsPresenter( "package:${buildMeta.applicationId}".toUri() ) externalIntentLauncher.launch(intent) - } catch (e: ActivityNotFoundException) { + } catch (_: ActivityNotFoundException) { val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) .putExtra(Settings.EXTRA_APP_PACKAGE, buildMeta.applicationId) externalIntentLauncher.launch(intent) diff --git a/libraries/fullscreenintent/impl/src/main/kotlin/io/element/android/libraries/fullscreenintent/impl/di/FullScreenIntentModule.kt b/libraries/fullscreenintent/impl/src/main/kotlin/io/element/android/libraries/fullscreenintent/impl/di/FullScreenIntentModule.kt index f1c6f1efe4..249485b15c 100644 --- a/libraries/fullscreenintent/impl/src/main/kotlin/io/element/android/libraries/fullscreenintent/impl/di/FullScreenIntentModule.kt +++ b/libraries/fullscreenintent/impl/src/main/kotlin/io/element/android/libraries/fullscreenintent/impl/di/FullScreenIntentModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/fullscreenintent/impl/src/test/kotlin/io/element/android/libraries/fullscreenintent/test/FullScreenIntentPermissionsPresenterTest.kt b/libraries/fullscreenintent/impl/src/test/kotlin/io/element/android/libraries/fullscreenintent/test/FullScreenIntentPermissionsPresenterTest.kt index 0d10032da0..44a5579e86 100644 --- a/libraries/fullscreenintent/impl/src/test/kotlin/io/element/android/libraries/fullscreenintent/test/FullScreenIntentPermissionsPresenterTest.kt +++ b/libraries/fullscreenintent/impl/src/test/kotlin/io/element/android/libraries/fullscreenintent/test/FullScreenIntentPermissionsPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/indicator/api/build.gradle.kts b/libraries/indicator/api/build.gradle.kts index 5bc1ac42d9..c6f33ec83f 100644 --- a/libraries/indicator/api/build.gradle.kts +++ b/libraries/indicator/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/indicator/api/src/main/kotlin/io/element/android/libraries/indicator/api/IndicatorService.kt b/libraries/indicator/api/src/main/kotlin/io/element/android/libraries/indicator/api/IndicatorService.kt index b2f8b5eff8..0409a0f846 100644 --- a/libraries/indicator/api/src/main/kotlin/io/element/android/libraries/indicator/api/IndicatorService.kt +++ b/libraries/indicator/api/src/main/kotlin/io/element/android/libraries/indicator/api/IndicatorService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/indicator/impl/build.gradle.kts b/libraries/indicator/impl/build.gradle.kts index 2596b9fe67..8f1e9ea56d 100644 --- a/libraries/indicator/impl/build.gradle.kts +++ b/libraries/indicator/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/indicator/impl/src/main/kotlin/io/element/android/libraries/indicator/impl/DefaultIndicatorService.kt b/libraries/indicator/impl/src/main/kotlin/io/element/android/libraries/indicator/impl/DefaultIndicatorService.kt index 5cd17418b3..0629799c74 100644 --- a/libraries/indicator/impl/src/main/kotlin/io/element/android/libraries/indicator/impl/DefaultIndicatorService.kt +++ b/libraries/indicator/impl/src/main/kotlin/io/element/android/libraries/indicator/impl/DefaultIndicatorService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,7 +15,6 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.SessionScope import io.element.android.libraries.indicator.api.IndicatorService import io.element.android.libraries.matrix.api.encryption.BackupState @@ -23,7 +23,6 @@ import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.verification.SessionVerificationService @ContributesBinding(SessionScope::class) -@Inject class DefaultIndicatorService( private val sessionVerificationService: SessionVerificationService, private val encryptionService: EncryptionService, diff --git a/libraries/indicator/impl/src/test/kotlin/io/element/android/libraries/indicator/impl/DefaultIndicatorServiceTest.kt b/libraries/indicator/impl/src/test/kotlin/io/element/android/libraries/indicator/impl/DefaultIndicatorServiceTest.kt index 9e325a6573..cd861ef530 100644 --- a/libraries/indicator/impl/src/test/kotlin/io/element/android/libraries/indicator/impl/DefaultIndicatorServiceTest.kt +++ b/libraries/indicator/impl/src/test/kotlin/io/element/android/libraries/indicator/impl/DefaultIndicatorServiceTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/indicator/test/build.gradle.kts b/libraries/indicator/test/build.gradle.kts index 5d61dde8be..a5ce0c57c2 100644 --- a/libraries/indicator/test/build.gradle.kts +++ b/libraries/indicator/test/build.gradle.kts @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/indicator/test/src/main/kotlin/io/element/android/libraries/indicator/test/FakeIndicatorService.kt b/libraries/indicator/test/src/main/kotlin/io/element/android/libraries/indicator/test/FakeIndicatorService.kt index 26f8547b66..76cca70736 100644 --- a/libraries/indicator/test/src/main/kotlin/io/element/android/libraries/indicator/test/FakeIndicatorService.kt +++ b/libraries/indicator/test/src/main/kotlin/io/element/android/libraries/indicator/test/FakeIndicatorService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/maplibre-compose/build.gradle.kts b/libraries/maplibre-compose/build.gradle.kts index 756a09c32c..5552aae37f 100644 --- a/libraries/maplibre-compose/build.gradle.kts +++ b/libraries/maplibre-compose/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/CameraMode.kt b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/CameraMode.kt index 533ff777a5..8ef02f5bbc 100644 --- a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/CameraMode.kt +++ b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/CameraMode.kt @@ -1,8 +1,9 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * Copyright 2021 Google LLC * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/CameraMoveStartedReason.kt b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/CameraMoveStartedReason.kt index b2237ee35d..2683de1655 100644 --- a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/CameraMoveStartedReason.kt +++ b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/CameraMoveStartedReason.kt @@ -1,8 +1,9 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * Copyright 2021 Google LLC * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/CameraPositionState.kt b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/CameraPositionState.kt index b5b46ca847..1999526718 100644 --- a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/CameraPositionState.kt +++ b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/CameraPositionState.kt @@ -1,8 +1,9 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * Copyright 2021 Google LLC * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -156,8 +157,8 @@ public class CameraPositionState( /** * The default saver implementation for [CameraPositionState]. */ - public val Saver: Saver = Saver( - save = { SaveableCameraPositionState(it.position, it.cameraMode.toInternal()) }, + public val Saver: Saver = Saver( + save = { SaveableCameraPositionData(it.position, it.cameraMode.toInternal()) }, restore = { CameraPositionState(it.position, CameraMode.fromInternal(it.cameraMode)) } ) } @@ -172,7 +173,7 @@ public val currentCameraPositionState: CameraPositionState get() = LocalCameraPositionState.current @Parcelize -public data class SaveableCameraPositionState( +public data class SaveableCameraPositionData( val position: CameraPosition, val cameraMode: Int ) : Parcelable diff --git a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/IconAnchor.kt b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/IconAnchor.kt index ccb423f32f..e46dcdcf65 100644 --- a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/IconAnchor.kt +++ b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/IconAnchor.kt @@ -1,8 +1,9 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * Copyright 2021 Google LLC * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapApplier.kt b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapApplier.kt index df79e7e1c5..f8fd64c537 100644 --- a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapApplier.kt +++ b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapApplier.kt @@ -1,8 +1,9 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * Copyright 2021 Google LLC * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapLibreMap.kt b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapLibreMap.kt index f1ef0f0459..62c29fbd04 100644 --- a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapLibreMap.kt +++ b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapLibreMap.kt @@ -1,8 +1,9 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * Copyright 2021 Google LLC * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -71,10 +72,7 @@ public fun MapLibreMap( uiSettings: MapUiSettings = DefaultMapUiSettings, symbolManagerSettings: MapSymbolManagerSettings = DefaultMapSymbolManagerSettings, locationSettings: MapLocationSettings = DefaultMapLocationSettings, - content: ( - @Composable @MapLibreMapComposable - () -> Unit - )? = null, + content: (@Composable @MapLibreMapComposable () -> Unit)? = null, ) { // When in preview, early return a Box with the received modifier preserving layout if (LocalInspectionMode.current) { diff --git a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapLibreMapComposable.kt b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapLibreMapComposable.kt index cf88191580..c819dee711 100644 --- a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapLibreMapComposable.kt +++ b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapLibreMapComposable.kt @@ -1,8 +1,9 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * Copyright 2021 Google LLC * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 0cf7147530..7fb777aeba 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 @@ -1,8 +1,9 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * Copyright 2021 Google LLC * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapSymbolManagerSettings.kt b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapSymbolManagerSettings.kt index a073aed720..93c7b2118b 100644 --- a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapSymbolManagerSettings.kt +++ b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapSymbolManagerSettings.kt @@ -1,8 +1,9 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * Copyright 2021 Google LLC * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapUiSettings.kt b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapUiSettings.kt index 5e5c763f44..edee9b4dc5 100644 --- a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapUiSettings.kt +++ b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/MapUiSettings.kt @@ -1,8 +1,9 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * Copyright 2021 Google LLC * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 396d59df51..a07a596fe3 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 @@ -1,8 +1,9 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * Copyright 2021 Google LLC * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @file:Suppress("MatchingDeclarationName") diff --git a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/Symbol.kt b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/Symbol.kt index 2ebb5b10d3..e6a5c3f632 100644 --- a/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/Symbol.kt +++ b/libraries/maplibre-compose/src/main/kotlin/io/element/android/libraries/maplibre/compose/Symbol.kt @@ -1,8 +1,9 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * Copyright 2021 Google LLC * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/build.gradle.kts b/libraries/matrix/api/build.gradle.kts index 04a45654b2..1c70006cc8 100644 --- a/libraries/matrix/api/build.gradle.kts +++ b/libraries/matrix/api/build.gradle.kts @@ -1,12 +1,12 @@ import config.BuildTimeConfig import extension.buildConfigFieldStr -import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,8 +16,6 @@ plugins { alias(libs.plugins.kotlin.serialization) } -setupDependencyInjection() - android { namespace = "io.element.android.libraries.matrix.api" diff --git a/libraries/matrix/api/src/main/AndroidManifest.xml b/libraries/matrix/api/src/main/AndroidManifest.xml index 5c0a832239..608f16cc36 100644 --- a/libraries/matrix/api/src/main/AndroidManifest.xml +++ b/libraries/matrix/api/src/main/AndroidManifest.xml @@ -1,7 +1,8 @@ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 5f116612b1..dca269453a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -1,14 +1,17 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.api import io.element.android.libraries.core.data.tryOrNull +import io.element.android.libraries.matrix.api.analytics.SdkStoreSizes import io.element.android.libraries.matrix.api.core.DeviceId +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.MatrixPatterns import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId @@ -17,6 +20,8 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters import io.element.android.libraries.matrix.api.encryption.EncryptionService +import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopHandler +import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileHandler import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.media.MediaPreviewService import io.element.android.libraries.matrix.api.notification.NotificationService @@ -34,6 +39,7 @@ import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.spaces.SpaceService import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion import io.element.android.libraries.matrix.api.sync.SyncService +import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.verification.SessionVerificationService @@ -78,6 +84,7 @@ interface MatrixClient { suspend fun joinRoomByIdOrAlias(roomIdOrAlias: RoomIdOrAlias, serverNames: List): Result suspend fun knockRoom(roomIdOrAlias: RoomIdOrAlias, message: String, serverNames: List): Result suspend fun getCacheSize(): Long + suspend fun getDatabaseSizes(): Result /** * Will close the client and delete the cache data. @@ -183,6 +190,31 @@ interface MatrixClient { * Adds an emoji to the list of recent emoji reactions for this account. */ suspend fun addRecentEmoji(emoji: String): Result + + /** + * Marks the room with the provided [roomId] as read, sending a fully read receipt for [eventId]. + * + * This method should be used with caution as providing the [eventId] ourselves can result in incorrect read receipts. + * Use [Timeline.markAsRead] instead when possible. + */ + suspend fun markRoomAsFullyRead(roomId: RoomId, eventId: EventId): Result + + /** + * Check if linking a new device using QrCode is supported by the server. + */ + suspend fun canLinkNewDevice(): Result + + /** + * Create a handler to link a new mobile device, i.e. a device capable of scanning QrCodes. + */ + fun createLinkMobileHandler(): Result + + /** + * Create a handler to link a new desktop device, i.e. a device not capable of scanning QrCodes. + */ + fun createLinkDesktopHandler(): Result + + suspend fun performDatabaseVacuum(): Result } /** diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClientProvider.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClientProvider.kt index fb8b2f4d5f..54f70845fa 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClientProvider.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClientProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/SdkMetadata.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/SdkMetadata.kt index 34cf895366..16f34807fc 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/SdkMetadata.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/SdkMetadata.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/GetDatabaseSizesUseCase.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/GetDatabaseSizesUseCase.kt new file mode 100644 index 0000000000..ab11374116 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/GetDatabaseSizesUseCase.kt @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.analytics + +import io.element.android.libraries.matrix.api.core.SessionId + +fun interface GetDatabaseSizesUseCase { + suspend operator fun invoke(sessionId: SessionId): Result +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/SdkStoreSizes.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/SdkStoreSizes.kt new file mode 100644 index 0000000000..62b68b8fdb --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/SdkStoreSizes.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.analytics + +import io.element.android.libraries.core.data.ByteSize + +/** + * The sizes of the different stores (DBs) in the SDK. + */ +data class SdkStoreSizes( + val stateStore: ByteSize?, + val eventCacheStore: ByteSize?, + val mediaStore: ByteSize?, + val cryptoStore: ByteSize?, +) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/ViewRoomExt.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/ViewRoomExt.kt index ec5a3d8431..ac3b0c8e3a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/ViewRoomExt.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/ViewRoomExt.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCode.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCode.kt index 0e58e41872..b6053cce4a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCode.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 03e8d57150..c50ec09609 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 @@ -1,16 +1,21 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.api.auth -sealed class AuthenticationException(message: String) : Exception(message) { - class AccountAlreadyLoggedIn(userId: String) : AuthenticationException(userId) - class InvalidServerName(message: String) : AuthenticationException(message) - class SlidingSyncVersion(message: String) : AuthenticationException(message) - class Oidc(message: String) : AuthenticationException(message) - class Generic(message: String) : AuthenticationException(message) +sealed class AuthenticationException(message: String?) : Exception(message) { + data class AccountAlreadyLoggedIn( + val userId: String, + ) : AuthenticationException(null) + + class InvalidServerName(message: String?) : AuthenticationException(message) + class SlidingSyncVersion(message: String?) : AuthenticationException(message) + class ServerUnreachable(message: String?) : AuthenticationException(message) + class Oidc(message: String?) : AuthenticationException(message) + class Generic(message: String?) : AuthenticationException(message) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/HomeServerLoginCompatibilityChecker.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/HomeServerLoginCompatibilityChecker.kt new file mode 100644 index 0000000000..dcf43a80a4 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/HomeServerLoginCompatibilityChecker.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.auth + +/** + * Checks the homeserver's compatibility with Element X. + */ +interface HomeServerLoginCompatibilityChecker { + /** + * Performs the compatibility check given the homeserver's [url]. + * @return a `true` value if the homeserver is compatible, `false` if not, or a failure result if the check unexpectedly failed. + */ + suspend fun check(url: String): Result +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt index 38777944ee..1c574ad467 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixAuthenticationService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,6 @@ import io.element.android.libraries.matrix.api.auth.external.ExternalSession import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep import io.element.android.libraries.matrix.api.core.SessionId -import kotlinx.coroutines.flow.StateFlow interface MatrixAuthenticationService { /** @@ -22,8 +22,12 @@ interface MatrixAuthenticationService { * Generally this method should not be used directly, prefer using [MatrixClientProvider.getOrRestore] instead. */ suspend fun restoreSession(sessionId: SessionId): Result - fun getHomeserverDetails(): StateFlow - suspend fun setHomeserver(homeserver: String): Result + + /** + * Set the homeserver to use for authentication, and return its details. + */ + suspend fun setHomeserver(homeserver: String): Result + suspend fun login(username: String, password: String): Result /** diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixHomeServerDetails.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixHomeServerDetails.kt index c2f0741571..aa5ed9a41d 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixHomeServerDetails.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/MatrixHomeServerDetails.kt @@ -1,18 +1,17 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.api.auth -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -@Parcelize data class MatrixHomeServerDetails( val url: String, val supportsPasswordLogin: Boolean, val supportsOidcLogin: Boolean, -) : Parcelable +) { + val isSupported = supportsPasswordLogin || supportsOidcLogin +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcConfig.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcConfig.kt index f8434a9d4a..ee8b7ec50e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcConfig.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcDetails.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcDetails.kt index 61f418dd0a..c4fb87e3c2 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcDetails.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcDetails.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcPrompt.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcPrompt.kt index 79576e0adb..8ddad9f52e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcPrompt.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcPrompt.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcRedirectUrlProvider.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcRedirectUrlProvider.kt index 1dba996ea9..ad4d862474 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcRedirectUrlProvider.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcRedirectUrlProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/SessionRestorationException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/SessionRestorationException.kt new file mode 100644 index 0000000000..bf6f50cab5 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/SessionRestorationException.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.auth + +import io.element.android.libraries.matrix.api.core.SessionId + +sealed class SessionRestorationException(message: String, cause: Throwable? = null) : Exception(message, cause) { + data class MissingSession(val sessionId: SessionId) : SessionRestorationException("Session with id $sessionId not found") + class InvalidToken : SessionRestorationException("Access token is invalid or expired") +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/external/ExternalSession.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/external/ExternalSession.kt index a144a33559..1241f5fb15 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/external/ExternalSession.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/external/ExternalSession.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,5 +17,4 @@ data class ExternalSession( val accessToken: String, val refreshToken: String?, val homeserverUrl: String, - val slidingSyncProxy: String? ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/MatrixQrCodeLoginData.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/MatrixQrCodeLoginData.kt index 48532e44b9..413f17af63 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/MatrixQrCodeLoginData.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/MatrixQrCodeLoginData.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/MatrixQrCodeLoginDataFactory.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/MatrixQrCodeLoginDataFactory.kt index 41f4374c12..b0e3089501 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/MatrixQrCodeLoginDataFactory.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/MatrixQrCodeLoginDataFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeDecodeException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeDecodeException.kt index 514a8d521d..438ee4e765 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeDecodeException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeDecodeException.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeLoginStep.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeLoginStep.kt index 20b57d2c71..56b3dc6ca5 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeLoginStep.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrCodeLoginStep.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,6 +13,7 @@ sealed interface QrCodeLoginStep { data class EstablishingSecureChannel(val checkCode: String) : QrCodeLoginStep data object Starting : QrCodeLoginStep data class WaitingForToken(val userCode: String) : QrCodeLoginStep + data object SyncingSecrets : QrCodeLoginStep data class Failed(val error: QrLoginException) : QrCodeLoginStep data object Finished : QrCodeLoginStep } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrLoginException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrLoginException.kt index ab278abd56..c81437eff0 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrLoginException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/qrlogin/QrLoginException.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,5 +17,8 @@ sealed class QrLoginException : Exception() { data object OidcMetadataInvalid : QrLoginException() data object SlidingSyncNotAvailable : QrLoginException() data object OtherDeviceNotSignedIn : QrLoginException() + data object CheckCodeAlreadySent : QrLoginException() + data object CheckCodeCannotBeSent : QrLoginException() data object Unknown : QrLoginException() + data object NotFound : QrLoginException() } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/DeviceId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/DeviceId.kt index 7f615a77d2..1fd8485cfb 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/DeviceId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/DeviceId.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/EventId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/EventId.kt index 08a9c2eeaa..0a2fe6ec08 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/EventId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/EventId.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/FlowId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/FlowId.kt index f9253bbb98..70564be0b2 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/FlowId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/FlowId.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt index c833d46718..a933e50529 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatterns.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/ProgressCallback.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/ProgressCallback.kt index dc515f09f2..a7abc0f797 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/ProgressCallback.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/ProgressCallback.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomAlias.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomAlias.kt index 3a02d0a9a3..c4a5c40aac 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomAlias.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomAlias.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomId.kt index 8d339ae704..1758563f55 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomId.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomIdOrAlias.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomIdOrAlias.kt index cfccf6f6cb..4ac480e064 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomIdOrAlias.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/RoomIdOrAlias.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SendHandle.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SendHandle.kt index ca169fc402..f3b8904fff 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SendHandle.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SendHandle.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SessionId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SessionId.kt index 5a95e84825..7fcfcd94a3 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SessionId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SessionId.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SpaceId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SpaceId.kt index 6db907bacd..6acf2e43d8 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SpaceId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/SpaceId.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/ThreadId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/ThreadId.kt index da0594f023..5b4221381c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/ThreadId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/ThreadId.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/TransactionId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/TransactionId.kt index 89d39e94d9..b548dcca9c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/TransactionId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/TransactionId.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UniqueId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UniqueId.kt index 5222934dca..e2c84cce22 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UniqueId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UniqueId.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UserId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UserId.kt index ece3a9a296..b00fdde2ca 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UserId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/core/UserId.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/createroom/CreateRoomParameters.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/createroom/CreateRoomParameters.kt index 3a4aaeea4f..a0a0bde1ed 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/createroom/CreateRoomParameters.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/createroom/CreateRoomParameters.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/createroom/RoomPreset.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/createroom/RoomPreset.kt index 296c4ae99a..83fa25cd59 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/createroom/RoomPreset.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/createroom/RoomPreset.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.api.createroom diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupState.kt index 855f3ef0a9..9c21ce044b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupUploadState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupUploadState.kt index 5d93163445..2a90cdbb7c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupUploadState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupUploadState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EnableRecoveryProgress.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EnableRecoveryProgress.kt index 69394bc159..3fc27692f2 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EnableRecoveryProgress.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EnableRecoveryProgress.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt index 961178ee3e..aefad517dc 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt @@ -1,12 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.api.encryption +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import kotlinx.coroutines.flow.Flow @@ -17,7 +19,7 @@ interface EncryptionService { val recoveryStateStateFlow: StateFlow val enableRecoveryProgressStateFlow: StateFlow val isLastDevice: StateFlow - val hasDevicesToVerifyAgainst: StateFlow + val hasDevicesToVerifyAgainst: StateFlow> suspend fun enableBackups(): Result @@ -62,8 +64,6 @@ interface EncryptionService { */ suspend fun startIdentityReset(): Result - suspend fun isUserVerified(userId: UserId): Result - /** * Remember this identity, ensuring it does not result in a pin violation. */ @@ -80,8 +80,10 @@ interface EncryptionService { /** * Get the identity state of a user, if known. + * @param userId the user id to get the identity for. + * @param fallbackToServer whether to fallback to fetching the identity from the server if not known locally. Defaults to true. */ - suspend fun getUserIdentity(userId: UserId): Result + suspend fun getUserIdentity(userId: UserId, fallbackToServer: Boolean = true): Result } /** diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/RecoveryException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/RecoveryException.kt index 7dffbf3653..4b580f2e8b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/RecoveryException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/RecoveryException.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/RecoveryState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/RecoveryState.kt index 367aba5e00..eee547cbe9 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/RecoveryState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/RecoveryState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/SteadyStateException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/SteadyStateException.kt index a9d4e5b7d1..6410633cc1 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/SteadyStateException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/SteadyStateException.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/identity/IdentityState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/identity/IdentityState.kt index d565c47336..f6f35d8ab2 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/identity/IdentityState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/identity/IdentityState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/identity/IdentityStateChange.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/identity/IdentityStateChange.kt index 0a377df401..f269e5fd84 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/identity/IdentityStateChange.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/identity/IdentityStateChange.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/exception/ClientException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/exception/ClientException.kt index 56497e8fa6..52b1577bf0 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/exception/ClientException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/exception/ClientException.kt @@ -1,16 +1,21 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.api.exception -sealed class ClientException(message: String, val details: String?) : Exception(message) { - class Generic(message: String, details: String?) : ClientException(message, details) - class MatrixApi(val kind: ErrorKind, val code: String, message: String, details: String?) : ClientException(message, details) - class Other(message: String) : ClientException(message, null) +sealed class ClientException(message: String, val details: String?, cause: Throwable? = null) : Exception(message, cause) { + class Generic(message: String, details: String?, cause: Throwable? = null) : ClientException(message, details, cause) + class MatrixApi(val kind: ErrorKind, val code: String, message: String, details: String?, cause: Throwable? = null) : ClientException( + message = message, + details = details, + cause = cause + ) + class Other(message: String, cause: Throwable? = null) : ClientException(message, null, cause) } fun ClientException.isNetworkError(): Boolean { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/exception/ErrorKind.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/exception/ErrorKind.kt index 8ca7ce5579..9d37dde164 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/exception/ErrorKind.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/exception/ErrorKind.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/exception/NotificationResolverException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/exception/NotificationResolverException.kt index ab251f19d8..fd3adf2592 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/exception/NotificationResolverException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/exception/NotificationResolverException.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/linknewdevice/CheckCodeSender.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/linknewdevice/CheckCodeSender.kt new file mode 100644 index 0000000000..ecb440de83 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/linknewdevice/CheckCodeSender.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.linknewdevice + +interface CheckCodeSender { + /** + * Validates the given [code]. Returns true if the code is valid, false otherwise. + * This method can be called multiple times to validate different codes. + */ + suspend fun validate(code: UByte): Boolean + + /** + * Sends the given [code]. + * This method can be called only once. + */ + suspend fun send(code: UByte): Result +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/linknewdevice/ErrorType.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/linknewdevice/ErrorType.kt new file mode 100644 index 0000000000..0f61007d47 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/linknewdevice/ErrorType.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.linknewdevice + +sealed class ErrorType(message: String) : Exception(message) { + /** + * The requested device ID is already in use. + */ + class DeviceIdAlreadyInUse(message: String) : ErrorType(message) + + /** + * The check code was incorrect. + */ + class InvalidCheckCode(message: String) : ErrorType(message) + + /** + * The other client proposed an unsupported protocol. + */ + class UnsupportedProtocol(message: String) : ErrorType(message) + + /** + * Secrets backup not set up properly. + */ + class MissingSecretsBackup(message: String) : ErrorType(message) + + /** + * The rendezvous session was not found and might have expired. + */ + class NotFound(message: String) : ErrorType(message) + + /** + * The device could not be created. + */ + class UnableToCreateDevice(message: String) : ErrorType(message) + + /** + * An unknown error has happened. + */ + class Unknown(message: String) : ErrorType(message) +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/linknewdevice/LinkDesktopHandler.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/linknewdevice/LinkDesktopHandler.kt new file mode 100644 index 0000000000..d600c1a8bf --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/linknewdevice/LinkDesktopHandler.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.linknewdevice + +import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeDecodeException +import kotlinx.coroutines.flow.StateFlow + +interface LinkDesktopHandler { + val linkDesktopStep: StateFlow + suspend fun handleScannedQrCode(data: ByteArray) +} + +sealed interface LinkDesktopStep { + data object Uninitialized : LinkDesktopStep + data object Starting : LinkDesktopStep + data class WaitingForAuth( + val verificationUri: String, + ) : LinkDesktopStep + + data class EstablishingSecureChannel( + val checkCode: UByte, + val checkCodeString: String, + ) : LinkDesktopStep + + data class InvalidQrCode( + val error: QrCodeDecodeException, + ) : LinkDesktopStep + + data class Error( + val errorType: ErrorType, + ) : LinkDesktopStep + + data object SyncingSecrets : LinkDesktopStep + + data object Done : LinkDesktopStep +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/linknewdevice/LinkMobileHandler.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/linknewdevice/LinkMobileHandler.kt new file mode 100644 index 0000000000..0c261cdd1a --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/linknewdevice/LinkMobileHandler.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.linknewdevice + +import kotlinx.coroutines.flow.Flow + +interface LinkMobileHandler { + val linkMobileStep: Flow + suspend fun start() +} + +sealed interface LinkMobileStep { + data object Uninitialized : LinkMobileStep + data object Starting : LinkMobileStep + data class QrReady(val data: String) : LinkMobileStep + data class WaitingForAuth(val verificationUri: String) : LinkMobileStep + data class QrScanned(val checkCodeSender: CheckCodeSender) : LinkMobileStep + data class Error(val errorType: ErrorType) : LinkMobileStep + data object SyncingSecrets : LinkMobileStep + data object Done : LinkMobileStep +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/logs/LoggerTags.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/logs/LoggerTags.kt new file mode 100644 index 0000000000..7f19b2d3db --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/logs/LoggerTags.kt @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.logs + +import io.element.android.libraries.core.log.logger.LoggerTag + +object LoggerTags { + val linkNewDevice = LoggerTag("LinkNewDevice") +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/AudioDetails.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/AudioDetails.kt index c2cd4c2988..b93386850e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/AudioDetails.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/AudioDetails.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/AudioInfo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/AudioInfo.kt index a3dadf8a16..a91a3c03f6 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/AudioInfo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/AudioInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/FileInfo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/FileInfo.kt index f2a6e7522c..794807cd79 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/FileInfo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/FileInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/ImageInfo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/ImageInfo.kt index e4ec33fe39..946e523b5f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/ImageInfo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/ImageInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MatrixMediaLoader.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MatrixMediaLoader.kt index a507aed391..a36849b812 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MatrixMediaLoader.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MatrixMediaLoader.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaFile.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaFile.kt index 793f5acd7f..b5cefe897b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaFile.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaFile.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaPreviewConfig.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaPreviewConfig.kt index f1adec8e89..c91fea7e88 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaPreviewConfig.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaPreviewConfig.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaPreviewService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaPreviewService.kt index dffa2d25e4..39718ddfd2 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaPreviewService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaPreviewService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaPreviewValue.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaPreviewValue.kt index 60f7552fd9..b77b01b692 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaPreviewValue.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaPreviewValue.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaSource.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaSource.kt index 25e6a47be4..56e32ba2fc 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaSource.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaSource.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaUploadHandler.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaUploadHandler.kt index fdf14c4cca..c07949441b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaUploadHandler.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/MediaUploadHandler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/ThumbnailInfo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/ThumbnailInfo.kt index 04203bf283..368abd4389 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/ThumbnailInfo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/ThumbnailInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/VideoInfo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/VideoInfo.kt index 0ae6e9f55d..e5f3915d55 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/VideoInfo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/VideoInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/mxc/MxcTools.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/mxc/MxcTools.kt index 8525425e1b..306ab8354b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/mxc/MxcTools.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/mxc/MxcTools.kt @@ -1,34 +1,19 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.api.mxc -import dev.zacsweers.metro.Inject - -@Inject -class MxcTools { - /** - * Regex to match a Matrix Content (mxc://) URI. - * - * See: https://spec.matrix.org/v1.8/client-server-api/#matrix-content-mxc-uris - */ - private val mxcRegex = Regex("""^mxc://([^/]+)/([^/]+)$""") - +interface MxcTools { /** * Sanitizes an mxcUri to be used as a relative file path. * * @param mxcUri the Matrix Content (mxc://) URI of the file. * @return the relative file path as "/" or null if the mxcUri is invalid. */ - fun mxcUri2FilePath(mxcUri: String): String? = mxcRegex.matchEntire(mxcUri)?.let { match -> - buildString { - append(match.groupValues[1]) - append("/") - append(match.groupValues[2]) - } - } + fun mxcUri2FilePath(mxcUri: String): String? } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt index 1a58ced0fd..aff3092549 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -29,6 +30,7 @@ data class NotificationData( val roomDisplayName: String?, val isDirect: Boolean, val isDm: Boolean, + val isSpace: Boolean, val isEncrypted: Boolean, val isNoisy: Boolean, val timestamp: Long, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationService.kt index ddec326cef..369587cd10 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notificationsettings/NotificationSettingsService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notificationsettings/NotificationSettingsService.kt index 703ec1205e..4d8ce8afb4 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notificationsettings/NotificationSettingsService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notificationsettings/NotificationSettingsService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -31,6 +32,7 @@ interface NotificationSettingsService { suspend fun setCallEnabled(enabled: Boolean): Result suspend fun isInviteForMeEnabled(): Result suspend fun setInviteForMeEnabled(enabled: Boolean): Result - suspend fun getRoomsWithUserDefinedRules(): Result> + suspend fun getRoomsWithUserDefinedRules(): Result> suspend fun canHomeServerPushEncryptedEventsToDevice(): Result + suspend fun getRawPushRules(): Result } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/oidc/AccountManagementAction.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/oidc/AccountManagementAction.kt index 506d1d9f3f..77e8854417 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/oidc/AccountManagementAction.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/oidc/AccountManagementAction.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt index 7f7d685326..cc3e2b5cf6 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/MatrixToConverter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 c66dc89693..878b0e7a69 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkData.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkData.kt index 1f5f39dee7..a554ce2b20 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkData.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkData.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,6 +14,7 @@ import androidx.compose.runtime.Immutable 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.RoomIdOrAlias +import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.UserId import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -28,6 +30,7 @@ sealed interface PermalinkData : Parcelable { data class RoomLink( val roomIdOrAlias: RoomIdOrAlias, val eventId: EventId? = null, + val threadId: ThreadId? = null, val viaParameters: ImmutableList = persistentListOf() ) : PermalinkData diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt index f51856db33..5365b8776a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/platform/InitPlatformService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/platform/InitPlatformService.kt index 6199d1704d..a08946bbac 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/platform/InitPlatformService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/platform/InitPlatformService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/poll/PollAnswer.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/poll/PollAnswer.kt index d55a902b03..043d2881c3 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/poll/PollAnswer.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/poll/PollAnswer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/poll/PollKind.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/poll/PollKind.kt index 81c55569be..e79d716f82 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/poll/PollKind.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/poll/PollKind.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.api.poll diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/pusher/PushersService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/pusher/PushersService.kt index 14c2d50694..6764598235 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/pusher/PushersService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/pusher/PushersService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/pusher/SetHttpPusherData.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/pusher/SetHttpPusherData.kt index 3525594c08..8a557f4b70 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/pusher/SetHttpPusherData.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/pusher/SetHttpPusherData.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/pusher/UnsetHttpPusherData.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/pusher/UnsetHttpPusherData.kt index ad833a6a78..dc9cf12ffe 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/pusher/UnsetHttpPusherData.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/pusher/UnsetHttpPusherData.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/recentemojis/AddRecentEmoji.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/recentemojis/AddRecentEmoji.kt deleted file mode 100644 index da657ea78a..0000000000 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/recentemojis/AddRecentEmoji.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2025 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.matrix.api.recentemojis - -import dev.zacsweers.metro.Inject -import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.matrix.api.MatrixClient -import kotlinx.coroutines.withContext - -@Inject -class AddRecentEmoji( - private val client: MatrixClient, - private val dispatchers: CoroutineDispatchers, -) { - suspend operator fun invoke(emoji: String): Result = withContext(dispatchers.io) { - client.addRecentEmoji(emoji) - } -} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/BaseRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/BaseRoom.kt index 2694191f89..481171ace0 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/BaseRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/BaseRoom.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,10 +14,12 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.draft.ComposerDraft +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility import io.element.android.libraries.matrix.api.timeline.ReceiptType +import io.element.android.libraries.matrix.api.timeline.Timeline import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -97,6 +100,11 @@ interface BaseRoom : Closeable { */ suspend fun userRole(userId: UserId): Result + /** + * Gets the permissions of the room. + */ + suspend fun roomPermissions(): Result + /** * Gets the display name of the user with the provided [userId] in the room. */ @@ -122,57 +130,6 @@ interface BaseRoom : Closeable { */ suspend fun forget(): Result - /** - * Returns `true` if the user with the provided [userId] can invite other users to the room. - */ - suspend fun canUserInvite(userId: UserId): Result - - /** - * Returns `true` if the user with the provided [userId] can kick other users from the room. - */ - suspend fun canUserKick(userId: UserId): Result - - /** - * Returns `true` if the user with the provided [userId] can ban other users from the room. - */ - suspend fun canUserBan(userId: UserId): Result - - /** - * Returns `true` if the user with the provided [userId] can redact their own messages. - */ - suspend fun canUserRedactOwn(userId: UserId): Result - - /** - * Returns `true` if the user with the provided [userId] can redact messages from other users. - */ - suspend fun canUserRedactOther(userId: UserId): Result - - /** - * Returns `true` if the user with the provided [userId] can send state events. - */ - suspend fun canUserSendState(userId: UserId, type: StateEventType): Result - - /** - * Returns `true` if the user with the provided [userId] can send messages. - */ - suspend fun canUserSendMessage(userId: UserId, type: MessageEventType): Result - - /** - * Returns `true` if the user with the provided [userId] can trigger an `@room` notification. - */ - suspend fun canUserTriggerRoomNotification(userId: UserId): Result - - /** - * Returns `true` if the user with the provided [userId] can pin or unpin messages. - */ - suspend fun canUserPinUnpin(userId: UserId): Result - - /** - * Returns `true` if the user with the provided [userId] can join or starts calls. - */ - suspend fun canUserJoinCall(userId: UserId): Result = - canUserSendState(userId, StateEventType.CALL_MEMBER) - /** * Sets the room as favorite or not, based on the [isFavorite] parameter. */ @@ -180,6 +137,10 @@ interface BaseRoom : Closeable { /** * Mark the room as read by trying to attach an unthreaded read receipt to the latest room event. + * + * Note this will instantiate a new timeline, which is an expensive operation. + * Prefer using [Timeline.markAsRead] instead when possible. + * * @param receiptType The type of receipt to send. */ suspend fun markAsRead(receiptType: ReceiptType): Result diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/CreateTimelineParams.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/CreateTimelineParams.kt index 7d7b3a379a..b5374c8be8 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/CreateTimelineParams.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/CreateTimelineParams.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/CurrentUserMembership.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/CurrentUserMembership.kt index 9b1b86703b..ecf43e90f3 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/CurrentUserMembership.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/CurrentUserMembership.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/FilterRoomMembers.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/FilterRoomMembers.kt index a1c4f0bf01..80cefc41e1 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/FilterRoomMembers.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/FilterRoomMembers.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/ForwardEventException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/ForwardEventException.kt index 21ccb16278..f73b756810 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/ForwardEventException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/ForwardEventException.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/IntentionalMention.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/IntentionalMention.kt index deee0490a7..25d5fa6f7d 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/IntentionalMention.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/IntentionalMention.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/JoinedRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/JoinedRoom.kt index fae6b04a93..1cfcfc46f2 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/JoinedRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/JoinedRoom.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MessageEventType.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MessageEventType.kt index 0f2a61da6e..1341378b90 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MessageEventType.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MessageEventType.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,10 +12,21 @@ import androidx.compose.runtime.Immutable @Immutable sealed interface MessageEventType { + data object Audio : MessageEventType + data object Beacon : MessageEventType data object CallAnswer : MessageEventType + data object CallCandidates : MessageEventType data object CallInvite : MessageEventType data object CallHangup : MessageEventType - data object CallCandidates : MessageEventType + data object CallNegotiate : MessageEventType + data object CallNotify : MessageEventType + data object CallReject : MessageEventType + data object CallSdpStreamMetadataChanged : MessageEventType + data object CallSelectAnswer : MessageEventType + data object Emote : MessageEventType + data object Encrypted : MessageEventType + data object File : MessageEventType + data object Image : MessageEventType data object RtcNotification : MessageEventType data object KeyVerificationReady : MessageEventType data object KeyVerificationStart : MessageEventType @@ -23,10 +35,13 @@ sealed interface MessageEventType { data object KeyVerificationKey : MessageEventType data object KeyVerificationMac : MessageEventType data object KeyVerificationDone : MessageEventType + data object Location : MessageEventType + data object Message : MessageEventType data object Reaction : MessageEventType data object RoomEncrypted : MessageEventType data object RoomMessage : MessageEventType data object RoomRedaction : MessageEventType + data object RtcDecline : MessageEventType data object Sticker : MessageEventType data object PollEnd : MessageEventType data object PollResponse : MessageEventType @@ -34,5 +49,7 @@ sealed interface MessageEventType { data object UnstablePollEnd : MessageEventType data object UnstablePollResponse : MessageEventType data object UnstablePollStart : MessageEventType + data object Video : MessageEventType + data object Voice : MessageEventType data class Other(val type: String) : MessageEventType } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/NotJoinedRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/NotJoinedRoom.kt index b83292e0f2..fd5e71ff9d 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/NotJoinedRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/NotJoinedRoom.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomInfo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomInfo.kt index 48e549ab3f..9a789187ca 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomInfo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomInfo.kt @@ -1,13 +1,13 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.api.room -import androidx.compose.runtime.Immutable import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId @@ -19,7 +19,6 @@ import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.collections.immutable.ImmutableList -@Immutable data class RoomInfo( val id: RoomId, /** The room's name from the room state event if received from sync, or one that's been computed otherwise. */ @@ -80,15 +79,4 @@ data class RoomInfo( ) { val aliases: List get() = listOfNotNull(canonicalAlias) + alternativeAliases - - /** - * Returns the list of users with the given [role] in this room. - */ - fun usersWithRole(role: RoomMember.Role): List { - return if (role is RoomMember.Role.Owner && role.isCreator) { - this.creators - } else { - this.roomPowerLevels?.usersWithRole(role).orEmpty().toList() - } - } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheck.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheck.kt index 4f0bf2dcae..f33319e2ee 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheck.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheck.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt index f1b1104a27..0b3d7071c8 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt @@ -1,12 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.api.room +import androidx.compose.runtime.Immutable import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.user.MatrixUser @@ -24,6 +26,7 @@ data class RoomMember( /** * Role of the RoomMember, based on its [powerLevel]. */ + @Immutable sealed interface Role { data class Owner(val isCreator: Boolean) : Role data object Admin : Role @@ -96,6 +99,6 @@ fun RoomMember.getBestName(): String { fun RoomMember.toMatrixUser() = MatrixUser( userId = userId, - displayName = displayName, - avatarUrl = avatarUrl, + displayName = displayName.takeUnless { membership == RoomMembershipState.BAN }, + avatarUrl = avatarUrl.takeUnless { membership == RoomMembershipState.BAN }, ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembersState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembersState.kt index c83b74ac70..1c35fab7d0 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembersState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembersState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipDetails.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipDetails.kt index 20d7b0e2ad..df99d1be67 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipDetails.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipDetails.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipObserver.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipObserver.kt index a4a718b219..852a997ee9 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipObserver.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipObserver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomNotificationSettings.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomNotificationSettings.kt index b86bd51c0d..4505c99e0a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomNotificationSettings.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomNotificationSettings.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomNotificationSettingsState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomNotificationSettingsState.kt index 15c1003317..243f6c02af 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomNotificationSettingsState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomNotificationSettingsState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomType.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomType.kt index d788257dd0..49cc345c24 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomType.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomType.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt index f9b0de2449..057c5838e5 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StateEventType.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StateEventType.kt index 832fb0414f..41d64afff1 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StateEventType.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StateEventType.kt @@ -1,33 +1,40 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.api.room -enum class StateEventType { - POLICY_RULE_ROOM, - POLICY_RULE_SERVER, - POLICY_RULE_USER, - CALL_MEMBER, - ROOM_ALIASES, - ROOM_AVATAR, - ROOM_CANONICAL_ALIAS, - ROOM_CREATE, - ROOM_ENCRYPTION, - ROOM_GUEST_ACCESS, - ROOM_HISTORY_VISIBILITY, - ROOM_JOIN_RULES, - ROOM_MEMBER_EVENT, - ROOM_NAME, - ROOM_PINNED_EVENTS, - ROOM_POWER_LEVELS, - ROOM_SERVER_ACL, - ROOM_THIRD_PARTY_INVITE, - ROOM_TOMBSTONE, - ROOM_TOPIC, - SPACE_CHILD, - SPACE_PARENT +sealed interface StateEventType { + data object PolicyRuleRoom : StateEventType + data object PolicyRuleServer : StateEventType + data object PolicyRuleUser : StateEventType + data object CallMember : StateEventType + data object RoomAliases : StateEventType + data object RoomAvatar : StateEventType + data object RoomCanonicalAlias : StateEventType + data object RoomCreate : StateEventType + data object RoomEncryption : StateEventType + data object RoomGuestAccess : StateEventType + data object RoomHistoryVisibility : StateEventType + data object RoomJoinRules : StateEventType + data object RoomMemberEvent : StateEventType + data object RoomName : StateEventType + data object RoomPinnedEvents : StateEventType + data object RoomPowerLevels : StateEventType + data object RoomServerAcl : StateEventType + data object RoomThirdPartyInvite : StateEventType + data object RoomTombstone : StateEventType + data object RoomTopic : StateEventType + data object SpaceChild : StateEventType + data object SpaceParent : StateEventType + data object BeaconInfo : StateEventType + data object MemberHints : StateEventType + data object RoomImagePack : StateEventType + data object RoomLanguage : StateEventType + + data class Custom(val type: String) : StateEventType } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/MatrixRoomAlias.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/MatrixRoomAlias.kt index 0b3bcdbc1c..1b26e8c92f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/MatrixRoomAlias.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/MatrixRoomAlias.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/ResolvedRoomAlias.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/ResolvedRoomAlias.kt index 81f7205140..65a3f2a265 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/ResolvedRoomAlias.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/ResolvedRoomAlias.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/RoomAliasHelper.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/RoomAliasHelper.kt index c42bcb6982..c23252961f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/RoomAliasHelper.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/RoomAliasHelper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/draft/ComposerDraft.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/draft/ComposerDraft.kt index 24a76d273a..bd1bdacdc9 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/draft/ComposerDraft.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/draft/ComposerDraft.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/draft/ComposerDraftType.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/draft/ComposerDraftType.kt index fc828af33d..79e9bd24df 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/draft/ComposerDraftType.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/draft/ComposerDraftType.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/errors/FocusEventException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/errors/FocusEventException.kt index 5e787ce4f7..a966394833 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/errors/FocusEventException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/errors/FocusEventException.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/history/RoomHistoryVisibility.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/history/RoomHistoryVisibility.kt index 09faa9fb00..9f3826fe06 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/history/RoomHistoryVisibility.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/history/RoomHistoryVisibility.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/AllowRule.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/AllowRule.kt index 4dfc3ac565..247fba860b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/AllowRule.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/AllowRule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRoom.kt index 731512b2e9..4220b68f0d 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRoom.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRule.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRule.kt index 0eada82e29..dad2492233 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRule.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/join/JoinRule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/knock/KnockRequest.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/knock/KnockRequest.kt index cf085a2297..317157793e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/knock/KnockRequest.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/knock/KnockRequest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/location/AssetType.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/location/AssetType.kt index 828b661551..42d384fd74 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/location/AssetType.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/location/AssetType.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/message/RoomMessage.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/message/RoomMessage.kt deleted file mode 100644 index acba06dce8..0000000000 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/message/RoomMessage.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.matrix.api.room.message - -import io.element.android.libraries.matrix.api.core.EventId -import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem - -data class RoomMessage( - val eventId: EventId, - val event: EventTimelineItem, - val sender: UserId, - val originServerTs: Long, -) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomMembersWithRole.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomMembersWithRole.kt similarity index 56% rename from libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomMembersWithRole.kt rename to libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomMembersWithRole.kt index 689157f215..4981de3b1a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/MatrixRoomMembersWithRole.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomMembersWithRole.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,17 +15,16 @@ import io.element.android.libraries.matrix.api.room.activeRoomMembers import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart /** - * Return a flow of the list of active room members who have the given role. + * Return a flow of the list of active room members who match the predicate. */ -fun BaseRoom.usersWithRole(role: RoomMember.Role): Flow> { - // Ensure the room members flow is ready +fun BaseRoom.usersWithRole(predicate: (RoomMember.Role) -> Boolean): Flow> { + // Wait until members are ready to avoid returning empty lists initially val readyMembersFlow = membersStateFlow .onStart { if (membersStateFlow.value is RoomMembersState.Unknown) { @@ -33,12 +33,17 @@ fun BaseRoom.usersWithRole(role: RoomMember.Role): Flow roomInfo.usersWithRole(role) } - .combine(readyMembersFlow) { powerLevels, membersState -> - membersState.activeRoomMembers() - .filter { powerLevels.contains(it.userId) } - .toImmutableList() - } - .distinctUntilChanged() + return readyMembersFlow.map { membersState -> + membersState + .activeRoomMembers() + .filter { predicate(it.role) } + .toImmutableList() + }.distinctUntilChanged() +} + +/** + * Return the number of active room members who match the predicate. + */ +fun BaseRoom.userCountWithRole(predicate: (RoomMember.Role) -> Boolean): Flow { + return usersWithRole(predicate).map { it.size } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomPermissions.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomPermissions.kt new file mode 100644 index 0000000000..a12f7d9606 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomPermissions.kt @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.room.powerlevels + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.remember +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.room.BaseRoom +import io.element.android.libraries.matrix.api.room.MessageEventType +import io.element.android.libraries.matrix.api.room.StateEventType +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map + +/** + * Provides information about the permissions of users in a room. + */ +interface RoomPermissions : AutoCloseable { + /** + * Returns true if the current user is able to ban from the room. + */ + fun canOwnUserBan(): Boolean + + /** + * Returns true if the current user is able to invite in the room. + */ + fun canOwnUserInvite(): Boolean + + /** + * Returns true if the current user is able to kick from the room. + */ + fun canOwnUserKick(): Boolean + + /** + * Returns true if the current user is able to pin or unpin events in the + * room. + */ + fun canOwnUserPinUnpin(): Boolean + + /** + * Returns true if the current user user is able to redact messages of + * other users in the room. + */ + fun canOwnUserRedactOther(): Boolean + + /** + * Returns true if the current user is able to redact their own messages in + * the room. + */ + fun canOwnUserRedactOwn(): Boolean + + /** + * Returns true if the current user is able to send a specific message type + * in the room. + */ + fun canOwnUserSendMessage(message: MessageEventType): Boolean + + /** + * Returns true if the current user is able to send a specific state event + * type in the room. + */ + fun canOwnUserSendState(stateEvent: StateEventType): Boolean + + /** + * Returns true if the current user is able to trigger a notification in + * the room. + */ + fun canOwnUserTriggerRoomNotification(): Boolean + + /** + * Returns true if the user with the given userId is able to ban in the + * room. + */ + fun canUserBan(userId: UserId): Boolean + + /** + * Returns true if the user with the given userId is able to invite in the + * room. + */ + fun canUserInvite(userId: UserId): Boolean + + /** + * Returns true if the user with the given userId is able to kick in the + * room. + */ + fun canUserKick(userId: UserId): Boolean + + /** + * Returns true if the user with the given userId is able to pin or unpin + * events in the room. + */ + fun canUserPinUnpin(userId: UserId): Boolean + + /** + * Returns true if the user with the given userId is able to redact + * messages of other users in the room. + */ + fun canUserRedactOther(userId: UserId): Boolean + + /** + * Returns true if the user with the given userId is able to redact + * their own messages in the room. + */ + fun canUserRedactOwn(userId: UserId): Boolean + + /** + * Returns true if the user with the given userId is able to send a + * specific message type in the room. + */ + fun canUserSendMessage(userId: UserId, message: MessageEventType): Boolean + + /** + * Returns true if the user with the given userId is able to send a + * specific state event type in the room. + */ + fun canUserSendState(userId: UserId, stateEvent: StateEventType): Boolean + + /** + * Returns true if the user with the given userId is able to trigger a + * notification in the room. + * + * The call may fail if there is an error in getting the power levels. + */ + fun canUserTriggerRoomNotification(userId: UserId): Boolean +} + +/** + * Returns true if the current user can edit roles and permissions in the room ie. can send + * a power levels state event. + */ +fun RoomPermissions.canEditRolesAndPermissions(): Boolean { + return canOwnUserSendState(StateEventType.RoomPowerLevels) +} + +/** + * Returns true if the current user can start a call in the room ie. can send + * a call member state event. + */ +fun RoomPermissions.canCall(): Boolean { + return canOwnUserSendState(StateEventType.CallMember) +} + +fun Result.use(default: T, block: (RoomPermissions) -> T): T { + return fold( + onSuccess = { perms -> + perms.use(block) + }, + onFailure = { + default + } + ) +} + +fun BaseRoom.permissionsFlow(default: T, block: (RoomPermissions) -> T): Flow { + return roomInfoFlow + .map { info -> + // If the user is a privileged creator, we return a constant hashcode to avoid recomputing permissions + // each time the power levels change (as they have all permissions). + if (info.privilegedCreatorRole && info.creators.contains(sessionId)) { + Long.MAX_VALUE + } else { + info.roomPowerLevels?.hashCode() ?: 0L + } + } + .distinctUntilChanged() + .map { + roomPermissions().use(default, block) + } +} + +@Composable +fun BaseRoom.permissionsAsState(default: T, block: (RoomPermissions) -> T): State { + return remember(this, default, block) { + permissionsFlow(default, block) + }.collectAsState(default) +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomPowerLevels.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomPowerLevels.kt index 635a6cc1f4..31019878d9 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomPowerLevels.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomPowerLevels.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -34,19 +35,6 @@ data class RoomPowerLevels( return users[userId] ?: 0L } - /** - * Returns the set of [UserId]s that have the given role in the room. - * - * **WARNING**: This method must not be used with a creator role. It'll result in a runtime error. - */ - fun usersWithRole(role: RoomMember.Role): Set { - return if (role is RoomMember.Role.Owner && role.isCreator) { - error("RoomPowerLevels.usersWithRole should not be used with a creator role, use roomInfo.creators instead") - } else { - users.filterValues { RoomMember.Role.forPowerLevel(it) == role }.keys - } - } - /** * Returns the role of the user in the room based on their power level. * If the user is not found, returns null. diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomPowerLevelsValues.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomPowerLevelsValues.kt index 0acafc2a43..e8f88ed86d 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomPowerLevelsValues.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomPowerLevelsValues.kt @@ -1,71 +1,22 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.api.room.powerlevels -import io.element.android.libraries.core.extensions.runCatchingExceptions -import io.element.android.libraries.matrix.api.room.BaseRoom -import io.element.android.libraries.matrix.api.room.MessageEventType -import io.element.android.libraries.matrix.api.room.StateEventType - data class RoomPowerLevelsValues( val ban: Long, val invite: Long, val kick: Long, - val sendEvents: Long, + val eventsDefault: Long, + val stateDefault: Long, val redactEvents: Long, val roomName: Long, val roomAvatar: Long, val roomTopic: Long, + val spaceChild: Long, ) - -/** - * Shortcut for calling [BaseRoom.canUserInvite] with our own user. - */ -suspend fun BaseRoom.canInvite(): Result = canUserInvite(sessionId) - -/** - * Shortcut for calling [BaseRoom.canUserKick] with our own user. - */ -suspend fun BaseRoom.canKick(): Result = canUserKick(sessionId) - -/** - * Shortcut for calling [BaseRoom.canUserBan] with our own user. - */ -suspend fun BaseRoom.canBan(): Result = canUserBan(sessionId) - -/** - * Shortcut for calling [BaseRoom.canUserSendState] with our own user. - */ -suspend fun BaseRoom.canSendState(type: StateEventType): Result = canUserSendState(sessionId, type) - -/** - * Shortcut for calling [BaseRoom.canUserSendMessage] with our own user. - */ -suspend fun BaseRoom.canSendMessage(type: MessageEventType): Result = canUserSendMessage(sessionId, type) - -/** - * Shortcut for calling [BaseRoom.canUserRedactOwn] with our own user. - */ -suspend fun BaseRoom.canRedactOwn(): Result = canUserRedactOwn(sessionId) - -/** - * Shortcut for calling [BaseRoom.canRedactOther] with our own user. - */ -suspend fun BaseRoom.canRedactOther(): Result = canUserRedactOther(sessionId) - -/** - * Shortcut for checking if current user can handle knock requests. - */ -suspend fun BaseRoom.canHandleKnockRequests(): Result = runCatchingExceptions { - canInvite().getOrThrow() || canBan().getOrThrow() || canKick().getOrThrow() -} - -/** - * Shortcut for calling [BaseRoom.canUserPinUnpin] with our own user. - */ -suspend fun BaseRoom.canPinUnpin(): Result = canUserPinUnpin(sessionId) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/UserRoleChange.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/UserRoleChange.kt index 9ab472e6b4..43d6b8ad70 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/UserRoleChange.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/UserRoleChange.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/preview/RoomPreviewInfo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/preview/RoomPreviewInfo.kt index 61de6f7d15..ee422d6f9f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/preview/RoomPreviewInfo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/preview/RoomPreviewInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentDirectRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentDirectRoom.kt index e0a51ef005..8221674439 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentDirectRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentDirectRoom.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tombstone/PredecessorRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tombstone/PredecessorRoom.kt index 4e6e8a7157..d66d149c48 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tombstone/PredecessorRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tombstone/PredecessorRoom.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tombstone/SuccessorRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tombstone/SuccessorRoom.kt index 957585a464..c8179d8bcc 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tombstone/SuccessorRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/tombstone/SuccessorRoom.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDescription.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDescription.kt index 16aa58de28..b6280080f5 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDescription.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDescription.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt index 402a60f639..c48bf54e9c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryList.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -26,9 +27,9 @@ interface RoomDirectoryList { /** * The current search results as a state flow. */ - val state: Flow + val state: Flow - data class State( + data class SearchResult( val hasMoreToLoad: Boolean, val items: List, ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt index d91b49363b..8f71cfb18c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomDirectoryService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomVisibility.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomVisibility.kt index f29c2aac59..1d4cd18009 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomVisibility.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomdirectory/RoomVisibility.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/DynamicRoomList.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/DynamicRoomList.kt index b55a57d9dd..acba1e3cf6 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/DynamicRoomList.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/DynamicRoomList.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/LatestEventValue.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/LatestEventValue.kt new file mode 100644 index 0000000000..5482a67875 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/LatestEventValue.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.roomlist + +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.timeline.item.event.EventContent +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails + +sealed interface LatestEventValue { + data object None : LatestEventValue + data class Remote( + val timestamp: Long, + val content: EventContent, + val senderId: UserId, + val senderProfile: ProfileDetails, + val isOwn: Boolean, + ) : LatestEventValue + + data class Local( + val timestamp: Long, + val content: EventContent, + val senderId: UserId, + val senderProfile: ProfileDetails, + val isSending: Boolean, + ) : LatestEventValue +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomList.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomList.kt index c367f1b675..a0d092596a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomList.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomList.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -64,7 +65,7 @@ suspend fun RoomList.awaitLoaded(timeout: Duration = Duration.INFINITE) { it is RoomList.LoadingState.Loaded } } - } catch (timeoutException: TimeoutCancellationException) { + } catch (_: TimeoutCancellationException) { Timber.d("awaitAllRoomsAreLoaded: no response after $timeout") } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListFilter.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListFilter.kt index 405906a185..33d233d589 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListFilter.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListFilter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt index 261a37defb..4a531792df 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt index c4f1851089..89a4acfec1 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomSummary.kt @@ -1,20 +1,24 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.api.roomlist import io.element.android.libraries.matrix.api.room.RoomInfo -import io.element.android.libraries.matrix.api.room.message.RoomMessage data class RoomSummary( val info: RoomInfo, - val lastMessage: RoomMessage?, + val latestEvent: LatestEventValue, ) { val roomId = info.id - val lastMessageTimestamp = lastMessage?.originServerTs + val latestEventTimestamp = when (latestEvent) { + is LatestEventValue.None -> null + is LatestEventValue.Local -> latestEvent.timestamp + is LatestEventValue.Remote -> latestEvent.timestamp + } val isOneToOne get() = info.activeMembersCount == 2L } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/server/UserServerResolver.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/server/UserServerResolver.kt index 7342272769..3529b17d85 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/server/UserServerResolver.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/server/UserServerResolver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/LeaveSpaceHandle.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/LeaveSpaceHandle.kt index 292a973dda..174ec20dc9 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/LeaveSpaceHandle.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/LeaveSpaceHandle.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/LeaveSpaceRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/LeaveSpaceRoom.kt index fb90896e05..071d5693b8 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/LeaveSpaceRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/LeaveSpaceRoom.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceRoom.kt index 3e72632e6c..d21c3764f3 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceRoom.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceRoomList.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceRoomList.kt index 1dddfadc5b..e2528bf117 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceRoomList.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceRoomList.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceRoomVisibility.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceRoomVisibility.kt index 98afa1d508..47a74461db 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceRoomVisibility.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceRoomVisibility.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceService.kt index f1fea6b62a..6f5ba674ec 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/spaces/SpaceService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SlidingSyncVersion.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SlidingSyncVersion.kt index fd4de06d35..8a60626ed4 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SlidingSyncVersion.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SlidingSyncVersion.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt index 13cb54b500..ce0019d8dc 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncState.kt index 6d253c8126..e4210c8ab4 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimelineItem.kt index 96aa9afeaf..b9e9608ef1 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimelineItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/ReceiptType.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/ReceiptType.kt index 2b45a27053..f0c9bbaeca 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/ReceiptType.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/ReceiptType.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt index f8f5793368..500d9f3191 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt @@ -1,13 +1,15 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.api.timeline import android.os.Parcelable +import androidx.compose.runtime.Immutable import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.ThreadId @@ -42,6 +44,7 @@ interface Timeline : AutoCloseable { } @Parcelize + @Immutable sealed interface Mode : Parcelable { data object Live : Mode data class FocusedOnEvent(val eventId: EventId) : Mode @@ -52,7 +55,9 @@ interface Timeline : AutoCloseable { val mode: Mode val membershipChangeEventReceived: Flow + val onSyncedEventReceived: Flow suspend fun sendReadReceipt(eventId: EventId, receiptType: ReceiptType): Result + suspend fun markAsRead(receiptType: ReceiptType): Result suspend fun paginate(direction: PaginationDirection): Result val backwardPaginationStatus: StateFlow @@ -225,4 +230,9 @@ interface Timeline : AutoCloseable { * pinned */ suspend fun unpinEvent(eventId: EventId): Result + + /** + * Get the latest event id of the timeline. + */ + suspend fun getLatestEventId(): Result } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt index 7fffc94fe3..0e05257a17 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineException.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineProvider.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineProvider.kt index acc18ee1a0..28e487f527 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineProvider.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/TimelineProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,7 +17,7 @@ import kotlinx.coroutines.flow.first * It could be the live timeline, a pinned timeline or a detached timeline. * By default, the active timeline is the live timeline. */ -interface TimelineProvider { +fun interface TimelineProvider { fun activeTimelineFlow(): StateFlow } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/ThreadSummary.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/ThreadSummary.kt index c34e4c9ac3..97923995a0 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/ThreadSummary.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/ThreadSummary.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,7 +13,7 @@ import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.event.EventContent import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId -import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails sealed interface EventThreadInfo { data class ThreadRoot(val summary: ThreadSummary) : EventThreadInfo @@ -28,6 +29,6 @@ data class EmbeddedEventInfo( val eventOrTransactionId: EventOrTransactionId, val content: EventContent, val senderId: UserId, - val senderProfile: ProfileTimelineDetails, + val senderProfile: ProfileDetails, val timestamp: Long, ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/TimelineItemDebugInfo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/TimelineItemDebugInfo.kt index 1ea8a34590..f34e4181ef 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/TimelineItemDebugInfo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/TimelineItemDebugInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt index 5fefe8ae31..b6ed7dc602 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -25,7 +26,7 @@ data class MessageContent( val inReplyTo: InReplyTo?, val isEdited: Boolean, val threadInfo: EventThreadInfo?, - val type: MessageType + val type: MessageType, ) : EventContent data object RedactedContent : EventContent @@ -35,6 +36,7 @@ data class StickerContent( val body: String?, val info: ImageInfo, val source: MediaSource, + val threadInfo: EventThreadInfo?, ) : EventContent { val bestDescription: String get() = body ?: filename @@ -48,10 +50,12 @@ data class PollContent( val votes: ImmutableMap>, val endTime: ULong?, val isEdited: Boolean, + val threadInfo: EventThreadInfo?, ) : EventContent data class UnableToDecryptContent( - val data: Data + val data: Data, + val threadInfo: EventThreadInfo?, ) : EventContent { @Immutable sealed interface Data { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventOrTransactionId.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventOrTransactionId.kt index f0abd0a6f1..a900326831 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventOrTransactionId.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventOrTransactionId.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventReaction.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventReaction.kt index ee304957b2..c9dd22e75c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventReaction.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventReaction.kt @@ -1,16 +1,15 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.api.timeline.item.event -import androidx.compose.runtime.Immutable import kotlinx.collections.immutable.ImmutableList -@Immutable data class EventReaction( val key: String, val senders: ImmutableList diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt index fc08c1cee1..401240f927 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -26,7 +27,7 @@ data class EventTimelineItem( val reactions: ImmutableList, val receipts: ImmutableList, val sender: UserId, - val senderProfile: ProfileTimelineDetails, + val senderProfile: ProfileDetails, val timestamp: Long, val content: EventContent, val origin: TimelineItemEventOrigin?, @@ -38,7 +39,13 @@ data class EventTimelineItem( return (content as? MessageContent)?.inReplyTo } - fun threadInfo(): EventThreadInfo? = (content as? MessageContent)?.threadInfo + fun threadInfo(): EventThreadInfo? = when (content) { + is MessageContent -> content.threadInfo + is PollContent -> content.threadInfo + is StickerContent -> content.threadInfo + is UnableToDecryptContent -> content.threadInfo + else -> null + } fun hasNotLoadedInReplyTo(): Boolean { val details = inReplyTo() diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventType.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventType.kt index d6b354376c..8978d28e5b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventType.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventType.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/FormattedBody.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/FormattedBody.kt index d5deac22be..88dda6ca5b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/FormattedBody.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/FormattedBody.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/InReplyTo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/InReplyTo.kt index 52b1cb9504..43a54602ca 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/InReplyTo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/InReplyTo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -24,7 +25,7 @@ sealed interface InReplyTo { val eventId: EventId, val content: EventContent, val senderId: UserId, - val senderProfile: ProfileTimelineDetails, + val senderProfile: ProfileDetails, ) : InReplyTo /** diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/LocalEventSendState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/LocalEventSendState.kt index f28ef46c66..05a510bafa 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/LocalEventSendState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/LocalEventSendState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MembershipChange.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MembershipChange.kt index 7d825c541d..e8cd52ec12 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MembershipChange.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MembershipChange.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageFormat.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageFormat.kt index 436e064ffc..8ef6b1debf 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageFormat.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageFormat.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageShield.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageShield.kt index 2fd9ec699a..a422e0bcc0 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageShield.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageShield.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageType.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageType.kt index d2d4f3997c..6de2876f61 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageType.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageType.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt index 3e03515c7c..ed3f53169f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt @@ -1,13 +1,15 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.api.timeline.item.event import androidx.compose.runtime.Immutable +import io.element.android.libraries.matrix.api.room.join.JoinRule @Immutable sealed interface OtherState { @@ -21,7 +23,7 @@ sealed interface OtherState { data object RoomEncryption : OtherState data object RoomGuestAccess : OtherState data object RoomHistoryVisibility : OtherState - data object RoomJoinRules : OtherState + data class RoomJoinRules(val joinRule: JoinRule?) : OtherState data class RoomName(val name: String?) : OtherState data class RoomPinnedEvents(val change: Change) : OtherState { enum class Change { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileDetails.kt similarity index 64% rename from libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt rename to libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileDetails.kt index 46ab482ad5..6393a27bb2 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileDetails.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,20 +12,20 @@ import androidx.compose.runtime.Immutable import io.element.android.libraries.matrix.api.core.UserId @Immutable -sealed interface ProfileTimelineDetails { - data object Unavailable : ProfileTimelineDetails +sealed interface ProfileDetails { + data object Unavailable : ProfileDetails - data object Pending : ProfileTimelineDetails + data object Pending : ProfileDetails data class Ready( val displayName: String?, val displayNameAmbiguous: Boolean, val avatarUrl: String? - ) : ProfileTimelineDetails + ) : ProfileDetails data class Error( val message: String - ) : ProfileTimelineDetails + ) : ProfileDetails } /** @@ -33,9 +34,9 @@ sealed interface ProfileTimelineDetails { * If the display name is ambiguous, the user ID is appended in parentheses. * Otherwise, the display name is returned. */ -fun ProfileTimelineDetails.getDisambiguatedDisplayName(userId: UserId): String { +fun ProfileDetails.getDisambiguatedDisplayName(userId: UserId): String { return when (this) { - is ProfileTimelineDetails.Ready -> when { + is ProfileDetails.Ready -> when { displayName == null -> userId.value displayNameAmbiguous -> "$displayName ($userId)" else -> displayName @@ -44,16 +45,16 @@ fun ProfileTimelineDetails.getDisambiguatedDisplayName(userId: UserId): String { } } -fun ProfileTimelineDetails.getDisplayName(): String? { +fun ProfileDetails.getDisplayName(): String? { return when (this) { - is ProfileTimelineDetails.Ready -> displayName + is ProfileDetails.Ready -> displayName else -> null } } -fun ProfileTimelineDetails.getAvatarUrl(): String? { +fun ProfileDetails.getAvatarUrl(): String? { return when (this) { - is ProfileTimelineDetails.Ready -> avatarUrl + is ProfileDetails.Ready -> avatarUrl else -> null } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ReactionSender.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ReactionSender.kt index af4fb3ca98..92eb0b5512 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ReactionSender.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ReactionSender.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/Receipt.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/Receipt.kt index 8249260f1a..9a371ba7ee 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/Receipt.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/Receipt.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineItemEventOrigin.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineItemEventOrigin.kt index 528e28d5cf..f867a03cf9 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineItemEventOrigin.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineItemEventOrigin.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/UtdCause.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/UtdCause.kt index 24d3a4079b..6203d345e4 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/UtdCause.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/UtdCause.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt index 1a32bde270..8e32c2e9ed 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/LogLevel.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/LogLevel.kt index 9485f14e1e..9cd40f4929 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/LogLevel.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/LogLevel.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TraceLogPack.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TraceLogPack.kt index cdb9c07996..a0e5050a23 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TraceLogPack.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TraceLogPack.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -19,6 +20,12 @@ enum class TraceLogPack(val key: String) { }, NOTIFICATION_CLIENT("notification_client") { override val title: String = "Notification Client" + }, + SYNC_PROFILING("sync_profiling") { + override val title: String = "Sync Profiling" + }, + LATEST_EVENTS("latest_events") { + override val title = "Latest Events" }; abstract val title: String diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingConfiguration.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingConfiguration.kt index f559176fa6..569683d3f7 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingConfiguration.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingConfiguration.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,4 +14,5 @@ data class TracingConfiguration( val traceLogPacks: Set, val writesToLogcat: Boolean, val writesToFilesConfiguration: WriteToFilesConfiguration, + val sdkSentryDsn: String?, ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingService.kt index 8c183c80b4..d4ef27fd0d 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/WriteToFilesConfiguration.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/WriteToFilesConfiguration.kt index a60ba27490..64822845e7 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/WriteToFilesConfiguration.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/WriteToFilesConfiguration.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/MatrixSearchUserResults.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/MatrixSearchUserResults.kt index 2c0462e8e8..5f49ae6d76 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/MatrixSearchUserResults.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/MatrixSearchUserResults.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/MatrixUser.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/MatrixUser.kt index c089004af3..53cefa3694 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/MatrixUser.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/MatrixUser.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationData.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationData.kt index 7d460dc042..e96681a31b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationData.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationData.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationRequestDetails.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationRequestDetails.kt index edf8c6ff78..175c9fbf2a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationRequestDetails.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationRequestDetails.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt index 6e34c3de32..4d2e786038 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/VerificationRequest.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/VerificationRequest.kt index f26afe50f6..303ab284be 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/VerificationRequest.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/VerificationRequest.kt @@ -1,17 +1,21 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.api.verification import android.os.Parcelable +import androidx.compose.runtime.Immutable import io.element.android.libraries.matrix.api.core.UserId import kotlinx.parcelize.Parcelize +@Immutable sealed interface VerificationRequest : Parcelable { + @Immutable sealed interface Outgoing : VerificationRequest { @Parcelize data object CurrentSession : Outgoing diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/CallAnalyticCredentialsProvider.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/CallAnalyticCredentialsProvider.kt index ee57c6edbc..5b2a691afc 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/CallAnalyticCredentialsProvider.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/CallAnalyticCredentialsProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/CallWidgetSettingsProvider.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/CallWidgetSettingsProvider.kt index 8334fb5fde..6c91a90928 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/CallWidgetSettingsProvider.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/CallWidgetSettingsProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/MatrixWidgetDriver.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/MatrixWidgetDriver.kt index ac8460785b..d8cca95842 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/MatrixWidgetDriver.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/MatrixWidgetDriver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/MatrixWidgetSettings.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/MatrixWidgetSettings.kt index bf534c44f8..00b0eb6f25 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/MatrixWidgetSettings.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/widget/MatrixWidgetSettings.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCodeTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCodeTest.kt index 9fe44332ce..c8dfe2d1f8 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCodeTest.kt +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/auth/AuthErrorCodeTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/auth/MatrixHomeServerDetailsTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/auth/MatrixHomeServerDetailsTest.kt new file mode 100644 index 0000000000..d4b360ef53 --- /dev/null +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/auth/MatrixHomeServerDetailsTest.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.api.auth + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.test.auth.aMatrixHomeServerDetails +import org.junit.Test + +class MatrixHomeServerDetailsTest { + @Test + fun `if homeserver supports oidc, then it is supported`() { + val sut = aMatrixHomeServerDetails( + supportsOidcLogin = true, + supportsPasswordLogin = false, + ) + assertThat(sut.isSupported).isTrue() + } + + @Test + fun `if homeserver supports password, then it is supported`() { + val sut = aMatrixHomeServerDetails( + supportsOidcLogin = false, + supportsPasswordLogin = true, + ) + assertThat(sut.isSupported).isTrue() + } + + @Test + fun `if homeserver supports both, then it is supported`() { + val sut = aMatrixHomeServerDetails( + supportsOidcLogin = true, + supportsPasswordLogin = true, + ) + assertThat(sut.isSupported).isTrue() + } + + @Test + fun `if homeserver supports none, then it is not supported`() { + val sut = aMatrixHomeServerDetails( + supportsOidcLogin = false, + supportsPasswordLogin = false, + ) + assertThat(sut.isSupported).isFalse() + } +} diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt index 96a40d04ba..4d1f905ba5 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/core/MatrixPatternsTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/notification/NotificationDataTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/notification/NotificationDataTest.kt index 956ea94fdc..26dc241147 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/notification/NotificationDataTest.kt +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/notification/NotificationDataTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheckTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheckTest.kt index 8f9b236c88..1461d28fd9 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheckTest.kt +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheckTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetailsTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetailsTest.kt index c173fa25b4..ffb59ba7d1 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetailsTest.kt +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetailsTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,23 +18,23 @@ private val aUserId = UserId(A_USER_ID) class ProfileTimelineDetailsTest { @Test fun `getDisambiguatedDisplayName of Unavailable should be equal to userId`() { - assertThat(ProfileTimelineDetails.Unavailable.getDisambiguatedDisplayName(aUserId)).isEqualTo(A_USER_ID) + assertThat(ProfileDetails.Unavailable.getDisambiguatedDisplayName(aUserId)).isEqualTo(A_USER_ID) } @Test fun `getDisambiguatedDisplayName of Error should be equal to userId`() { - assertThat(ProfileTimelineDetails.Error("An error").getDisambiguatedDisplayName(aUserId)).isEqualTo(A_USER_ID) + assertThat(ProfileDetails.Error("An error").getDisambiguatedDisplayName(aUserId)).isEqualTo(A_USER_ID) } @Test fun `getDisambiguatedDisplayName of Pending should be equal to userId`() { - assertThat(ProfileTimelineDetails.Pending.getDisambiguatedDisplayName(aUserId)).isEqualTo(A_USER_ID) + assertThat(ProfileDetails.Pending.getDisambiguatedDisplayName(aUserId)).isEqualTo(A_USER_ID) } @Test fun `getDisambiguatedDisplayName of Ready without display name should be equal to userId`() { assertThat( - ProfileTimelineDetails.Ready( + ProfileDetails.Ready( displayName = null, displayNameAmbiguous = false, avatarUrl = null, @@ -44,7 +45,7 @@ class ProfileTimelineDetailsTest { @Test fun `getDisambiguatedDisplayName of Ready with display name should be equal to display name`() { assertThat( - ProfileTimelineDetails.Ready( + ProfileDetails.Ready( displayName = "Alice", displayNameAmbiguous = false, avatarUrl = null, @@ -55,7 +56,7 @@ class ProfileTimelineDetailsTest { @Test fun `getDisambiguatedDisplayName of Ready with display name and ambiguous should be equal to display name with user id`() { assertThat( - ProfileTimelineDetails.Ready( + ProfileDetails.Ready( displayName = "Alice", displayNameAmbiguous = true, avatarUrl = null, diff --git a/libraries/matrix/impl/build.gradle.kts b/libraries/matrix/impl/build.gradle.kts index 14ba9df71f..6edf26008a 100644 --- a/libraries/matrix/impl/build.gradle.kts +++ b/libraries/matrix/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -33,6 +34,7 @@ dependencies { implementation(projects.libraries.featureflag.api) implementation(projects.libraries.network) implementation(projects.libraries.preferences.api) + implementation(projects.libraries.workmanager.api) implementation(projects.services.analytics.api) implementation(projects.services.toolbox.api) api(projects.libraries.matrix.api) @@ -48,6 +50,7 @@ dependencies { testImplementation(projects.libraries.preferences.test) testImplementation(projects.libraries.previewutils) testImplementation(projects.libraries.sessionStorage.test) + testImplementation(projects.libraries.workmanager.test) testImplementation(projects.services.analytics.test) testImplementation(projects.services.toolbox.test) } diff --git a/libraries/matrix/impl/src/main/AndroidManifest.xml b/libraries/matrix/impl/src/main/AndroidManifest.xml index 5c0a832239..608f16cc36 100644 --- a/libraries/matrix/impl/src/main/AndroidManifest.xml +++ b/libraries/matrix/impl/src/main/AndroidManifest.xml @@ -1,7 +1,8 @@ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/ClientBuilderProvider.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/ClientBuilderProvider.kt index 57486da3e8..f3cf4ffaf2 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/ClientBuilderProvider.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/ClientBuilderProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.matrix.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import org.matrix.rustcomponents.sdk.ClientBuilder interface ClientBuilderProvider { @@ -17,7 +17,6 @@ interface ClientBuilderProvider { } @ContributesBinding(AppScope::class) -@Inject class RustClientBuilderProvider : ClientBuilderProvider { override fun provide(): ClientBuilder { return ClientBuilder() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt index 7a3bfb3e6b..ee9f1d3eac 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegate.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index c646e2ee18..4cb84abc03 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,18 +12,23 @@ import io.element.android.libraries.androidutils.file.getSizeOfFiles import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.childScope +import io.element.android.libraries.core.data.bytes import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.extensions.mapFailure import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.analytics.SdkStoreSizes import io.element.android.libraries.matrix.api.core.DeviceId +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomIdOrAlias import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters import io.element.android.libraries.matrix.api.createroom.RoomPreset +import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopHandler +import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileHandler import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.oidc.AccountManagementAction import io.element.android.libraries.matrix.api.room.BaseRoom @@ -43,6 +49,9 @@ import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.impl.encryption.RustEncryptionService import io.element.android.libraries.matrix.impl.exception.mapClientException +import io.element.android.libraries.matrix.impl.linknewdevice.RustLinkDesktopHandler +import io.element.android.libraries.matrix.impl.linknewdevice.RustLinkMobileHandler +import io.element.android.libraries.matrix.impl.linknewdevice.RustQrCodeDataParser import io.element.android.libraries.matrix.impl.mapper.map import io.element.android.libraries.matrix.impl.media.RustMediaLoader import io.element.android.libraries.matrix.impl.media.RustMediaPreviewService @@ -73,7 +82,11 @@ import io.element.android.libraries.matrix.impl.util.SessionPathsProvider import io.element.android.libraries.matrix.impl.util.cancelAndDestroy import io.element.android.libraries.matrix.impl.util.mxCallbackFlow import io.element.android.libraries.matrix.impl.verification.RustSessionVerificationService +import io.element.android.libraries.matrix.impl.workmanager.PerformDatabaseVacuumWorkManagerRequest import io.element.android.libraries.sessionstorage.api.SessionStore +import io.element.android.libraries.workmanager.api.WorkManagerRequestType +import io.element.android.libraries.workmanager.api.WorkManagerScheduler +import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -129,6 +142,8 @@ class RustMatrixClient( clock: SystemClock, timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory, private val featureFlagService: FeatureFlagService, + private val analyticsService: AnalyticsService, + private val workManagerScheduler: WorkManagerScheduler, ) : MatrixClient { override val sessionId: UserId = UserId(innerClient.userId()) override val deviceId: DeviceId = DeviceId(innerClient.deviceId()) @@ -136,7 +151,9 @@ class RustMatrixClient( private val sessionDispatcher = dispatchers.io.limitedParallelism(64) private val innerRoomListService = innerSyncService.roomListService() - private val innerSpaceService = innerClient.spaceService() + + // TODO refactor this and `innerNotificationClient` to be behind a suspend function instead + private val innerSpaceService = runBlocking { innerClient.spaceService() } override val roomMembershipObserver = RoomMembershipObserver() @@ -176,6 +193,7 @@ class RustMatrixClient( roomListFactory = RoomListFactory( innerRoomListService = innerRoomListService, sessionCoroutineScope = sessionCoroutineScope, + analyticsService = analyticsService, ), roomSyncSubscriber = roomSyncSubscriber, ) @@ -210,6 +228,7 @@ class RustMatrixClient( roomMembershipObserver = roomMembershipObserver, roomInfoMapper = roomInfoMapper, featureFlagService = featureFlagService, + analyticsService = analyticsService, ) override val matrixMediaLoader: MatrixMediaLoader = RustMediaLoader( @@ -270,6 +289,9 @@ class RustMatrixClient( // Force a refresh of the profile getUserProfile() } + + // Schedule regular database vacuuming to ensure DB performance remains optimal + scheduleDatabaseVacuum() } override fun userIdServerName(): String { @@ -286,7 +308,7 @@ class RustMatrixClient( override suspend fun getUrl(url: String): Result = withContext(sessionDispatcher) { runCatchingExceptions { innerClient.getUrl(url) - } + }.mapFailure { it.mapClientException() } } override suspend fun getRoom(roomId: RoomId): BaseRoom? = withContext(sessionDispatcher) { @@ -553,6 +575,17 @@ class RustMatrixClient( return getCacheSize(includeCryptoDb = false) } + override suspend fun getDatabaseSizes(): Result = runCatchingExceptions { + innerClient.getStoreSizes().run { + SdkStoreSizes( + stateStore = stateStore?.bytes, + eventCacheStore = eventCacheStore?.bytes, + mediaStore = mediaStore?.bytes, + cryptoStore = cryptoStore?.bytes, + ) + } + } + override suspend fun clearCache() { innerClient.clearCaches(innerSyncService) destroy() @@ -713,6 +746,49 @@ class RustMatrixClient( } } + override suspend fun canLinkNewDevice(): Result = withContext(sessionDispatcher) { + runCatchingExceptions { + innerClient.isLoginWithQrCodeSupported() + } + } + + override fun createLinkMobileHandler(): Result { + return runCatchingExceptions { + val handler = innerClient.newGrantLoginWithQrCodeHandler() + RustLinkMobileHandler( + inner = handler, + sessionCoroutineScope = sessionCoroutineScope, + sessionDispatcher = sessionDispatcher, + ) + } + } + + override fun createLinkDesktopHandler(): Result { + return runCatchingExceptions { + val handler = innerClient.newGrantLoginWithQrCodeHandler() + RustLinkDesktopHandler( + inner = handler, + sessionCoroutineScope = sessionCoroutineScope, + sessionDispatcher = sessionDispatcher, + qrCodeDataParser = RustQrCodeDataParser(), + ) + } + } + + override suspend fun markRoomAsFullyRead(roomId: RoomId, eventId: EventId): Result = withContext(sessionDispatcher) { + runCatchingExceptions { + val room = innerClient.getRoom(roomId.value) ?: error("Could not fetch associated room") + room.markAsFullyReadUnchecked(eventId.value) + } + } + + override suspend fun performDatabaseVacuum(): Result = withContext(sessionDispatcher) { + runCatchingExceptions { + Timber.d("Performing database vacuuming for session $sessionId...") + innerClient.optimizeStores() + } + } + private suspend fun getCacheSize( includeCryptoDb: Boolean = false, ): Long = withContext(sessionDispatcher) { @@ -737,6 +813,15 @@ class RustMatrixClient( // Delete all the files for this session sessionPathsProvider.provides(sessionId)?.deleteRecursively() } + + private fun scheduleDatabaseVacuum() { + // If there's already a periodic work request, do not schedule another one + if (workManagerScheduler.hasPendingWork(sessionId, WorkManagerRequestType.DB_VACUUM)) return + + Timber.i("Scheduling periodic database vacuuming for session $sessionId") + val request = PerformDatabaseVacuumWorkManagerRequest(sessionId) + workManagerScheduler.submit(request) + } } private val defaultRoomCreationPowerLevels = PowerLevels( diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt index 35b5bcf2b9..5932acec20 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,6 +10,8 @@ package io.element.android.libraries.matrix.impl import dev.zacsweers.metro.Inject import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.data.ByteUnit +import io.element.android.libraries.core.data.megaBytes import io.element.android.libraries.di.CacheDirectory import io.element.android.libraries.di.annotations.AppCoroutineScope import io.element.android.libraries.featureflag.api.FeatureFlagService @@ -19,10 +22,12 @@ import io.element.android.libraries.matrix.impl.paths.SessionPaths import io.element.android.libraries.matrix.impl.paths.getSessionPaths import io.element.android.libraries.matrix.impl.proxy.ProxyProvider import io.element.android.libraries.matrix.impl.room.TimelineEventTypeFilterFactory +import io.element.android.libraries.matrix.impl.storage.SqliteStoreBuilderProvider import io.element.android.libraries.matrix.impl.util.anonymizedTokens import io.element.android.libraries.network.useragent.UserAgentProvider import io.element.android.libraries.sessionstorage.api.SessionData import io.element.android.libraries.sessionstorage.api.SessionStore +import io.element.android.libraries.workmanager.api.WorkManagerScheduler import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.coroutines.CoroutineScope @@ -35,10 +40,13 @@ import org.matrix.rustcomponents.sdk.SlidingSyncVersion import org.matrix.rustcomponents.sdk.SlidingSyncVersionBuilder import org.matrix.rustcomponents.sdk.use import timber.log.Timber +import uniffi.matrix_sdk_base.MediaRetentionPolicy import uniffi.matrix_sdk_crypto.CollectStrategy import uniffi.matrix_sdk_crypto.DecryptionSettings import uniffi.matrix_sdk_crypto.TrustRequirement import java.io.File +import kotlin.time.Duration.Companion.days +import kotlin.time.toJavaDuration @Inject class RustMatrixClientFactory( @@ -55,6 +63,8 @@ class RustMatrixClientFactory( private val featureFlagService: FeatureFlagService, private val timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory, private val clientBuilderProvider: ClientBuilderProvider, + private val sqliteStoreBuilderProvider: SqliteStoreBuilderProvider, + private val workManagerScheduler: WorkManagerScheduler, ) { private val sessionDelegate = RustClientSessionDelegate(sessionStore, appCoroutineScope, coroutineDispatchers) @@ -68,6 +78,19 @@ class RustMatrixClientFactory( .username(sessionData.userId) .use { it.build() } + client.setMediaRetentionPolicy( + MediaRetentionPolicy( + // Make this 500MB instead of 400MB + maxCacheSize = 500.megaBytes.into(ByteUnit.BYTES).toULong(), + // This is the default value, but let's make it explicit + maxFileSize = 20.megaBytes.into(ByteUnit.BYTES).toULong(), + // Use 30 days instead of 60 + lastAccessExpiry = 30.days.toJavaDuration(), + // This is the default value, but let's make it explicit + cleanupFrequency = 1.days.toJavaDuration(), + ) + ) + client.restoreSession(sessionData.toSession()) create(client) @@ -94,6 +117,8 @@ class RustMatrixClientFactory( clock = clock, timelineEventTypeFilterFactory = timelineEventTypeFilterFactory, featureFlagService = featureFlagService, + analyticsService = analyticsService, + workManagerScheduler = workManagerScheduler, ).also { Timber.tag(it.toString()).d("Creating Client with access token '$anonymizedAccessToken' and refresh token '$anonymizedRefreshToken'") } @@ -105,12 +130,12 @@ class RustMatrixClientFactory( slidingSyncType: ClientBuilderSlidingSync, ): ClientBuilder { return clientBuilderProvider.provide() - .sessionPaths( - dataPath = sessionPaths.fileDirectory.absolutePath, - cachePath = sessionPaths.cacheDirectory.absolutePath, - ) + .run { + sqliteStoreBuilderProvider.provide(sessionPaths) + .passphrase(passphrase) + .setupClientBuilder(this) + } .setSessionDelegate(sessionDelegate) - .sessionPassphrase(passphrase) .userAgent(userAgentProvider.provide()) .addRootCertificates(userCertificatesProvider.provides()) .autoEnableBackups(true) @@ -168,7 +193,7 @@ sealed interface ClientBuilderSlidingSync { data object Native : ClientBuilderSlidingSync } -private fun SessionData.toSession() = Session( +fun SessionData.toSession() = Session( accessToken = accessToken, refreshToken = refreshToken, userId = userId, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustSdkMetadata.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustSdkMetadata.kt index 88ff69459f..65bf81e181 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustSdkMetadata.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustSdkMetadata.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,12 +10,10 @@ package io.element.android.libraries.matrix.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.matrix.api.SdkMetadata import org.matrix.rustcomponents.sdk.sdkGitSha @ContributesBinding(AppScope::class) -@Inject class RustSdkMetadata : SdkMetadata { override val sdkGitSha: String get() = sdkGitSha() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/DefaultGetDatabaseSizesUseCase.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/DefaultGetDatabaseSizesUseCase.kt new file mode 100644 index 0000000000..8973483132 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/DefaultGetDatabaseSizesUseCase.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.analytics + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.matrix.api.MatrixClientProvider +import io.element.android.libraries.matrix.api.analytics.GetDatabaseSizesUseCase +import io.element.android.libraries.matrix.api.analytics.SdkStoreSizes +import io.element.android.libraries.matrix.api.core.SessionId + +@ContributesBinding(AppScope::class) +class DefaultGetDatabaseSizesUseCase( + private val clientProvider: Lazy, +) : GetDatabaseSizesUseCase { + override suspend fun invoke(sessionId: SessionId): Result { + val client = clientProvider.value.getOrNull(sessionId) + ?: return Result.failure(IllegalArgumentException("No MatrixClient for session $sessionId")) + + return client.getDatabaseSizes() + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedRoomExt.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedRoomExt.kt index 66fcee1e48..263ce15bd3 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedRoomExt.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedRoomExt.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/RustAnalyticsSdkManager.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/RustAnalyticsSdkManager.kt new file mode 100644 index 0000000000..a74acab683 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/RustAnalyticsSdkManager.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.analytics + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import io.element.android.services.analytics.api.AnalyticsSdkManager +import io.element.android.services.analytics.api.AnalyticsSdkSpan +import org.matrix.rustcomponents.sdk.enableSentryLogging + +@ContributesBinding(AppScope::class) +class RustAnalyticsSdkManager : AnalyticsSdkManager { + override fun enableSdkAnalytics(enabled: Boolean) { + enableSentryLogging(enabled) + } + + override fun startSpan(name: String, parentTraceId: String?): AnalyticsSdkSpan { + return RustAnalyticsSdkSpan(name = name, parentTraceId = parentTraceId) + } + + override fun bridge(parentTraceId: String?): AnalyticsSdkSpan { + // A bridge span has no name + return RustAnalyticsSdkSpan(name = null, parentTraceId = parentTraceId) + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/RustAnalyticsSdkSpan.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/RustAnalyticsSdkSpan.kt new file mode 100644 index 0000000000..9ed8763ec4 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/RustAnalyticsSdkSpan.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.analytics + +import io.element.android.libraries.core.extensions.runCatchingExceptions +import io.element.android.services.analytics.api.AnalyticsSdkSpan +import kotlinx.coroutines.DelicateCoroutinesApi +import org.matrix.rustcomponents.sdk.LogLevel +import org.matrix.rustcomponents.sdk.Span +import timber.log.Timber + +class RustAnalyticsSdkSpan( + name: String? = null, + private val parentTraceId: String?, +) : AnalyticsSdkSpan { + private val inner = if (name != null) { + Span( + target = "elementx", + name = name, + file = "-", + line = null, + level = LogLevel.WARN, + bridgeTraceId = parentTraceId, + ) + } else { + Span.newBridgeSpan( + target = "elementx", + parentTraceId = parentTraceId, + ) + } + + override fun enter() { + if (Span.current().isNone()) { + inner.enter() + } else { + Timber.w("Not entering span sentry.trace='$parentTraceId' because another span is already active") + } + } + + @OptIn(DelicateCoroutinesApi::class) + override fun exit() { + inner.exit() + runCatchingExceptions { inner.destroy() } + Timber.d("Exited span sentry.trace='$parentTraceId'") + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt index c9a71a17d2..8d71ad3ce8 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt index 05eb4c4d5f..36af4113d2 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationException.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,7 +13,6 @@ import org.matrix.rustcomponents.sdk.ClientBuildException import org.matrix.rustcomponents.sdk.OidcException fun Throwable.mapAuthenticationException(): AuthenticationException { - val message = this.message ?: "Unknown error" return when (this) { is AuthenticationException -> this is ClientBuildException -> when (this) { @@ -20,7 +20,7 @@ fun Throwable.mapAuthenticationException(): AuthenticationException { is ClientBuildException.InvalidServerName -> AuthenticationException.InvalidServerName(message) is ClientBuildException.SlidingSyncVersion -> AuthenticationException.SlidingSyncVersion(message) is ClientBuildException.Sdk -> AuthenticationException.Generic(message) - is ClientBuildException.ServerUnreachable -> AuthenticationException.Generic(message) + is ClientBuildException.ServerUnreachable -> AuthenticationException.ServerUnreachable(message) is ClientBuildException.SlidingSync -> AuthenticationException.Generic(message) is ClientBuildException.WellKnownDeserializationException -> AuthenticationException.Generic(message) is ClientBuildException.WellKnownLookupFailed -> AuthenticationException.Generic(message) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetails.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetails.kt index 379222f457..acf96d69d4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetails.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetails.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfigurationProvider.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfigurationProvider.kt index a614c97680..6f9dd67b12 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfigurationProvider.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfigurationProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcPrompt.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcPrompt.kt index 0ba9eb4c49..e21d8d94c6 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcPrompt.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcPrompt.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustHomeServerLoginCompatibilityChecker.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustHomeServerLoginCompatibilityChecker.kt new file mode 100644 index 0000000000..bab803f9b2 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustHomeServerLoginCompatibilityChecker.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.auth + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.core.extensions.runCatchingExceptions +import io.element.android.libraries.matrix.api.auth.HomeServerLoginCompatibilityChecker +import io.element.android.libraries.matrix.impl.ClientBuilderProvider +import io.element.android.libraries.matrix.impl.certificates.UserCertificatesProvider +import timber.log.Timber + +@ContributesBinding(AppScope::class) +class RustHomeServerLoginCompatibilityChecker( + private val clientBuilderProvider: ClientBuilderProvider, + private val userCertificatesProvider: UserCertificatesProvider, +) : HomeServerLoginCompatibilityChecker { + override suspend fun check(url: String): Result = runCatchingExceptions { + clientBuilderProvider.provide() + .inMemoryStore() + .serverNameOrHomeserverUrl(url) + .addRootCertificates(userCertificatesProvider.provides()) + .build() + .use { + it.homeserverLoginDetails() + } + .use { + Timber.d("Homeserver $url | OIDC: ${it.supportsOidcLogin()} | Password: ${it.supportsPasswordLogin()} | SSO: ${it.supportsSsoLogin()}") + it.supportsOidcLogin() || it.supportsPasswordLogin() + } + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index 88c86a43d6..7cd9fbedf5 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.matrix.impl.auth import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.extensions.mapFailure @@ -20,10 +20,12 @@ import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails import io.element.android.libraries.matrix.api.auth.OidcDetails import io.element.android.libraries.matrix.api.auth.OidcPrompt +import io.element.android.libraries.matrix.api.auth.SessionRestorationException import io.element.android.libraries.matrix.api.auth.external.ExternalSession import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.impl.ClientBuilderSlidingSync import io.element.android.libraries.matrix.impl.RustMatrixClientFactory import io.element.android.libraries.matrix.impl.auth.qrlogin.QrErrorMapper @@ -34,12 +36,13 @@ import io.element.android.libraries.matrix.impl.keys.PassphraseGenerator import io.element.android.libraries.matrix.impl.mapper.toSessionData import io.element.android.libraries.matrix.impl.paths.SessionPaths import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory +import io.element.android.libraries.matrix.impl.toSession import io.element.android.libraries.sessionstorage.api.LoginType import io.element.android.libraries.sessionstorage.api.SessionStore import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeoutOrNull import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.ClientBuilder import org.matrix.rustcomponents.sdk.HumanQrLoginException @@ -49,10 +52,10 @@ import org.matrix.rustcomponents.sdk.QrLoginProgress import org.matrix.rustcomponents.sdk.QrLoginProgressListener import timber.log.Timber import uniffi.matrix_sdk.OAuthAuthorizationData +import kotlin.time.Duration.Companion.seconds @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) -@Inject class RustMatrixAuthenticationService( private val sessionPathsFactory: SessionPathsFactory, private val coroutineDispatchers: CoroutineDispatchers, @@ -69,7 +72,6 @@ class RustMatrixAuthenticationService( // Ideally it would be possible to get the sessionPath from the Client to avoid doing this. private var sessionPaths: SessionPaths? = null private var currentClient: Client? = null - private var currentHomeserver = MutableStateFlow(null) private val newMatrixClientObservers = mutableListOf<(MatrixClient) -> Unit>() override fun listenToNewMatrixClients(lambda: (MatrixClient) -> Unit) { @@ -95,10 +97,10 @@ class RustMatrixAuthenticationService( } rustMatrixClientFactory.create(sessionData) } else { - error("Token is not valid") + throw SessionRestorationException.InvalidToken() } } else { - error("No session to restore with id $sessionId") + throw SessionRestorationException.MissingSession(sessionId) } }.mapFailure { failure -> failure.mapClientException() @@ -113,9 +115,7 @@ class RustMatrixAuthenticationService( return passphrase } - override fun getHomeserverDetails(): StateFlow = currentHomeserver - - override suspend fun setHomeserver(homeserver: String): Result = + override suspend fun setHomeserver(homeserver: String): Result = withContext(coroutineDispatchers.io) { val emptySessionPath = rotateSessionPath() runCatchingExceptions { @@ -124,8 +124,7 @@ class RustMatrixAuthenticationService( } currentClient = client - val homeServerDetails = client.homeserverLoginDetails().map() - currentHomeserver.value = homeServerDetails.copy(url = homeserver) + client.homeserverLoginDetails().map() }.onFailure { clear() }.mapFailure { failure -> @@ -166,7 +165,7 @@ class RustMatrixAuthenticationService( override suspend fun importCreatedSession(externalSession: ExternalSession): Result = withContext(coroutineDispatchers.io) { runCatchingExceptions { - currentClient ?: error("You need to call `setHomeserver()` first") + val client = currentClient ?: error("You need to call `setHomeserver()` first") val currentSessionPaths = sessionPaths ?: error("You need to call `setHomeserver()` first") val sessionData = externalSession.toSessionData( isTokenValid = true, @@ -174,8 +173,21 @@ class RustMatrixAuthenticationService( passphrase = pendingPassphrase, sessionPaths = currentSessionPaths, ) - clear() + + // We restore the client using the just retrieved session data + client.restoreSession(sessionData.toSession()) + val matrixClient = rustMatrixClientFactory.create(client) + + // We wait for the verification state to be known + matrixClient.waitForKnownVerificationState() + + // And once it's ready we share it and save the actual session data + newMatrixClientObservers.forEach { it.invoke(matrixClient) } sessionStore.addSession(sessionData) + + // Clean up the strong reference held here since it's no longer necessary + currentClient = null + SessionId(sessionData.userId) } } @@ -244,6 +256,8 @@ class RustMatrixAuthenticationService( sessionPaths = currentSessionPaths, ) val matrixClient = rustMatrixClientFactory.create(client) + matrixClient.waitForKnownVerificationState() + newMatrixClientObservers.forEach { it.invoke(matrixClient) } sessionStore.addSession(sessionData) @@ -287,14 +301,16 @@ class RustMatrixAuthenticationService( runCatchingExceptions { val client = makeQrCodeLoginClient( sessionPaths = emptySessionPaths, - passphrase = pendingPassphrase, qrCodeData = sdkQrCodeLoginData, ) - client.loginWithQrCode( - qrCodeData = qrCodeData.rustQrCodeData, + client.newLoginWithQrCodeHandler( oidcConfiguration = oidcConfiguration, - progressListener = progressListener, - ) + ).use { + it.scan( + qrCodeData = qrCodeData.rustQrCodeData, + progressListener = progressListener, + ) + } // Ensure that the user is not already logged in with the same account ensureNotAlreadyLoggedIn(client) val sessionData = client.session() @@ -343,7 +359,6 @@ class RustMatrixAuthenticationService( private suspend fun makeQrCodeLoginClient( sessionPaths: SessionPaths, - passphrase: String?, qrCodeData: QrCodeData, ): Client { Timber.d("Creating client for QR Code login with simplified sliding sync") @@ -353,7 +368,6 @@ class RustMatrixAuthenticationService( passphrase = pendingPassphrase, slidingSyncType = ClientBuilderSlidingSync.Discovered, ) - .sessionPassphrase(passphrase) .serverNameOrHomeserverUrl(qrCodeData.serverName()!!) .build() } @@ -362,4 +376,12 @@ class RustMatrixAuthenticationService( currentClient?.close() currentClient = null } + + private suspend fun MatrixClient.waitForKnownVerificationState() { + withTimeoutOrNull(10.seconds) { + Timber.d("Waiting for a known verification status...") + val status = sessionVerificationService.sessionVerifiedStatus.first { it != SessionVerifiedStatus.Unknown } + Timber.d("Finished waiting for a known verification status: $status") + } ?: Timber.w("Timed out waiting for a known verification status") + } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt index 7b1c614bec..65e15b5f2b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -42,5 +43,8 @@ object QrErrorMapper { is RustHumanQrLoginException.Unknown -> QrLoginException.Unknown is RustHumanQrLoginException.OidcMetadataInvalid -> QrLoginException.OidcMetadataInvalid is RustHumanQrLoginException.SlidingSyncNotAvailable -> QrLoginException.SlidingSyncNotAvailable + is RustHumanQrLoginException.CheckCodeAlreadySent -> QrLoginException.CheckCodeAlreadySent + is RustHumanQrLoginException.CheckCodeCannotBeSent -> QrLoginException.CheckCodeCannotBeSent + is RustHumanQrLoginException.NotFound -> QrLoginException.NotFound } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrLoginProgressExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrLoginProgressExtensions.kt index 6c95f31a9f..96ae276b0f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrLoginProgressExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrLoginProgressExtensions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,6 +16,7 @@ fun QrLoginProgress.toStep(): QrCodeLoginStep { is QrLoginProgress.EstablishingSecureChannel -> QrCodeLoginStep.EstablishingSecureChannel(checkCodeString) is QrLoginProgress.Starting -> QrCodeLoginStep.Starting is QrLoginProgress.WaitingForToken -> QrCodeLoginStep.WaitingForToken(userCode) + is QrLoginProgress.SyncingSecrets -> QrCodeLoginStep.SyncingSecrets is QrLoginProgress.Done -> QrCodeLoginStep.Finished } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/RustQrCodeLoginDataFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/RustQrCodeLoginDataFactory.kt index 17cff41bc1..0e2c206805 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/RustQrCodeLoginDataFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/RustQrCodeLoginDataFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,14 +10,12 @@ package io.element.android.libraries.matrix.impl.auth.qrlogin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginDataFactory import org.matrix.rustcomponents.sdk.QrCodeData @ContributesBinding(AppScope::class) -@Inject class RustQrCodeLoginDataFactory : MatrixQrCodeLoginDataFactory { override fun parseQrCodeData(data: ByteArray): Result { return runCatchingExceptions { SdkQrCodeLoginData(QrCodeData.fromBytes(data)) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/SdkQrCodeLoginData.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/SdkQrCodeLoginData.kt index 485e9bfaec..b269804091 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/SdkQrCodeLoginData.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/SdkQrCodeLoginData.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/certificates/DefaultUserCertificatesProvider.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/certificates/DefaultUserCertificatesProvider.kt index d5d637f54b..902fe5beb2 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/certificates/DefaultUserCertificatesProvider.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/certificates/DefaultUserCertificatesProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,13 +10,11 @@ package io.element.android.libraries.matrix.impl.certificates import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import timber.log.Timber import java.security.KeyStore import java.security.KeyStoreException @ContributesBinding(AppScope::class) -@Inject class DefaultUserCertificatesProvider : UserCertificatesProvider { /** * Get additional user-installed certificates from the `AndroidCAStore` `Keystore`. diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/certificates/UserCertificatesProvider.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/certificates/UserCertificatesProvider.kt index c1936bd348..90d1584f2c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/certificates/UserCertificatesProvider.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/certificates/UserCertificatesProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/core/ProgressWatcherWrapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/core/ProgressWatcherWrapper.kt index b79dba27b9..058243a482 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/core/ProgressWatcherWrapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/core/ProgressWatcherWrapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/core/RustSendHandle.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/core/RustSendHandle.kt index 9d5e113703..bb472f7c5f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/core/RustSendHandle.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/core/RustSendHandle.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/RoomModule.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/RoomModule.kt new file mode 100644 index 0000000000..af6f0325d3 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/RoomModule.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.di + +import dev.zacsweers.metro.BindingContainer +import dev.zacsweers.metro.ContributesTo +import dev.zacsweers.metro.Provides +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.di.annotations.RoomCoroutineScope +import io.element.android.libraries.matrix.api.room.BaseRoom +import kotlinx.coroutines.CoroutineScope + +@BindingContainer +@ContributesTo(RoomScope::class) +object RoomModule { + @RoomCoroutineScope + @Provides + fun providesSessionCoroutineScope(room: BaseRoom): CoroutineScope { + return room.roomCoroutineScope + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt index 37cf406f5f..6ca7d27a8f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupStateMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupStateMapper.kt index 3c5ce8a090..a3728bb157 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupStateMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupStateMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupUploadStateMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupUploadStateMapper.kt index b3fa0d7605..393813ab7a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupUploadStateMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupUploadStateMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/EnableRecoveryProgressMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/EnableRecoveryProgressMapper.kt index 90a3cf5c44..4d0de46060 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/EnableRecoveryProgressMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/EnableRecoveryProgressMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/EncryptionExtension.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/EncryptionExtension.kt index 8b051b916d..1fd670ea36 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/EncryptionExtension.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/EncryptionExtension.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RecoveryExceptionMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RecoveryExceptionMapper.kt index 5568d008ec..f5d1a4253b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RecoveryExceptionMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RecoveryExceptionMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -29,7 +30,7 @@ fun Throwable.mapRecoveryException(): RecoveryException { } } else -> RecoveryException.Client( - ClientException.Other("Unknown error") + ClientException.Other("Unknown error", this) ) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RecoveryStateMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RecoveryStateMapper.kt index 94f3c5d953..dbb2fd9cc5 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RecoveryStateMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RecoveryStateMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt index 91cafd1df0..e081d1c9f1 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt @@ -1,12 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.encryption +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.extensions.flatMap import io.element.android.libraries.core.extensions.mapFailure @@ -42,6 +44,7 @@ import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.EnableRecoveryProgressListener import org.matrix.rustcomponents.sdk.Encryption import org.matrix.rustcomponents.sdk.UserIdentity +import timber.log.Timber import org.matrix.rustcomponents.sdk.BackupUploadState as RustBackupUploadState import org.matrix.rustcomponents.sdk.EnableRecoveryProgress as RustEnableRecoveryProgress import org.matrix.rustcomponents.sdk.RecoveryException as RustRecoveryException @@ -103,14 +106,20 @@ class RustEncryptionService( * TODO This is a temporary workaround, when we will have a way to observe * the sessions, this code will have to be updated. */ - override val hasDevicesToVerifyAgainst: StateFlow = flow { + override val hasDevicesToVerifyAgainst: StateFlow> = flow { while (currentCoroutineContext().isActive) { - val result = hasDevicesToVerifyAgainst().getOrDefault(false) - emit(result) + val result = hasDevicesToVerifyAgainst() + result + .onSuccess { + emit(AsyncData.Success(it)) + } + .onFailure { + Timber.e(it, "Failed to get hasDevicesToVerifyAgainst, retrying in 5s...") + } delay(5_000) } } - .stateIn(sessionCoroutineScope, SharingStarted.Eagerly, false) + .stateIn(sessionCoroutineScope, SharingStarted.Eagerly, AsyncData.Uninitialized) override suspend fun enableBackups(): Result = withContext(dispatchers.io) { runCatchingExceptions { @@ -231,10 +240,6 @@ class RustEncryptionService( } } - override suspend fun isUserVerified(userId: UserId): Result = runCatchingExceptions { - getUserIdentityInternal(userId).isVerified() - } - override suspend fun pinUserIdentity(userId: UserId): Result = runCatchingExceptions { getUserIdentityInternal(userId).pin() } @@ -243,8 +248,8 @@ class RustEncryptionService( getUserIdentityInternal(userId).withdrawVerification() } - override suspend fun getUserIdentity(userId: UserId): Result = runCatchingExceptions { - val identity = getUserIdentityInternal(userId) + override suspend fun getUserIdentity(userId: UserId, fallbackToServer: Boolean): Result = runCatchingExceptions { + val identity = getUserIdentityInternal(userId, fallbackToServer) val isVerified = identity.isVerified() when { identity.hasVerificationViolation() -> IdentityState.VerificationViolation @@ -254,10 +259,10 @@ class RustEncryptionService( } } - suspend fun getUserIdentityInternal(userId: UserId): UserIdentity { + private suspend fun getUserIdentityInternal(userId: UserId, fallbackToServer: Boolean = true): UserIdentity { return service.userIdentity( userId = userId.value, - // requestFromHomeserverIfNeeded = true, + fallbackToServer = fallbackToServer, ) ?: error("User identity not found") } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustIdentityResetHandle.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustIdentityResetHandle.kt index 3651cc52ac..4813ec1cc3 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustIdentityResetHandle.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustIdentityResetHandle.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/SteadyStateExceptionMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/SteadyStateExceptionMapper.kt index 5755eff49e..08b6b2a032 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/SteadyStateExceptionMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/SteadyStateExceptionMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/exception/ClientException.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/exception/ClientException.kt index 21b7f393a1..eefa6d4986 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/exception/ClientException.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/exception/ClientException.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,15 +15,16 @@ fun Throwable.mapClientException(): ClientException { return when (this) { is RustClientException -> { when (this) { - is RustClientException.Generic -> ClientException.Generic(msg, details) + is RustClientException.Generic -> ClientException.Generic(message = msg, details = details, cause = this) is RustClientException.MatrixApi -> ClientException.MatrixApi( kind = kind.map(), code = code, message = msg, details = details, + cause = this, ) } } - else -> ClientException.Other(message ?: "Unknown error") + else -> ClientException.Other(message ?: "Unknown error", this) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/exception/ErrorKind.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/exception/ErrorKind.kt index 12b15b6465..cb82c92f32 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/exception/ErrorKind.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/exception/ErrorKind.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultPassphraseGenerator.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultPassphraseGenerator.kt index b6d9aa2b89..d13ff7779e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultPassphraseGenerator.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultPassphraseGenerator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,13 +11,11 @@ package io.element.android.libraries.matrix.impl.keys import android.util.Base64 import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import java.security.SecureRandom private const val SECRET_SIZE = 256 @ContributesBinding(AppScope::class) -@Inject class DefaultPassphraseGenerator : PassphraseGenerator { override fun generatePassphrase(): String? { val key = ByteArray(size = SECRET_SIZE) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/PassphraseGenerator.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/PassphraseGenerator.kt index f6725a5dfb..e0f925fa28 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/PassphraseGenerator.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/keys/PassphraseGenerator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/HumanQrGrantLoginExceptionExtension.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/HumanQrGrantLoginExceptionExtension.kt new file mode 100644 index 0000000000..2d47b60def --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/HumanQrGrantLoginExceptionExtension.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.linknewdevice + +import io.element.android.libraries.matrix.api.linknewdevice.ErrorType +import org.matrix.rustcomponents.sdk.HumanQrGrantLoginException + +internal fun HumanQrGrantLoginException.map() = when (this) { + is HumanQrGrantLoginException.DeviceIdAlreadyInUse -> ErrorType.DeviceIdAlreadyInUse(message.orEmpty()) + is HumanQrGrantLoginException.InvalidCheckCode -> ErrorType.InvalidCheckCode(message.orEmpty()) + is HumanQrGrantLoginException.MissingSecretsBackup -> ErrorType.MissingSecretsBackup(message.orEmpty()) + is HumanQrGrantLoginException.NotFound -> ErrorType.NotFound(message.orEmpty()) + is HumanQrGrantLoginException.UnableToCreateDevice -> ErrorType.UnableToCreateDevice(message.orEmpty()) + is HumanQrGrantLoginException.Unknown -> ErrorType.Unknown(message.orEmpty()) + is HumanQrGrantLoginException.UnsupportedProtocol -> ErrorType.UnsupportedProtocol(message.orEmpty()) +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/QrCodeDataParser.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/QrCodeDataParser.kt new file mode 100644 index 0000000000..6472850a8c --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/QrCodeDataParser.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.linknewdevice + +import org.matrix.rustcomponents.sdk.QrCodeData + +interface QrCodeDataParser { + fun parse(data: ByteArray): QrCodeData +} + +class RustQrCodeDataParser : QrCodeDataParser { + override fun parse(data: ByteArray): QrCodeData { + return QrCodeData.fromBytes(data) + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustCheckCodeSender.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustCheckCodeSender.kt new file mode 100644 index 0000000000..9919d1fafc --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustCheckCodeSender.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.linknewdevice + +import io.element.android.libraries.core.extensions.runCatchingExceptions +import io.element.android.libraries.matrix.api.linknewdevice.CheckCodeSender +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext +import org.matrix.rustcomponents.sdk.CheckCodeSender as FfiCheckCodeSender + +class RustCheckCodeSender( + private val inner: FfiCheckCodeSender, + private val sessionDispatcher: CoroutineDispatcher, +) : CheckCodeSender { + override suspend fun validate(code: UByte): Boolean = withContext(sessionDispatcher) { + runCatchingExceptions { + // TODO https://github.com/matrix-org/matrix-rust-sdk/pull/5957 + // inner.validate(code) + true + }.getOrNull() ?: true + } + + override suspend fun send(code: UByte): Result = withContext(sessionDispatcher) { + runCatchingExceptions { + inner.send(code) + } + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkDesktopHandler.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkDesktopHandler.kt new file mode 100644 index 0000000000..211bdc3d4e --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkDesktopHandler.kt @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.linknewdevice + +import io.element.android.libraries.core.log.logger.LoggerTag +import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopHandler +import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopStep +import io.element.android.libraries.matrix.api.logs.LoggerTags +import io.element.android.libraries.matrix.impl.auth.qrlogin.QrErrorMapper +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.matrix.rustcomponents.sdk.GrantLoginWithQrCodeHandler +import org.matrix.rustcomponents.sdk.GrantQrLoginProgress +import org.matrix.rustcomponents.sdk.GrantQrLoginProgressListener +import org.matrix.rustcomponents.sdk.HumanQrGrantLoginException +import org.matrix.rustcomponents.sdk.QrCodeDecodeException +import timber.log.Timber + +private val tag = LoggerTag("RustLinkDesktopHandler", LoggerTags.linkNewDevice) + +class RustLinkDesktopHandler( + private val inner: GrantLoginWithQrCodeHandler, + private val sessionCoroutineScope: CoroutineScope, + private val sessionDispatcher: CoroutineDispatcher, + private val qrCodeDataParser: QrCodeDataParser, +) : LinkDesktopHandler { + private val _linkDesktopStep = MutableStateFlow(LinkDesktopStep.Uninitialized) + override val linkDesktopStep: StateFlow = _linkDesktopStep.asStateFlow() + + override suspend fun handleScannedQrCode(data: ByteArray) = withContext(sessionDispatcher) { + Timber.tag(tag.value).d("Emit Uninitialized") + _linkDesktopStep.emit(LinkDesktopStep.Uninitialized) + try { + val qrCodeData = qrCodeDataParser.parse(data) + inner.scan( + qrCodeData = qrCodeData, + progressListener = object : GrantQrLoginProgressListener { + override fun onUpdate(state: GrantQrLoginProgress) { + sessionCoroutineScope.launch { + val mappedState = state.map() + Timber.tag(tag.value).d("Emit ${mappedState::class.java.simpleName}") + _linkDesktopStep.emit(mappedState) + } + } + } + ) + } catch (e: QrCodeDecodeException) { + Timber.tag(tag.value).w(e, "Invalid QR code scanned") + _linkDesktopStep.emit( + LinkDesktopStep.InvalidQrCode( + error = QrErrorMapper.map(e) + ) + ) + } catch (e: HumanQrGrantLoginException) { + Timber.tag(tag.value).w(e, "Error during QR login grant") + _linkDesktopStep.emit(LinkDesktopStep.Error(e.map())) + } + } + + private fun GrantQrLoginProgress.map() = when (this) { + GrantQrLoginProgress.Done -> LinkDesktopStep.Done + GrantQrLoginProgress.Starting -> LinkDesktopStep.Starting + GrantQrLoginProgress.SyncingSecrets -> LinkDesktopStep.SyncingSecrets + is GrantQrLoginProgress.WaitingForAuth -> LinkDesktopStep.WaitingForAuth( + verificationUri = verificationUri, + ) + is GrantQrLoginProgress.EstablishingSecureChannel -> LinkDesktopStep.EstablishingSecureChannel( + checkCode = checkCode, + checkCodeString = checkCodeString, + ) + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkMobileHandler.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkMobileHandler.kt new file mode 100644 index 0000000000..0189987d96 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkMobileHandler.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.linknewdevice + +import io.element.android.libraries.core.log.logger.LoggerTag +import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileHandler +import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileStep +import io.element.android.libraries.matrix.api.logs.LoggerTags +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.matrix.rustcomponents.sdk.GrantGeneratedQrLoginProgress +import org.matrix.rustcomponents.sdk.GrantGeneratedQrLoginProgressListener +import org.matrix.rustcomponents.sdk.GrantLoginWithQrCodeHandler +import org.matrix.rustcomponents.sdk.HumanQrGrantLoginException +import timber.log.Timber + +private val tag = LoggerTag("RustLinkMobileHandler", LoggerTags.linkNewDevice) + +class RustLinkMobileHandler( + private val inner: GrantLoginWithQrCodeHandler, + private val sessionCoroutineScope: CoroutineScope, + private val sessionDispatcher: CoroutineDispatcher, +) : LinkMobileHandler { + private val _linkMobileStep = MutableStateFlow(LinkMobileStep.Uninitialized) + override val linkMobileStep: Flow = _linkMobileStep.asStateFlow() + + override suspend fun start() = withContext(sessionDispatcher) { + Timber.tag(tag.value).d("Emit Uninitialized") + _linkMobileStep.emit(LinkMobileStep.Uninitialized) + try { + inner.generate( + progressListener = object : GrantGeneratedQrLoginProgressListener { + override fun onUpdate(state: GrantGeneratedQrLoginProgress) { + sessionCoroutineScope.launch { + val mappedState = state.map() + Timber.tag(tag.value).d("Emit ${mappedState::class.java.simpleName}") + _linkMobileStep.emit(mappedState) + } + } + } + ) + } catch (e: HumanQrGrantLoginException) { + Timber.tag(tag.value).w(e, "Error during QR login grant") + _linkMobileStep.emit(LinkMobileStep.Error(e.map())) + } + } + + private fun GrantGeneratedQrLoginProgress.map(): LinkMobileStep { + return when (this) { + GrantGeneratedQrLoginProgress.Done -> LinkMobileStep.Done + is GrantGeneratedQrLoginProgress.QrReady -> { + LinkMobileStep.QrReady(String(qrCode.toBytes(), Charsets.ISO_8859_1)) + } + is GrantGeneratedQrLoginProgress.QrScanned -> LinkMobileStep.QrScanned( + RustCheckCodeSender( + inner = checkCodeSender, + sessionDispatcher = sessionDispatcher, + ) + ) + GrantGeneratedQrLoginProgress.Starting -> LinkMobileStep.Starting + GrantGeneratedQrLoginProgress.SyncingSecrets -> LinkMobileStep.SyncingSecrets + is GrantGeneratedQrLoginProgress.WaitingForAuth -> LinkMobileStep.WaitingForAuth(verificationUri) + } + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/IdentityState.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/IdentityState.kt index 4a2b2e5ab5..5bb95fc6ff 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/IdentityState.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/IdentityState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt index 2b5cac67ea..3199ebf71a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/Session.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -27,7 +28,6 @@ internal fun Session.toSessionData( refreshToken = refreshToken, homeserverUrl = homeserverUrl ?: this.homeserverUrl, oidcData = oidcData, - slidingSyncProxy = null, loginTimestamp = Date(), isTokenValid = isTokenValid, loginType = loginType, @@ -53,7 +53,6 @@ internal fun ExternalSession.toSessionData( refreshToken = refreshToken, homeserverUrl = homeserverUrl, oidcData = null, - slidingSyncProxy = slidingSyncProxy, loginTimestamp = Date(), isTokenValid = isTokenValid, loginType = loginType, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/UserProfileMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/UserProfileMapper.kt index 6cc338610b..14c813548a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/UserProfileMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mapper/UserProfileMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioDetails.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioDetails.kt index 639660a989..73eb408fdd 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioDetails.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioDetails.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioInfo.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioInfo.kt index f00392aa42..9c20ad7c3f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioInfo.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/FileInfo.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/FileInfo.kt index 7a0d93972f..9059e79be2 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/FileInfo.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/FileInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 e43ded8be1..83ab5ebb77 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaSource.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaSource.kt index 0d61c690cf..08bef24722 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaSource.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaSource.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaUploadHandlerImpl.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaUploadHandlerImpl.kt index dbfa67f374..c3f5d97567 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaUploadHandlerImpl.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaUploadHandlerImpl.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaFile.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaFile.kt index f6e944ae55..4ecb33d6d5 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaFile.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaFile.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt index d7cd9c4092..892c99f64a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaPreviewService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaPreviewService.kt index 15e86ab983..454c600ea4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaPreviewService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaPreviewService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ThumbnailInfo.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ThumbnailInfo.kt index ca56ea4e10..0303f8700b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ThumbnailInfo.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ThumbnailInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/VideoInfo.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/VideoInfo.kt index c6143b8918..33f622fb3d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/VideoInfo.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/VideoInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mxc/DefaultMxcTools.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mxc/DefaultMxcTools.kt new file mode 100644 index 0000000000..b9ff2f5505 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/mxc/DefaultMxcTools.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.mxc + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.matrix.api.mxc.MxcTools + +@ContributesBinding(AppScope::class) +class DefaultMxcTools : MxcTools { + /** + * Regex to match a Matrix Content (mxc://) URI. + * + * See: https://spec.matrix.org/v1.8/client-server-api/#matrix-content-mxc-uris + */ + private val mxcRegex = Regex("""^mxc://([^/]+)/([^/]+)$""") + + /** + * Sanitizes an mxcUri to be used as a relative file path. + * + * @param mxcUri the Matrix Content (mxc://) URI of the file. + * @return the relative file path as "/" or null if the mxcUri is invalid. + */ + override fun mxcUri2FilePath(mxcUri: String): String? = mxcRegex.matchEntire(mxcUri)?.let { match -> + buildString { + append(match.groupValues[1]) + append("/") + append(match.groupValues[2]) + } + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt index e7831bc492..511c899fdb 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,6 +13,7 @@ import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.notification.NotificationContent import io.element.android.libraries.matrix.api.notification.NotificationData @@ -38,11 +40,11 @@ class NotificationMapper( isDirect = item.roomInfo.isDirect, activeMembersCount = item.roomInfo.joinedMembersCount.toInt(), ) + val timestamp = item.timestamp() ?: clock.epochMillis() NotificationData( sessionId = sessionId, eventId = eventId, - // FIXME once the `NotificationItem` in the SDK returns the thread id - threadId = null, + threadId = item.threadId?.let(::ThreadId), roomId = roomId, senderAvatarUrl = item.senderInfo.avatarUrl, senderDisplayName = item.senderInfo.displayName, @@ -51,10 +53,11 @@ class NotificationMapper( roomDisplayName = item.roomInfo.displayName, isDirect = item.roomInfo.isDirect, isDm = isDm, + isSpace = item.roomInfo.isSpace, isEncrypted = item.roomInfo.isEncrypted.orFalse(), isNoisy = item.isNoisy.orFalse(), - timestamp = item.timestamp() ?: clock.epochMillis(), - content = item.event.use { notificationContentMapper.map(it) }.getOrThrow(), + timestamp = timestamp, + content = notificationContentMapper.map(item.event).getOrThrow(), hasMention = item.hasMention.orFalse(), ) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt index 1379392ae3..59d95af05b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -44,7 +45,7 @@ class RustNotificationService( } val items = notificationClient.getNotifications(requests) buildMap { - val eventIds = requests.flatMap { it.eventIds } + val eventIds = requests.flatMap { it.eventIds }.distinct() for (rawEventId in eventIds) { val roomId = RoomId(requests.find { it.eventIds.contains(rawEventId) }?.roomId!!) val eventId = EventId(rawEventId) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt index 2ca4a3c823..450216b452 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,7 +18,7 @@ import io.element.android.libraries.matrix.impl.timeline.item.event.EventMessage import org.matrix.rustcomponents.sdk.MessageLikeEventContent import org.matrix.rustcomponents.sdk.StateEventContent import org.matrix.rustcomponents.sdk.TimelineEvent -import org.matrix.rustcomponents.sdk.TimelineEventType +import org.matrix.rustcomponents.sdk.TimelineEventContent import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.RtcNotificationType as SdkRtcNotificationType @@ -25,18 +26,19 @@ class TimelineEventToNotificationContentMapper { fun map(timelineEvent: TimelineEvent): Result { return runCatchingExceptions { timelineEvent.use { - timelineEvent.eventType().use { eventType -> - eventType.toContent(senderId = UserId(timelineEvent.senderId())) + val senderId = UserId(timelineEvent.senderId()) + timelineEvent.content().use { eventContent -> + eventContent.toContent(senderId = senderId) } } } } } -private fun TimelineEventType.toContent(senderId: UserId): NotificationContent { +private fun TimelineEventContent.toContent(senderId: UserId): NotificationContent { return when (this) { - is TimelineEventType.MessageLike -> content.toContent(senderId) - is TimelineEventType.State -> content.toContent() + is TimelineEventContent.MessageLike -> content.toContent(senderId) + is TimelineEventContent.State -> content.toContent() } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RoomNotificationSettingsMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RoomNotificationSettingsMapper.kt index f83f7890d0..f453c93ec1 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RoomNotificationSettingsMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RoomNotificationSettingsMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt index b4f3e70754..7da0f14d14 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -130,13 +131,17 @@ class RustNotificationSettingsService( } } - override suspend fun getRoomsWithUserDefinedRules(): Result> = + override suspend fun getRoomsWithUserDefinedRules(): Result> = runCatchingExceptions { - notificationSettings.await().getRoomsWithUserDefinedRules(enabled = true) + notificationSettings.await().getRoomsWithUserDefinedRules(enabled = true).map(::RoomId) } override suspend fun canHomeServerPushEncryptedEventsToDevice(): Result = runCatchingExceptions { notificationSettings.await().canPushEncryptedEventToDevice() } + + override suspend fun getRawPushRules(): Result = runCatchingExceptions { + notificationSettings.await().getRawPushRules() + } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/oidc/AccountManagementAction.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/oidc/AccountManagementAction.kt index 2fa852f1f9..f998126e17 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/oidc/AccountManagementAction.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/oidc/AccountManagementAction.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/paths/SessionPaths.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/paths/SessionPaths.kt index 16e7854b80..1888cd0f88 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/paths/SessionPaths.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/paths/SessionPaths.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/paths/SessionPathsFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/paths/SessionPathsFactory.kt index d583872503..d2e519fa9e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/paths/SessionPathsFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/paths/SessionPathsFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverter.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverter.kt index c03be3bbe9..efb106acd3 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverter.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,6 @@ import android.net.Uri import androidx.core.net.toUri import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.appconfig.MatrixConfiguration import io.element.android.libraries.core.extensions.replacePrefix import io.element.android.libraries.matrix.api.permalink.MatrixToConverter @@ -20,7 +20,6 @@ import io.element.android.libraries.matrix.api.permalink.MatrixToConverter * Mapping of an input URI to a matrix.to compliant URI. */ @ContributesBinding(AppScope::class) -@Inject class DefaultMatrixToConverter : MatrixToConverter { /** * Try to convert a URL from an element web instance or from a client permalink to a matrix.to url. diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt index 2ed7990ee4..2b34138dc9 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.matrix.impl.permalink import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.core.MatrixPatterns import io.element.android.libraries.matrix.api.core.RoomAlias @@ -20,7 +20,6 @@ import org.matrix.rustcomponents.sdk.matrixToRoomAliasPermalink import org.matrix.rustcomponents.sdk.matrixToUserPermalink @ContributesBinding(AppScope::class) -@Inject class DefaultPermalinkBuilder : PermalinkBuilder { override fun permalinkForUser(userId: UserId): Result { if (!MatrixPatterns.isUserId(userId.value)) { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParser.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParser.kt index 0454b719e3..e59530e874 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParser.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParser.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.libraries.matrix.impl.permalink import androidx.core.net.toUri import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomAlias @@ -32,7 +32,6 @@ import org.matrix.rustcomponents.sdk.parseMatrixEntityFrom * or matrix: permalinks (e.g. matrix:u/chagai95:matrix.org) */ @ContributesBinding(AppScope::class) -@Inject class DefaultPermalinkParser( private val matrixToConverter: MatrixToConverter ) : PermalinkParser { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/platform/RustInitPlatformService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/platform/RustInitPlatformService.kt index 492d5e5792..cb1fdc93fd 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/platform/RustInitPlatformService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/platform/RustInitPlatformService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,14 +10,12 @@ package io.element.android.libraries.matrix.impl.platform import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.matrix.api.platform.InitPlatformService import io.element.android.libraries.matrix.api.tracing.TracingConfiguration import io.element.android.libraries.matrix.impl.tracing.map import org.matrix.rustcomponents.sdk.initPlatform @ContributesBinding(AppScope::class) -@Inject class RustInitPlatformService : InitPlatformService { override fun init(tracingConfiguration: TracingConfiguration) { initPlatform( diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/poll/PollAnswer.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/poll/PollAnswer.kt index 070ce1a5c6..df9eb142c6 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/poll/PollAnswer.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/poll/PollAnswer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/poll/PollKind.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/poll/PollKind.kt index ad434d36d9..d1d7bdc934 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/poll/PollKind.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/poll/PollKind.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/proxy/DefaultProxyProvider.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/proxy/DefaultProxyProvider.kt index e5318671f5..79dc7e6271 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/proxy/DefaultProxyProvider.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/proxy/DefaultProxyProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,6 @@ import android.provider.Settings import androidx.core.content.getSystemService import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.annotations.ApplicationContext import timber.log.Timber @@ -29,7 +29,6 @@ import timber.log.Timber * ``` */ @ContributesBinding(AppScope::class) -@Inject class DefaultProxyProvider( @ApplicationContext private val context: Context diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/proxy/ProxyProvider.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/proxy/ProxyProvider.kt index 85707b1029..1428183df6 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/proxy/ProxyProvider.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/proxy/ProxyProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/pushers/RustPushersService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/pushers/RustPushersService.kt index 0fed390b14..860993d4bc 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/pushers/RustPushersService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/pushers/RustPushersService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/FocusEventException.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/FocusEventException.kt index 4b17fbba90..4199b2816c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/FocusEventException.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/FocusEventException.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/JoinedRustRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/JoinedRustRoom.kt index baa9f85906..770efb3918 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/JoinedRustRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/JoinedRustRoom.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -50,13 +51,16 @@ import io.element.android.libraries.matrix.impl.widget.generateWidgetWebViewUrl import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.DateDividerMode import org.matrix.rustcomponents.sdk.IdentityStatusChangeListener @@ -73,6 +77,7 @@ import org.matrix.rustcomponents.sdk.getElementCallRequiredPermissions import org.matrix.rustcomponents.sdk.use import timber.log.Timber import uniffi.matrix_sdk.RoomPowerLevelChanges +import uniffi.matrix_sdk_ui.TimelineReadReceiptTracking import kotlin.coroutines.cancellation.CancellationException import org.matrix.rustcomponents.sdk.IdentityStatusChange as RustIdentityStateChange import org.matrix.rustcomponents.sdk.KnockRequest as InnerKnockRequest @@ -91,8 +96,6 @@ class JoinedRustRoom( private val roomDispatcher = coroutineDispatchers.io.limitedParallelism(32) private val innerRoom = baseRoom.innerRoom - override val syncUpdateFlow = MutableStateFlow(0L) - override val roomTypingMembersFlow: Flow> = mxCallbackFlow { val initial = emptyList() channel.trySend(initial) @@ -135,11 +138,24 @@ class JoinedRustRoom( override val roomNotificationSettingsStateFlow = MutableStateFlow(RoomNotificationSettingsState.Unknown) - override val liveTimeline = liveInnerTimeline.map(mode = Timeline.Mode.Live) { - syncUpdateFlow.value = systemClock.epochMillis() - } + override val liveTimeline = liveInnerTimeline.map(mode = Timeline.Mode.Live) + + override val syncUpdateFlow = flow { + var counter = 0L + liveTimeline.onSyncedEventReceived.collect { + emit(++counter) + } + }.stateIn( + scope = roomCoroutineScope, + started = WhileSubscribed(), + initialValue = 0L, + ) init { + subscribeToRoomMembersChange() + } + + private fun subscribeToRoomMembersChange() { val powerLevelChanges = roomInfoFlow.map { it.roomPowerLevels }.distinctUntilChanged() val membershipChanges = liveTimeline.membershipChangeEventReceived.onStart { emit(Unit) } combine(membershipChanges, powerLevelChanges) { _, _ -> } @@ -222,7 +238,7 @@ class JoinedRustRoom( filter = filter, internalIdPrefix = internalIdPrefix, dateDividerMode = dateDividerMode, - trackReadReceipts = trackReadReceipts, + trackReadReceipts = if (trackReadReceipts) TimelineReadReceiptTracking.ALL_EVENTS else TimelineReadReceiptTracking.DISABLED, reportUtds = true, ) ).let { innerTimeline -> @@ -381,10 +397,12 @@ class JoinedRustRoom( invite = roomPowerLevelsValues.invite, kick = roomPowerLevelsValues.kick, redact = roomPowerLevelsValues.redactEvents, - eventsDefault = roomPowerLevelsValues.sendEvents, + stateDefault = roomPowerLevelsValues.stateDefault, + eventsDefault = roomPowerLevelsValues.eventsDefault, roomName = roomPowerLevelsValues.roomName, roomAvatar = roomPowerLevelsValues.roomAvatar, roomTopic = roomPowerLevelsValues.roomTopic, + spaceChild = roomPowerLevelsValues.spaceChild, ) innerRoom.applyPowerLevelChanges(changes) } @@ -473,11 +491,11 @@ class JoinedRustRoom( override fun destroy() { baseRoom.destroy() liveInnerTimeline.destroy() + Timber.d("Room $roomId destroyed") } private fun InnerTimeline.map( mode: Timeline.Mode, - onNewSyncedEvent: () -> Unit = {}, ): Timeline { val timelineCoroutineScope = roomCoroutineScope.childScope(coroutineDispatchers.main, "TimelineScope-$roomId-$this") return RustTimeline( @@ -488,7 +506,6 @@ class JoinedRustRoom( coroutineScope = timelineCoroutineScope, dispatcher = roomDispatcher, roomContentForwarder = roomContentForwarder, - onNewSyncedEvent = onNewSyncedEvent, ) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/Mention.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/Mention.kt index a8e039720d..22303ae68c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/Mention.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/Mention.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MessageEventType.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MessageEventType.kt index 14bd6d5e9d..47b37b7923 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MessageEventType.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MessageEventType.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,11 +12,21 @@ import io.element.android.libraries.matrix.api.room.MessageEventType import org.matrix.rustcomponents.sdk.MessageLikeEventType fun MessageEventType.map(): MessageLikeEventType = when (this) { + MessageEventType.Audio -> MessageLikeEventType.Audio + MessageEventType.Beacon -> MessageLikeEventType.Beacon MessageEventType.CallAnswer -> MessageLikeEventType.CallAnswer + MessageEventType.CallCandidates -> MessageLikeEventType.CallCandidates MessageEventType.CallInvite -> MessageLikeEventType.CallInvite MessageEventType.CallHangup -> MessageLikeEventType.CallHangup - MessageEventType.CallCandidates -> MessageLikeEventType.CallCandidates - MessageEventType.RtcNotification -> MessageLikeEventType.RtcNotification + MessageEventType.CallNegotiate -> MessageLikeEventType.CallNegotiate + MessageEventType.CallNotify -> MessageLikeEventType.CallNotify + MessageEventType.CallReject -> MessageLikeEventType.CallReject + MessageEventType.CallSdpStreamMetadataChanged -> MessageLikeEventType.CallSdpStreamMetadataChanged + MessageEventType.CallSelectAnswer -> MessageLikeEventType.CallSelectAnswer + MessageEventType.Emote -> MessageLikeEventType.Emote + MessageEventType.Encrypted -> MessageLikeEventType.Encrypted + MessageEventType.File -> MessageLikeEventType.File + MessageEventType.Image -> MessageLikeEventType.Image MessageEventType.KeyVerificationReady -> MessageLikeEventType.KeyVerificationReady MessageEventType.KeyVerificationStart -> MessageLikeEventType.KeyVerificationStart MessageEventType.KeyVerificationCancel -> MessageLikeEventType.KeyVerificationCancel @@ -23,17 +34,23 @@ fun MessageEventType.map(): MessageLikeEventType = when (this) { MessageEventType.KeyVerificationKey -> MessageLikeEventType.KeyVerificationKey MessageEventType.KeyVerificationMac -> MessageLikeEventType.KeyVerificationMac MessageEventType.KeyVerificationDone -> MessageLikeEventType.KeyVerificationDone + MessageEventType.Location -> MessageLikeEventType.Location + MessageEventType.Message -> MessageLikeEventType.Message MessageEventType.Reaction -> MessageLikeEventType.Reaction MessageEventType.RoomEncrypted -> MessageLikeEventType.RoomEncrypted MessageEventType.RoomMessage -> MessageLikeEventType.RoomMessage MessageEventType.RoomRedaction -> MessageLikeEventType.RoomRedaction + MessageEventType.RtcDecline -> MessageLikeEventType.RtcDecline MessageEventType.Sticker -> MessageLikeEventType.Sticker MessageEventType.PollEnd -> MessageLikeEventType.PollEnd MessageEventType.PollResponse -> MessageLikeEventType.PollResponse MessageEventType.PollStart -> MessageLikeEventType.PollStart + MessageEventType.RtcNotification -> MessageLikeEventType.RtcNotification MessageEventType.UnstablePollEnd -> MessageLikeEventType.UnstablePollEnd MessageEventType.UnstablePollResponse -> MessageLikeEventType.UnstablePollResponse MessageEventType.UnstablePollStart -> MessageLikeEventType.UnstablePollStart + MessageEventType.Video -> MessageLikeEventType.Video + MessageEventType.Voice -> MessageLikeEventType.Voice is MessageEventType.Other -> MessageLikeEventType.Other(type) } @@ -61,5 +78,21 @@ fun MessageLikeEventType.map(): MessageEventType = when (this) { MessageLikeEventType.UnstablePollEnd -> MessageEventType.UnstablePollEnd MessageLikeEventType.UnstablePollResponse -> MessageEventType.UnstablePollResponse MessageLikeEventType.UnstablePollStart -> MessageEventType.UnstablePollStart + MessageLikeEventType.Audio -> MessageEventType.Audio + MessageLikeEventType.Beacon -> MessageEventType.Beacon + MessageLikeEventType.CallNegotiate -> MessageEventType.CallNegotiate + MessageLikeEventType.CallNotify -> MessageEventType.CallNotify + MessageLikeEventType.CallReject -> MessageEventType.CallReject + MessageLikeEventType.CallSdpStreamMetadataChanged -> MessageEventType.CallSdpStreamMetadataChanged + MessageLikeEventType.CallSelectAnswer -> MessageEventType.CallSelectAnswer + MessageLikeEventType.Emote -> MessageEventType.Emote + MessageLikeEventType.Encrypted -> MessageEventType.Encrypted + MessageLikeEventType.File -> MessageEventType.File + MessageLikeEventType.Image -> MessageEventType.Image + MessageLikeEventType.Location -> MessageEventType.Location + MessageLikeEventType.Message -> MessageEventType.Message + MessageLikeEventType.RtcDecline -> MessageEventType.RtcDecline + MessageLikeEventType.Video -> MessageEventType.Video + MessageLikeEventType.Voice -> MessageEventType.Voice is MessageLikeEventType.Other -> MessageEventType.Other(v1) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/NotJoinedRustRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/NotJoinedRustRoom.kt index fb88cf625b..973a36365c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/NotJoinedRustRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/NotJoinedRustRoom.kt @@ -1,13 +1,13 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.room -import androidx.compose.runtime.Immutable import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.room.NotJoinedRoom @@ -15,7 +15,6 @@ import io.element.android.libraries.matrix.api.room.RoomMembershipDetails import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper -@Immutable class NotJoinedRustRoom( private val sessionId: SessionId, override val localRoom: RustBaseRoom?, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt index 323b4b50f8..e000c2f916 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomInfoExt.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomInfoExt.kt index 2b5df7484a..668cfc46df 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomInfoExt.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomInfoExt.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomInfoMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomInfoMapper.kt index 4339b69101..36da41f28a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomInfoMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomInfoMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSyncSubscriber.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSyncSubscriber.kt index c630aea6cf..724e3f62f0 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSyncSubscriber.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomSyncSubscriber.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -33,7 +34,7 @@ class RoomSyncSubscriber( } subscribedRoomIds.add(roomId) } catch (exception: Exception) { - Timber.e("Failed to subscribe to room $roomId") + Timber.e(exception, "Failed to subscribe to room $roomId") } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomType.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomType.kt index c9940a6a58..a8681e1217 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomType.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomType.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt index 6061ebd79c..8c4847a73f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,13 +18,12 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.BaseRoom -import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.RoomInfo import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembersState import io.element.android.libraries.matrix.api.room.RoomMembershipObserver -import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.draft.ComposerDraft +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility @@ -32,6 +32,7 @@ import io.element.android.libraries.matrix.impl.room.draft.into import io.element.android.libraries.matrix.impl.room.member.RoomMemberListFetcher import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper import io.element.android.libraries.matrix.impl.room.powerlevels.RoomPowerLevelsValuesMapper +import io.element.android.libraries.matrix.impl.room.powerlevels.RustRoomPermissions import io.element.android.libraries.matrix.impl.room.tombstone.map import io.element.android.libraries.matrix.impl.roomdirectory.map import io.element.android.libraries.matrix.impl.timeline.toRustReceiptType @@ -84,7 +85,9 @@ class RustBaseRoom( }.stateIn(roomCoroutineScope, started = SharingStarted.Lazily, initialValue = initialRoomInfo) override fun predecessorRoom(): PredecessorRoom? { - return innerRoom.predecessorRoom()?.map() + return runCatchingExceptions { innerRoom.predecessorRoom()?.map() } + .onFailure { Timber.e(it, "Could not get predecessor room") } + .getOrNull() } override suspend fun subscribeToSync() = roomSyncSubscriber.subscribe(roomId) @@ -177,57 +180,9 @@ class RustBaseRoom( } } - override suspend fun canUserInvite(userId: UserId): Result = withContext(roomDispatcher) { + override suspend fun roomPermissions(): Result = withContext(roomDispatcher) { runCatchingExceptions { - innerRoom.getPowerLevels().use { it.canUserInvite(userId.value) } - } - } - - override suspend fun canUserKick(userId: UserId): Result = withContext(roomDispatcher) { - runCatchingExceptions { - innerRoom.getPowerLevels().use { it.canUserKick(userId.value) } - } - } - - override suspend fun canUserBan(userId: UserId): Result = withContext(roomDispatcher) { - runCatchingExceptions { - innerRoom.getPowerLevels().use { it.canUserBan(userId.value) } - } - } - - override suspend fun canUserRedactOwn(userId: UserId): Result = withContext(roomDispatcher) { - runCatchingExceptions { - innerRoom.getPowerLevels().use { it.canUserRedactOwn(userId.value) } - } - } - - override suspend fun canUserRedactOther(userId: UserId): Result = withContext(roomDispatcher) { - runCatchingExceptions { - innerRoom.getPowerLevels().use { it.canUserRedactOther(userId.value) } - } - } - - override suspend fun canUserSendState(userId: UserId, type: StateEventType): Result = withContext(roomDispatcher) { - runCatchingExceptions { - innerRoom.getPowerLevels().use { it.canUserSendState(userId.value, type.map()) } - } - } - - override suspend fun canUserSendMessage(userId: UserId, type: MessageEventType): Result = withContext(roomDispatcher) { - runCatchingExceptions { - innerRoom.getPowerLevels().use { it.canUserSendMessage(userId.value, type.map()) } - } - } - - override suspend fun canUserTriggerRoomNotification(userId: UserId): Result = withContext(roomDispatcher) { - runCatchingExceptions { - innerRoom.getPowerLevels().use { it.canUserTriggerRoomNotification(userId.value) } - } - } - - override suspend fun canUserPinUnpin(userId: UserId): Result = withContext(roomDispatcher) { - runCatchingExceptions { - innerRoom.getPowerLevels().use { it.canUserPinUnpin(userId.value) } + RustRoomPermissions(innerRoom.getPowerLevels()) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt index ee9e7bfe52..cbc39b1c61 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -22,6 +23,11 @@ import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.awaitLoaded import io.element.android.libraries.matrix.impl.room.preview.RoomPreviewInfoMapper import io.element.android.libraries.matrix.impl.roomlist.roomOrNull +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.api.inBridgeSdkSpan +import io.element.android.services.analytics.api.recordTransaction +import io.element.android.services.analyticsproviders.api.recordChildTransaction import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.NonCancellable @@ -31,10 +37,12 @@ import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.DateDividerMode import org.matrix.rustcomponents.sdk.Membership import org.matrix.rustcomponents.sdk.Room +import org.matrix.rustcomponents.sdk.RoomInfo import org.matrix.rustcomponents.sdk.TimelineConfiguration import org.matrix.rustcomponents.sdk.TimelineFilter import org.matrix.rustcomponents.sdk.TimelineFocus import timber.log.Timber +import uniffi.matrix_sdk_ui.TimelineReadReceiptTracking import java.util.concurrent.atomic.AtomicBoolean import org.matrix.rustcomponents.sdk.RoomListService as InnerRoomListService @@ -53,8 +61,9 @@ class RustRoomFactory( private val featureFlagService: FeatureFlagService, private val roomMembershipObserver: RoomMembershipObserver, private val roomInfoMapper: RoomInfoMapper, + private val analyticsService: AnalyticsService, ) { - private val dispatcher = dispatchers.io.limitedParallelism(1) + private val dispatcher = dispatchers.computation.limitedParallelism(1) private val mutex = Mutex() private val isDestroyed: AtomicBoolean = AtomicBoolean(false) @@ -80,24 +89,21 @@ class RustRoomFactory( return@withContext null } val room = awaitRoomInRoomList(roomId) ?: return@withContext null - getBaseRoom(room) + getBaseRoom(sdkRoom = room, roomInfo = room.roomInfo()) } } - private suspend fun getBaseRoom(sdkRoom: Room): RustBaseRoom { - val initialRoomInfo = sdkRoom.roomInfo() - return RustBaseRoom( - sessionId = sessionId, - deviceId = deviceId, - innerRoom = sdkRoom, - coroutineDispatchers = dispatchers, - roomSyncSubscriber = roomSyncSubscriber, - roomMembershipObserver = roomMembershipObserver, - roomInfoMapper = roomInfoMapper, - initialRoomInfo = roomInfoMapper.map(initialRoomInfo), - sessionCoroutineScope = sessionCoroutineScope, - ) - } + private fun getBaseRoom(sdkRoom: Room, roomInfo: RoomInfo) = RustBaseRoom( + sessionId = sessionId, + deviceId = deviceId, + innerRoom = sdkRoom, + coroutineDispatchers = dispatchers, + roomSyncSubscriber = roomSyncSubscriber, + roomMembershipObserver = roomMembershipObserver, + roomInfoMapper = roomInfoMapper, + initialRoomInfo = roomInfoMapper.map(roomInfo), + sessionCoroutineScope = sessionCoroutineScope, + ) suspend fun getJoinedRoomOrPreview(roomId: RoomId, serverNames: List): GetRoomResult? = withContext(dispatcher) { mutex.withLock { @@ -105,48 +111,71 @@ class RustRoomFactory( Timber.d("Room factory is destroyed, returning null for $roomId") return@withContext null } - val sdkRoom = awaitRoomInRoomList(roomId) ?: return@withContext null - if (sdkRoom.membership() == Membership.JOINED) { - val hideThreadedEvents = featureFlagService.isFeatureEnabled(FeatureFlags.Threads) - // Init the live timeline in the SDK from the Room - val timeline = sdkRoom.timelineWithConfiguration( - TimelineConfiguration( - focus = TimelineFocus.Live(hideThreadedEvents = hideThreadedEvents), - filter = eventFilters?.let(TimelineFilter::EventTypeFilter) ?: TimelineFilter.All, - internalIdPrefix = "live", - dateDividerMode = DateDividerMode.DAILY, - trackReadReceipts = true, - reportUtds = true, - ) - ) + val sdkRoom = awaitRoomInRoomList(roomId) ?: return@withLock null + val roomInfo = sdkRoom.roomInfo() - GetRoomResult.Joined( - JoinedRustRoom( - baseRoom = getBaseRoom(sdkRoom), - notificationSettingsService = notificationSettingsService, - roomContentForwarder = roomContentForwarder, - liveInnerTimeline = timeline, - coroutineDispatchers = dispatchers, - systemClock = systemClock, - featureFlagService = featureFlagService, + val parentTransaction = analyticsService.getLongRunningTransaction(AnalyticsLongRunningTransaction.OpenRoom) + + if (roomInfo.membership == Membership.JOINED) { + analyticsService.recordTransaction( + name = "Get joined room", + operation = "RustRoomFactory.getJoinedRoomOrPreview", + parentTransaction = parentTransaction, + ) { transaction -> + val hideThreadedEvents = featureFlagService.isFeatureEnabled(FeatureFlags.Threads) + // Init the live timeline in the SDK from the Room + val timeline = transaction.recordChildTransaction( + operation = "sdkRoom.timelineWithConfiguration", + description = "Get timeline from the SDK", + ) { timelineTransaction -> + analyticsService.inBridgeSdkSpan(parentTraceId = timelineTransaction.traceId()) { + sdkRoom.timelineWithConfiguration( + TimelineConfiguration( + focus = TimelineFocus.Live(hideThreadedEvents = hideThreadedEvents), + filter = eventFilters?.let(TimelineFilter::EventTypeFilter) ?: TimelineFilter.All, + internalIdPrefix = "live", + dateDividerMode = DateDividerMode.DAILY, + trackReadReceipts = TimelineReadReceiptTracking.ALL_EVENTS, + reportUtds = true, + ) + ) + } + } + + GetRoomResult.Joined( + JoinedRustRoom( + baseRoom = getBaseRoom(sdkRoom, roomInfo), + notificationSettingsService = notificationSettingsService, + roomContentForwarder = roomContentForwarder, + liveInnerTimeline = timeline, + coroutineDispatchers = dispatchers, + systemClock = systemClock, + featureFlagService = featureFlagService, + ) ) - ) - } else { - val preview = try { - sdkRoom.previewRoom(via = serverNames) - } catch (e: Exception) { - Timber.e(e, "Failed to get room preview for $roomId") - return@withContext null } + } else { + analyticsService.recordTransaction( + name = "Get preview of room", + operation = "RustRoomFactory.getJoinedRoomOrPreview", + parentTransaction = parentTransaction, + ) { + val preview = try { + sdkRoom.previewRoom(via = serverNames) + } catch (e: Exception) { + Timber.e(e, "Failed to get room preview for $roomId") + return@recordTransaction null + } - GetRoomResult.NotJoined( - NotJoinedRustRoom( - sessionId = sessionId, - localRoom = getBaseRoom(sdkRoom), - previewInfo = RoomPreviewInfoMapper.map(preview.info()), + GetRoomResult.NotJoined( + NotJoinedRustRoom( + sessionId = sessionId, + localRoom = getBaseRoom(sdkRoom, roomInfo), + previewInfo = RoomPreviewInfoMapper.map(preview.info()), + ) ) - ) + } } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/StateEventType.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/StateEventType.kt index 70e74c88bd..76fea0beef 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/StateEventType.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/StateEventType.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,51 +12,61 @@ import io.element.android.libraries.matrix.api.room.StateEventType import org.matrix.rustcomponents.sdk.StateEventType as RustStateEventType fun StateEventType.map(): RustStateEventType = when (this) { - StateEventType.POLICY_RULE_ROOM -> RustStateEventType.POLICY_RULE_ROOM - StateEventType.POLICY_RULE_SERVER -> RustStateEventType.POLICY_RULE_SERVER - StateEventType.POLICY_RULE_USER -> RustStateEventType.POLICY_RULE_USER - StateEventType.CALL_MEMBER -> RustStateEventType.CALL_MEMBER - StateEventType.ROOM_ALIASES -> RustStateEventType.ROOM_ALIASES - StateEventType.ROOM_AVATAR -> RustStateEventType.ROOM_AVATAR - StateEventType.ROOM_CANONICAL_ALIAS -> RustStateEventType.ROOM_CANONICAL_ALIAS - StateEventType.ROOM_CREATE -> RustStateEventType.ROOM_CREATE - StateEventType.ROOM_ENCRYPTION -> RustStateEventType.ROOM_ENCRYPTION - StateEventType.ROOM_GUEST_ACCESS -> RustStateEventType.ROOM_GUEST_ACCESS - StateEventType.ROOM_HISTORY_VISIBILITY -> RustStateEventType.ROOM_HISTORY_VISIBILITY - StateEventType.ROOM_JOIN_RULES -> RustStateEventType.ROOM_JOIN_RULES - StateEventType.ROOM_MEMBER_EVENT -> RustStateEventType.ROOM_MEMBER_EVENT - StateEventType.ROOM_NAME -> RustStateEventType.ROOM_NAME - StateEventType.ROOM_PINNED_EVENTS -> RustStateEventType.ROOM_PINNED_EVENTS - StateEventType.ROOM_POWER_LEVELS -> RustStateEventType.ROOM_POWER_LEVELS - StateEventType.ROOM_SERVER_ACL -> RustStateEventType.ROOM_SERVER_ACL - StateEventType.ROOM_THIRD_PARTY_INVITE -> RustStateEventType.ROOM_THIRD_PARTY_INVITE - StateEventType.ROOM_TOMBSTONE -> RustStateEventType.ROOM_TOMBSTONE - StateEventType.ROOM_TOPIC -> RustStateEventType.ROOM_TOPIC - StateEventType.SPACE_CHILD -> RustStateEventType.SPACE_CHILD - StateEventType.SPACE_PARENT -> RustStateEventType.SPACE_PARENT + StateEventType.PolicyRuleRoom -> RustStateEventType.PolicyRuleRoom + StateEventType.PolicyRuleServer -> RustStateEventType.PolicyRuleServer + StateEventType.PolicyRuleUser -> RustStateEventType.PolicyRuleUser + StateEventType.CallMember -> RustStateEventType.CallMember + StateEventType.RoomAliases -> RustStateEventType.RoomAliases + StateEventType.RoomAvatar -> RustStateEventType.RoomAvatar + StateEventType.RoomCanonicalAlias -> RustStateEventType.RoomCanonicalAlias + StateEventType.RoomCreate -> RustStateEventType.RoomCreate + StateEventType.RoomEncryption -> RustStateEventType.RoomEncryption + StateEventType.RoomGuestAccess -> RustStateEventType.RoomGuestAccess + StateEventType.RoomHistoryVisibility -> RustStateEventType.RoomHistoryVisibility + StateEventType.RoomJoinRules -> RustStateEventType.RoomJoinRules + StateEventType.RoomMemberEvent -> RustStateEventType.RoomMemberEvent + StateEventType.RoomName -> RustStateEventType.RoomName + StateEventType.RoomPinnedEvents -> RustStateEventType.RoomPinnedEvents + StateEventType.RoomPowerLevels -> RustStateEventType.RoomPowerLevels + StateEventType.RoomServerAcl -> RustStateEventType.RoomServerAcl + StateEventType.RoomThirdPartyInvite -> RustStateEventType.RoomThirdPartyInvite + StateEventType.RoomTombstone -> RustStateEventType.RoomTombstone + StateEventType.RoomTopic -> RustStateEventType.RoomTopic + StateEventType.SpaceChild -> RustStateEventType.SpaceChild + StateEventType.SpaceParent -> RustStateEventType.SpaceParent + StateEventType.BeaconInfo -> RustStateEventType.BeaconInfo + StateEventType.MemberHints -> RustStateEventType.MemberHints + StateEventType.RoomImagePack -> RustStateEventType.RoomImagePack + StateEventType.RoomLanguage -> RustStateEventType.RoomLanguage + is StateEventType.Custom -> RustStateEventType.Custom(type) } fun RustStateEventType.map(): StateEventType = when (this) { - RustStateEventType.POLICY_RULE_ROOM -> StateEventType.POLICY_RULE_ROOM - RustStateEventType.POLICY_RULE_SERVER -> StateEventType.POLICY_RULE_SERVER - RustStateEventType.POLICY_RULE_USER -> StateEventType.POLICY_RULE_USER - RustStateEventType.CALL_MEMBER -> StateEventType.CALL_MEMBER - RustStateEventType.ROOM_ALIASES -> StateEventType.ROOM_ALIASES - RustStateEventType.ROOM_AVATAR -> StateEventType.ROOM_AVATAR - RustStateEventType.ROOM_CANONICAL_ALIAS -> StateEventType.ROOM_CANONICAL_ALIAS - RustStateEventType.ROOM_CREATE -> StateEventType.ROOM_CREATE - RustStateEventType.ROOM_ENCRYPTION -> StateEventType.ROOM_ENCRYPTION - RustStateEventType.ROOM_GUEST_ACCESS -> StateEventType.ROOM_GUEST_ACCESS - RustStateEventType.ROOM_HISTORY_VISIBILITY -> StateEventType.ROOM_HISTORY_VISIBILITY - RustStateEventType.ROOM_JOIN_RULES -> StateEventType.ROOM_JOIN_RULES - RustStateEventType.ROOM_MEMBER_EVENT -> StateEventType.ROOM_MEMBER_EVENT - RustStateEventType.ROOM_NAME -> StateEventType.ROOM_NAME - RustStateEventType.ROOM_PINNED_EVENTS -> StateEventType.ROOM_PINNED_EVENTS - RustStateEventType.ROOM_POWER_LEVELS -> StateEventType.ROOM_POWER_LEVELS - RustStateEventType.ROOM_SERVER_ACL -> StateEventType.ROOM_SERVER_ACL - RustStateEventType.ROOM_THIRD_PARTY_INVITE -> StateEventType.ROOM_THIRD_PARTY_INVITE - RustStateEventType.ROOM_TOMBSTONE -> StateEventType.ROOM_TOMBSTONE - RustStateEventType.ROOM_TOPIC -> StateEventType.ROOM_TOPIC - RustStateEventType.SPACE_CHILD -> StateEventType.SPACE_CHILD - RustStateEventType.SPACE_PARENT -> StateEventType.SPACE_PARENT + RustStateEventType.PolicyRuleRoom -> StateEventType.PolicyRuleRoom + RustStateEventType.PolicyRuleServer -> StateEventType.PolicyRuleServer + RustStateEventType.PolicyRuleUser -> StateEventType.PolicyRuleUser + RustStateEventType.CallMember -> StateEventType.CallMember + RustStateEventType.RoomAliases -> StateEventType.RoomAliases + RustStateEventType.RoomAvatar -> StateEventType.RoomAvatar + RustStateEventType.RoomCanonicalAlias -> StateEventType.RoomCanonicalAlias + RustStateEventType.RoomCreate -> StateEventType.RoomCreate + RustStateEventType.RoomEncryption -> StateEventType.RoomEncryption + RustStateEventType.RoomGuestAccess -> StateEventType.RoomGuestAccess + RustStateEventType.RoomHistoryVisibility -> StateEventType.RoomHistoryVisibility + RustStateEventType.RoomJoinRules -> StateEventType.RoomJoinRules + RustStateEventType.RoomMemberEvent -> StateEventType.RoomMemberEvent + RustStateEventType.RoomName -> StateEventType.RoomName + RustStateEventType.RoomPinnedEvents -> StateEventType.RoomPinnedEvents + RustStateEventType.RoomPowerLevels -> StateEventType.RoomPowerLevels + RustStateEventType.RoomServerAcl -> StateEventType.RoomServerAcl + RustStateEventType.RoomThirdPartyInvite -> StateEventType.RoomThirdPartyInvite + RustStateEventType.RoomTombstone -> StateEventType.RoomTombstone + RustStateEventType.RoomTopic -> StateEventType.RoomTopic + RustStateEventType.SpaceChild -> StateEventType.SpaceChild + RustStateEventType.SpaceParent -> StateEventType.SpaceParent + RustStateEventType.BeaconInfo -> StateEventType.BeaconInfo + RustStateEventType.MemberHints -> StateEventType.MemberHints + RustStateEventType.RoomImagePack -> StateEventType.RoomImagePack + RustStateEventType.RoomLanguage -> StateEventType.RoomLanguage + is RustStateEventType.Custom -> StateEventType.Custom(value) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/TimelineEventTypeFilterFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/TimelineEventTypeFilterFactory.kt index 29f72848b8..d25f595569 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/TimelineEventTypeFilterFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/TimelineEventTypeFilterFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.matrix.impl.room import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.matrix.api.room.StateEventType import org.matrix.rustcomponents.sdk.FilterTimelineEventType import org.matrix.rustcomponents.sdk.TimelineEventTypeFilter @@ -19,7 +19,6 @@ interface TimelineEventTypeFilterFactory { } @ContributesBinding(AppScope::class) -@Inject class RustTimelineEventTypeFilterFactory : TimelineEventTypeFilterFactory { override fun create(listStateEventType: List): TimelineEventTypeFilter { return TimelineEventTypeFilter.exclude( diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/alias/DefaultRoomAliasHelper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/alias/DefaultRoomAliasHelper.kt index 039144bf55..3db3c12f41 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/alias/DefaultRoomAliasHelper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/alias/DefaultRoomAliasHelper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,12 +10,10 @@ package io.element.android.libraries.matrix.impl.room.alias import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper @ContributesBinding(AppScope::class) -@Inject class DefaultRoomAliasHelper : RoomAliasHelper { override fun roomAliasNameFromRoomDisplayName(name: String): String { return org.matrix.rustcomponents.sdk.roomAliasNameFromRoomDisplayName(name) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/draft/ComposerDraftMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/draft/ComposerDraftMapper.kt index 1bdadb96fc..5e69b667cb 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/draft/ComposerDraftMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/draft/ComposerDraftMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,7 +18,9 @@ internal fun ComposerDraft.into(): RustComposerDraft { return RustComposerDraft( plainText = plainText, htmlText = htmlText, - draftType = draftType.into() + draftType = draftType.into(), + // TODO add media attachments to the draft + attachments = emptyList(), ) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/history/RoomHistoryVisibilityMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/history/RoomHistoryVisibilityMapper.kt index 60ff7cc698..30c8d7fb02 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/history/RoomHistoryVisibilityMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/history/RoomHistoryVisibilityMapper.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/AllowRule.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/AllowRule.kt index a93ce58236..2de5197a4f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/AllowRule.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/AllowRule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoom.kt index 5a790acd7a..66be1492a3 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoom.kt @@ -1,14 +1,14 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.room.join import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import im.vector.app.features.analytics.plan.JoinedRoom import io.element.android.libraries.core.extensions.mapFailure import io.element.android.libraries.di.SessionScope @@ -21,7 +21,6 @@ import io.element.android.libraries.matrix.impl.analytics.toAnalyticsJoinedRoom import io.element.android.services.analytics.api.AnalyticsService @ContributesBinding(SessionScope::class) -@Inject class DefaultJoinRoom( private val client: MatrixClient, private val analyticsService: AnalyticsService, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/JoinRule.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/JoinRule.kt index e145cbdb91..aac57451db 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/JoinRule.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/join/JoinRule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt index 02e405969b..9e27b7bc50 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/location/AssetType.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/location/AssetType.kt index 5a73223222..c7c2c88fcc 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/location/AssetType.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/location/AssetType.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberListFetcher.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberListFetcher.kt index fc394e4ce7..cfe36a23a5 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberListFetcher.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberListFetcher.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapper.kt index af7376e445..447fa427a6 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/message/RoomMessageFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/message/RoomMessageFactory.kt deleted file mode 100644 index 311b1ab5fb..0000000000 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/message/RoomMessageFactory.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.matrix.impl.room.message - -import io.element.android.libraries.matrix.api.room.message.RoomMessage -import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper -import org.matrix.rustcomponents.sdk.EventTimelineItem as RustEventTimelineItem - -class RoomMessageFactory( - private val eventTimelineItemMapper: EventTimelineItemMapper = EventTimelineItemMapper(), -) { - fun create(eventTimelineItem: RustEventTimelineItem?): RoomMessage? { - eventTimelineItem ?: return null - val mappedTimelineItem = eventTimelineItemMapper.map(eventTimelineItem) - return RoomMessage( - eventId = mappedTimelineItem.eventId ?: return null, - event = mappedTimelineItem, - sender = mappedTimelineItem.sender, - originServerTs = mappedTimelineItem.timestamp, - ) - } -} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/powerlevels/RoomPowerLevelsValuesMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/powerlevels/RoomPowerLevelsValuesMapper.kt index 9121d8bdf0..5e2a1c82da 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/powerlevels/RoomPowerLevelsValuesMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/powerlevels/RoomPowerLevelsValuesMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,11 +19,13 @@ object RoomPowerLevelsValuesMapper { ban = values.ban, invite = values.invite, kick = values.kick, - sendEvents = values.eventsDefault, + eventsDefault = values.eventsDefault, + stateDefault = values.stateDefault, redactEvents = values.redact, roomName = values.roomName, roomAvatar = values.roomAvatar, roomTopic = values.roomTopic, + spaceChild = values.spaceChild, ) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/powerlevels/RustRoomPermissions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/powerlevels/RustRoomPermissions.kt new file mode 100644 index 0000000000..57e20b166f --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/powerlevels/RustRoomPermissions.kt @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.room.powerlevels + +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.room.MessageEventType +import io.element.android.libraries.matrix.api.room.StateEventType +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions +import io.element.android.libraries.matrix.impl.room.map +import org.matrix.rustcomponents.sdk.RoomPowerLevels + +class RustRoomPermissions( + private val inner: RoomPowerLevels, +) : RoomPermissions { + override fun canOwnUserBan(): Boolean { + return inner.canOwnUserBan() + } + + override fun canOwnUserInvite(): Boolean { + return inner.canOwnUserInvite() + } + + override fun canOwnUserKick(): Boolean { + return inner.canOwnUserKick() + } + + override fun canOwnUserPinUnpin(): Boolean { + return inner.canOwnUserPinUnpin() + } + + override fun canOwnUserRedactOther(): Boolean { + return inner.canOwnUserRedactOther() + } + + override fun canOwnUserRedactOwn(): Boolean { + return inner.canOwnUserRedactOwn() + } + + override fun canOwnUserSendMessage(message: MessageEventType): Boolean { + return inner.canOwnUserSendMessage(message.map()) + } + + override fun canOwnUserSendState(stateEvent: StateEventType): Boolean { + return inner.canOwnUserSendState(stateEvent.map()) + } + + override fun canOwnUserTriggerRoomNotification(): Boolean { + return inner.canOwnUserTriggerRoomNotification() + } + + override fun canUserBan(userId: UserId): Boolean { + return inner.canUserBan(userId.value) + } + + override fun canUserInvite(userId: UserId): Boolean { + return inner.canUserInvite(userId.value) + } + + override fun canUserKick(userId: UserId): Boolean { + return inner.canUserKick(userId.value) + } + + override fun canUserPinUnpin(userId: UserId): Boolean { + return inner.canUserPinUnpin(userId.value) + } + + override fun canUserRedactOther(userId: UserId): Boolean { + return inner.canUserRedactOther(userId.value) + } + + override fun canUserRedactOwn(userId: UserId): Boolean { + return inner.canUserRedactOwn(userId.value) + } + + override fun canUserSendMessage(userId: UserId, message: MessageEventType): Boolean { + return inner.canUserSendMessage(userId.value, message.map()) + } + + override fun canUserSendState(userId: UserId, stateEvent: StateEventType): Boolean { + return inner.canUserSendState(userId.value, stateEvent.map()) + } + + override fun canUserTriggerRoomNotification(userId: UserId): Boolean { + return inner.canUserTriggerRoomNotification(userId.value) + } + + override fun close() { + inner.close() + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/preview/RoomPreviewInfoMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/preview/RoomPreviewInfoMapper.kt index cb1867ec8e..5e2bdd265a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/preview/RoomPreviewInfoMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/preview/RoomPreviewInfoMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/tombstone/PredecessorRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/tombstone/PredecessorRoom.kt index a09867bf6c..c9d493f993 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/tombstone/PredecessorRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/tombstone/PredecessorRoom.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/tombstone/SuccessorRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/tombstone/SuccessorRoom.kt index afc0ee96af..f806e3f1d7 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/tombstone/SuccessorRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/tombstone/SuccessorRoom.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt index 76a6394e94..dd17a2432c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchExtension.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchExtension.kt index 73e5e6f1fa..c560e3dc32 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchExtension.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchExtension.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt index abd38834dd..7c4f1e207a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomVisibilityMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomVisibilityMapper.kt index bfbc48d3b9..a66d0a30dd 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomVisibilityMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomVisibilityMapper.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt index e91fef7810..699291f68e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -73,9 +74,9 @@ class RustRoomDirectoryList( return !inner.isAtLastPage() } - override val state: Flow = + override val state: Flow = combine(hasMoreToLoad, processor.roomDescriptionsFlow) { hasMoreToLoad, items -> - RoomDirectoryList.State( + RoomDirectoryList.SearchResult( hasMoreToLoad = hasMoreToLoad, items = items ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt index 35ad336f9a..216991fdd1 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListDynamicEvents.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListDynamicEvents.kt index 9d7cdf5f3b..48d97cc684 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListDynamicEvents.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListDynamicEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListEntriesUpdateExt.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListEntriesUpdateExt.kt index 99d1646c72..27b19ebf19 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListEntriesUpdateExt.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListEntriesUpdateExt.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt index 8cdcd13f22..ae241e9c02 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -47,7 +48,7 @@ fun RoomListInterface.loadingStateFlow(): Flow = try { send(result.state) } catch (exception: Exception) { - Timber.d("loadingStateFlow() initialState failed.") + Timber.d(exception, "loadingStateFlow() initialState failed.") } result.stateStream }.catch { @@ -65,7 +66,11 @@ internal fun RoomListInterface.entriesFlow( trySendBlocking(roomEntriesUpdate) } } - val result = entriesWithDynamicAdapters(pageSize.toUInt(), listener) + val result = entriesWithDynamicAdaptersWith( + pageSize = pageSize.toUInt(), + enableLatestEventSorter = true, + listener = listener, + ) val controller = result.controller() controller.setFilter(initialFilterKind) roomListDynamicEvents.onEach { controllerEvents -> diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactory.kt index 53b8127ab8..b15411c382 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,6 +12,9 @@ import io.element.android.libraries.matrix.api.roomlist.DynamicRoomList import io.element.android.libraries.matrix.api.roomlist.RoomList import io.element.android.libraries.matrix.api.roomlist.RoomListFilter import io.element.android.libraries.matrix.api.roomlist.RoomSummary +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.api.finishLongRunningTransaction import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -35,8 +39,9 @@ private val ROOM_LIST_RUST_FILTERS = listOf( internal class RoomListFactory( private val innerRoomListService: RoomListService, private val sessionCoroutineScope: CoroutineScope, + private val analyticsService: AnalyticsService, ) { - private val roomSummaryDetailsFactory: RoomSummaryFactory = RoomSummaryFactory() + private val roomSummaryFactory: RoomSummaryFactory = RoomSummaryFactory() /** * Creates a room list that can be used to load more rooms and filter them dynamically. @@ -51,13 +56,15 @@ internal class RoomListFactory( val loadingStateFlow: MutableStateFlow = MutableStateFlow(RoomList.LoadingState.NotLoaded) val filteredSummariesFlow = MutableSharedFlow>(replay = 1, extraBufferCapacity = 1) val summariesFlow = MutableSharedFlow>(replay = 1, extraBufferCapacity = 1) - val processor = RoomSummaryListProcessor(summariesFlow, innerRoomListService, coroutineContext, roomSummaryDetailsFactory) + val processor = RoomSummaryListProcessor(summariesFlow, innerRoomListService, coroutineContext, roomSummaryFactory) // Makes sure we don't miss any events val dynamicEvents = MutableSharedFlow(replay = 100) val currentFilter = MutableStateFlow(initialFilter) val loadedPages = MutableStateFlow(1) var innerRoomList: InnerRoomList? = null + val firstRoomsTransaction = analyticsService.startTransaction("Load first set of rooms", "innerRoomList.entriesFlow") + coroutineScope.launch(coroutineContext) { innerRoomList = innerProvider() innerRoomList.let { innerRoomList -> @@ -66,6 +73,10 @@ internal class RoomListFactory( roomListDynamicEvents = dynamicEvents, initialFilterKind = RoomListEntriesDynamicFilterKind.All(ROOM_LIST_RUST_FILTERS), ).onEach { update -> + if (!firstRoomsTransaction.isFinished()) { + analyticsService.finishLongRunningTransaction(AnalyticsLongRunningTransaction.FirstRoomsDisplayed) + firstRoomsTransaction.finish() + } processor.postUpdate(update) }.launchIn(this) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilter.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilter.kt index 3fc59d7c44..ed4d5735e0 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilter.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryFactory.kt index 69f48a578e..738c1f72ea 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryFactory.kt @@ -1,30 +1,63 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.roomlist +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.roomlist.LatestEventValue import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.impl.room.RoomInfoMapper -import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory +import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper +import io.element.android.libraries.matrix.impl.timeline.item.event.map import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.use +import uniffi.matrix_sdk_ui.LatestEventValueLocalState +import org.matrix.rustcomponents.sdk.LatestEventValue as RustLatestEventValue class RoomSummaryFactory( - private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory(), + private val contentMapper: TimelineEventContentMapper = TimelineEventContentMapper(), private val roomInfoMapper: RoomInfoMapper = RoomInfoMapper(), ) { suspend fun create(room: Room): RoomSummary { val roomInfo = room.roomInfo().let(roomInfoMapper::map) - val latestRoomMessage = room.latestEvent().use { event -> - roomMessageFactory.create(event) + val latestEvent = room.latestEvent().use { event -> + when (event) { + is RustLatestEventValue.None -> LatestEventValue.None + is RustLatestEventValue.Local -> when (event.state) { + LatestEventValueLocalState.IS_SENDING, + LatestEventValueLocalState.CANNOT_BE_SENT -> LatestEventValue.Local( + timestamp = event.timestamp.toLong(), + content = contentMapper.map(event.content), + isSending = event.state == LatestEventValueLocalState.IS_SENDING, + senderId = UserId(event.sender), + senderProfile = event.profile.map(), + ) + // This is the same as a remote event, we just haven't received the local -> remote update yet + LatestEventValueLocalState.HAS_BEEN_SENT -> LatestEventValue.Remote( + timestamp = event.timestamp.toLong(), + content = contentMapper.map(event.content), + senderId = UserId(event.sender), + senderProfile = event.profile.map(), + isOwn = true, + ) + } + is RustLatestEventValue.Remote -> LatestEventValue.Remote( + timestamp = event.timestamp.toLong(), + content = contentMapper.map(event.content), + senderId = UserId(event.sender), + senderProfile = event.profile.map(), + isOwn = event.isOwn, + ) + } } return RoomSummary( info = roomInfo, - lastMessage = latestRoomMessage, + latestEvent = latestEvent, ) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt index e26cea1c94..3f5a919139 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -23,7 +24,7 @@ class RoomSummaryListProcessor( private val roomSummaries: MutableSharedFlow>, private val roomListService: RoomListServiceInterface, private val coroutineContext: CoroutineContext, - private val roomSummaryDetailsFactory: RoomSummaryFactory = RoomSummaryFactory(), + private val roomSummaryFactory: RoomSummaryFactory, ) { private val mutex = Mutex() @@ -102,7 +103,7 @@ class RoomSummaryListProcessor( } private suspend fun buildSummaryForRoomListEntry(entry: Room): RoomSummary { - return entry.use { roomSummaryDetailsFactory.create(room = it) } + return entry.use { roomSummaryFactory.create(room = it) } } private suspend fun buildRoomSummaryForIdentifier(identifier: String): RoomSummary? { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt index de075fc1b7..9c462ddaf5 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustRoomListService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/server/DefaultUserServerResolver.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/server/DefaultUserServerResolver.kt index ff62708f65..da21c03322 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/server/DefaultUserServerResolver.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/server/DefaultUserServerResolver.kt @@ -1,20 +1,19 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.server import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.server.UserServerResolver @ContributesBinding(SessionScope::class) -@Inject class DefaultUserServerResolver( private val matrixClient: MatrixClient, ) : UserServerResolver { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustLeaveSpaceHandle.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustLeaveSpaceHandle.kt index e7e629b40b..10e329e46f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustLeaveSpaceHandle.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustLeaveSpaceHandle.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceRoomList.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceRoomList.kt index b94c3ffd1b..c6fe86700e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceRoomList.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceRoomList.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceService.kt index 88f738f0c4..ba816c11c7 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -50,7 +51,7 @@ class RustSpaceService( override suspend fun joinedSpaces(): Result> = withContext(sessionDispatcher) { runCatchingExceptions { - innerSpaceService.joinedSpaces() + innerSpaceService.topLevelJoinedSpaces() .map { it.let(spaceRoomMapper::map) } @@ -96,7 +97,7 @@ internal fun SpaceServiceInterface.spaceListUpdate(): Flow } } Timber.d("Open spaceDiffFlow for SpaceServiceInterface ${this@spaceListUpdate}") - val taskHandle = subscribeToJoinedSpaces(listener) + val taskHandle = subscribeToTopLevelJoinedSpaces(listener) awaitClose { Timber.d("Close spaceDiffFlow for SpaceServiceInterface ${this@spaceListUpdate}") taskHandle.cancelAndDestroy() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/SpaceListUpdateProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/SpaceListUpdateProcessor.kt index 40310561e2..41f2cc1266 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/SpaceListUpdateProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/SpaceListUpdateProcessor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/SpaceRoomListExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/SpaceRoomListExtensions.kt index c7107f745f..62b65a4886 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/SpaceRoomListExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/SpaceRoomListExtensions.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/SpaceRoomMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/SpaceRoomMapper.kt index 3a9b78c3f9..f83cd648a6 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/SpaceRoomMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/spaces/SpaceRoomMapper.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/storage/SqliteStoreBuilder.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/storage/SqliteStoreBuilder.kt new file mode 100644 index 0000000000..7b5d9fb31c --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/storage/SqliteStoreBuilder.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.storage + +import io.element.android.libraries.matrix.impl.paths.SessionPaths +import org.matrix.rustcomponents.sdk.ClientBuilder +import org.matrix.rustcomponents.sdk.SqliteStoreBuilder as SdkSqliteStoreBuilder + +interface SqliteStoreBuilder { + fun passphrase(passphrase: String?): SqliteStoreBuilder + fun setupClientBuilder(clientBuilder: ClientBuilder): ClientBuilder +} + +class RustSqliteStoreBuilder( + private val sessionPaths: SessionPaths, +) : SqliteStoreBuilder { + private var inner = SdkSqliteStoreBuilder( + dataPath = sessionPaths.fileDirectory.absolutePath, + cachePath = sessionPaths.cacheDirectory.absolutePath, + ) + + override fun passphrase(passphrase: String?): SqliteStoreBuilder { + inner = inner.passphrase(passphrase) + return this + } + + override fun setupClientBuilder(clientBuilder: ClientBuilder): ClientBuilder { + return clientBuilder.sqliteStore(this.inner) + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/storage/SqliteStoreBuilderProvider.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/storage/SqliteStoreBuilderProvider.kt new file mode 100644 index 0000000000..29a30f3c56 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/storage/SqliteStoreBuilderProvider.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.storage + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.matrix.impl.paths.SessionPaths + +interface SqliteStoreBuilderProvider { + fun provide(sessionPaths: SessionPaths): SqliteStoreBuilder +} + +@ContributesBinding(AppScope::class) +class RustSqliteStoreBuilderProvider : SqliteStoreBuilderProvider { + override fun provide(sessionPaths: SessionPaths): SqliteStoreBuilder { + return RustSqliteStoreBuilder(sessionPaths) + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/AppStateMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/AppStateMapper.kt index 5620b286a6..53ffb62bb8 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/AppStateMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/AppStateMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt index 24baa79dfe..f7732cd9b7 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -29,7 +30,7 @@ import org.matrix.rustcomponents.sdk.SyncService as InnerSyncService class RustSyncService( private val inner: InnerSyncService, private val dispatcher: CoroutineDispatcher, - sessionCoroutineScope: CoroutineScope + sessionCoroutineScope: CoroutineScope, ) : SyncService { private val isServiceReady = AtomicBoolean(true) @@ -70,10 +71,10 @@ class RustSyncService( override val syncState: StateFlow = inner.stateFlow() .map(SyncServiceState::toSyncState) + .distinctUntilChanged() .onEach { state -> Timber.i("Sync state=$state") } - .distinctUntilChanged() .stateIn(sessionCoroutineScope, SharingStarted.Eagerly, SyncState.Idle) override val isOnline: StateFlow = syncState.mapState { it != SyncState.Offline } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SlidingSyncVersion.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SlidingSyncVersion.kt index 6284e0028f..8a1ed28095 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SlidingSyncVersion.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SlidingSyncVersion.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SyncServiceExtension.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SyncServiceExtension.kt index 8a130e7f02..34332576de 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SyncServiceExtension.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/SyncServiceExtension.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/EventOrTransactionId.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/EventOrTransactionId.kt index af51c0893b..67528061b0 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/EventOrTransactionId.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/EventOrTransactionId.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt index eb27f2d667..c522016ee1 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessor.kt @@ -1,15 +1,17 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.timeline +import androidx.compose.ui.util.fastForEach import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent -import kotlinx.coroutines.flow.Flow +import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.sync.Mutex @@ -20,58 +22,60 @@ import timber.log.Timber internal class MatrixTimelineDiffProcessor( private val timelineItems: MutableSharedFlow>, - private val timelineItemFactory: MatrixTimelineItemMapper, + private val membershipChangeEventReceivedFlow: MutableSharedFlow, + private val syncedEventReceivedFlow: MutableSharedFlow, + private val timelineItemMapper: MatrixTimelineItemMapper, ) { private val mutex = Mutex() - private val _membershipChangeEventReceived = MutableSharedFlow(extraBufferCapacity = 1) - val membershipChangeEventReceived: Flow = _membershipChangeEventReceived - suspend fun postDiffs(diffs: List) { - updateTimelineItems { + mutex.withLock { Timber.v("Update timeline items from postDiffs (with ${diffs.size} items) on ${Thread.currentThread()}") - diffs.forEach { diff -> - applyDiff(diff) + val result = processDiffs(diffs) + timelineItems.emit(result.items()) + if (result.hasNewEventsFromSync) { + syncedEventReceivedFlow.emit(Unit) + } + if (result.hasMembershipChangeEventFromSync) { + membershipChangeEventReceivedFlow.emit(Unit) } } } - private suspend fun updateTimelineItems(block: MutableList.() -> Unit) = - mutex.withLock { - val mutableTimelineItems = if (timelineItems.replayCache.isNotEmpty()) { - timelineItems.first().toMutableList() - } else { - mutableListOf() - } - block(mutableTimelineItems) - timelineItems.tryEmit(mutableTimelineItems) + private suspend fun processDiffs(diffs: List): DiffingResult { + val timelineItems = if (timelineItems.replayCache.isNotEmpty()) { + timelineItems.first() + } else { + emptyList() } + val result = DiffingResult(timelineItems) + diffs.forEach { diff -> + result.applyDiff(diff) + } + return result + } - private fun MutableList.applyDiff(diff: TimelineDiff) { + private fun DiffingResult.applyDiff(diff: TimelineDiff) { when (diff) { is TimelineDiff.Append -> { - val items = diff.values.map { it.asMatrixTimelineItem() } - addAll(items) + diff.values.fastForEach { item -> + add(item.map()) + } } is TimelineDiff.PushBack -> { - val item = diff.value.asMatrixTimelineItem() - if (item is MatrixTimelineItem.Event && item.event.content is RoomMembershipContent) { - // TODO - This is a temporary solution to notify the room screen about membership changes - // Ideally, this should be implemented by the Rust SDK - _membershipChangeEventReceived.tryEmit(Unit) - } + val item = diff.value.map() add(item) } is TimelineDiff.PushFront -> { - val item = diff.value.asMatrixTimelineItem() + val item = diff.value.map() add(0, item) } is TimelineDiff.Set -> { - val item = diff.value.asMatrixTimelineItem() + val item = diff.value.map() set(diff.index.toInt(), item) } is TimelineDiff.Insert -> { - val item = diff.value.asMatrixTimelineItem() + val item = diff.value.map() add(diff.index.toInt(), item) } is TimelineDiff.Remove -> { @@ -79,25 +83,91 @@ internal class MatrixTimelineDiffProcessor( } is TimelineDiff.Reset -> { clear() - val items = diff.values.map { it.asMatrixTimelineItem() } - addAll(items) + diff.values.fastForEach { item -> + add(item.map()) + } } TimelineDiff.PopFront -> { - removeFirstOrNull() + removeFirst() } TimelineDiff.PopBack -> { - removeLastOrNull() + removeLast() } TimelineDiff.Clear -> { clear() } is TimelineDiff.Truncate -> { - subList(diff.length.toInt(), size).clear() + truncate(diff.length.toInt()) } } } - private fun TimelineItem.asMatrixTimelineItem(): MatrixTimelineItem { - return timelineItemFactory.map(this) + private fun TimelineItem.map(): MatrixTimelineItem { + return timelineItemMapper.map(this) + } +} + +private class DiffingResult(initialItems: List) { + private val items = initialItems.toMutableList() + var hasNewEventsFromSync: Boolean = false + private set + var hasMembershipChangeEventFromSync: Boolean = false + private set + + fun items(): List = items + + fun add(item: MatrixTimelineItem) { + processItem(item) + items.add(item) + } + + fun add(index: Int, item: MatrixTimelineItem) { + processItem(item) + items.add(index, item) + } + + fun set(index: Int, item: MatrixTimelineItem) { + processItem(item) + items[index] = item + } + + fun removeAt(index: Int) { + items.removeAt(index) + } + + fun removeFirst() { + items.removeFirstOrNull() + } + + fun removeLast() { + items.removeLastOrNull() + } + + fun truncate(length: Int) { + items.subList(length, items.size).clear() + } + + fun clear() { + items.clear() + } + + private fun processItem(item: MatrixTimelineItem) { + if (skipProcessing()) return + when (item) { + is MatrixTimelineItem.Event -> { + if (item.event.origin == TimelineItemEventOrigin.SYNC) { + hasNewEventsFromSync = true + when (item.event.content) { + is RoomMembershipContent -> hasMembershipChangeEventFromSync = true + else -> Unit + } + } + } + else -> Unit + } + } + + private fun skipProcessing(): Boolean { + return hasNewEventsFromSync && hasMembershipChangeEventFromSync } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt index 310d68d4b9..affaf242b9 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/ReceiptTypeMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/ReceiptTypeMapper.kt index 51998d2d51..36591c1478 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/ReceiptTypeMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/ReceiptTypeMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RoomTimelineExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RoomTimelineExtensions.kt index 49fe7d90cc..bc0e5eed99 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RoomTimelineExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RoomTimelineExtensions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt index 4c52e78205..391349b52d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -29,7 +30,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransa import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo import io.element.android.libraries.matrix.impl.media.MediaUploadHandlerImpl import io.element.android.libraries.matrix.impl.media.map -import io.element.android.libraries.matrix.impl.media.toMSC3246range import io.element.android.libraries.matrix.impl.poll.toInner import io.element.android.libraries.matrix.impl.room.RoomContentForwarder import io.element.android.libraries.matrix.impl.room.location.toInner @@ -81,16 +81,18 @@ private const val PAGINATION_SIZE = 50 class RustTimeline( private val inner: InnerTimeline, override val mode: Timeline.Mode, - systemClock: SystemClock, + private val systemClock: SystemClock, private val joinedRoom: JoinedRoom, private val coroutineScope: CoroutineScope, private val dispatcher: CoroutineDispatcher, private val roomContentForwarder: RoomContentForwarder, - onNewSyncedEvent: () -> Unit, ) : Timeline { private val _timelineItems: MutableSharedFlow> = MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE) + private val _membershipChangeEventReceived = MutableSharedFlow(extraBufferCapacity = 1) + private val _onSyncedEventReceived: MutableSharedFlow = MutableSharedFlow(extraBufferCapacity = 1) + private val timelineEventContentMapper = TimelineEventContentMapper() private val inReplyToMapper = InReplyToMapper(timelineEventContentMapper) private val timelineItemMapper = MatrixTimelineItemMapper( @@ -99,18 +101,19 @@ class RustTimeline( virtualTimelineItemMapper = VirtualTimelineItemMapper(), eventTimelineItemMapper = EventTimelineItemMapper( contentMapper = timelineEventContentMapper - ) + ), ) private val timelineDiffProcessor = MatrixTimelineDiffProcessor( timelineItems = _timelineItems, - timelineItemFactory = timelineItemMapper, + membershipChangeEventReceivedFlow = _membershipChangeEventReceived, + syncedEventReceivedFlow = _onSyncedEventReceived, + timelineItemMapper = timelineItemMapper, ) private val timelineItemsSubscriber = TimelineItemsSubscriber( timeline = inner, timelineCoroutineScope = coroutineScope, timelineDiffProcessor = timelineDiffProcessor, dispatcher = dispatcher, - onNewSyncedEvent = onNewSyncedEvent, ) private val roomBeginningPostProcessor = RoomBeginningPostProcessor(mode) @@ -152,7 +155,13 @@ class RustTimeline( .launchIn(this) } - override val membershipChangeEventReceived: Flow = timelineDiffProcessor.membershipChangeEventReceived + override val membershipChangeEventReceived: Flow = _membershipChangeEventReceived + .onStart { timelineItemsSubscriber.subscribeIfNeeded() } + .onCompletion { timelineItemsSubscriber.unsubscribeIfNeeded() } + + override val onSyncedEventReceived: Flow = _onSyncedEventReceived + .onStart { timelineItemsSubscriber.subscribeIfNeeded() } + .onCompletion { timelineItemsSubscriber.unsubscribeIfNeeded() } override suspend fun sendReadReceipt(eventId: EventId, receiptType: ReceiptType): Result = withContext(dispatcher) { runCatchingExceptions { @@ -160,6 +169,12 @@ class RustTimeline( } } + override suspend fun markAsRead(receiptType: ReceiptType): Result = withContext(dispatcher) { + runCatchingExceptions { + inner.markAsRead(receiptType.toRustReceiptType()) + } + } + private fun updatePaginationStatus(direction: Timeline.PaginationDirection, update: (Timeline.PaginationStatus) -> Timeline.PaginationStatus) { when (direction) { Timeline.PaginationDirection.BACKWARDS -> backwardPaginationStatus.getAndUpdate(update) @@ -202,10 +217,12 @@ class RustTimeline( backwardPaginationStatus, forwardPaginationStatus, joinedRoom.roomInfoFlow.map { it.creators to it.isDm }.distinctUntilChanged(), - ) { timelineItems, + ) { + timelineItems, backwardPaginationStatus, forwardPaginationStatus, - (roomCreators, isDm) -> + (roomCreators, isDm), + -> withContext(dispatcher) { timelineItems .let { items -> @@ -484,7 +501,7 @@ class RustTimeline( inReplyTo = inReplyToEventId?.value, ), audioInfo = audioInfo.map(), - waveform = waveform.toMSC3246range(), + waveform = waveform, ) } } @@ -587,6 +604,12 @@ class RustTimeline( } } + override suspend fun getLatestEventId(): Result = withContext(dispatcher) { + runCatchingExceptions { + inner.latestEventId()?.let(::EventId) + } + } + private suspend fun fetchDetailsForEvent(eventId: EventId): Result = withContext(dispatcher) { runCatchingExceptions { inner.fetchDetailsForEvent(eventId.value) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineDiffExt.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineDiffExt.kt deleted file mode 100644 index 2da28399f6..0000000000 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineDiffExt.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.matrix.impl.timeline - -import org.matrix.rustcomponents.sdk.TimelineDiff -import org.matrix.rustcomponents.sdk.TimelineItem -import uniffi.matrix_sdk_ui.EventItemOrigin - -/** - * Tries to get an event origin from the TimelineDiff. - * If there is multiple events in the diff, uses the first one as it should be a good indicator. - */ -internal fun TimelineDiff.eventOrigin(): EventItemOrigin? { - return when (this) { - is TimelineDiff.Append -> values.firstOrNull()?.eventOrigin() - is TimelineDiff.PushBack -> value.eventOrigin() - is TimelineDiff.PushFront -> value.eventOrigin() - is TimelineDiff.Set -> value.eventOrigin() - is TimelineDiff.Insert -> value.eventOrigin() - is TimelineDiff.Reset -> values.firstOrNull()?.eventOrigin() - else -> null - } -} - -private fun TimelineItem.eventOrigin(): EventItemOrigin? { - return asEvent()?.origin -} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriber.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriber.kt index 3d95d660ff..adf9102b3e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriber.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriber.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,13 +12,11 @@ import io.element.android.libraries.core.coroutine.childScope import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancelChildren -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.matrix.rustcomponents.sdk.Timeline -import uniffi.matrix_sdk_ui.EventItemOrigin /** * This class is responsible for subscribing to a timeline and post the items/diffs to the timelineDiffProcessor. @@ -28,7 +27,6 @@ internal class TimelineItemsSubscriber( dispatcher: CoroutineDispatcher, private val timeline: Timeline, private val timelineDiffProcessor: MatrixTimelineDiffProcessor, - private val onNewSyncedEvent: () -> Unit, ) { private var subscriptionCount = 0 private val mutex = Mutex() @@ -43,9 +41,6 @@ internal class TimelineItemsSubscriber( if (subscriptionCount == 0) { timeline.timelineDiffFlow() .onEach { diffs -> - if (diffs.any { diff -> diff.eventOrigin() == EventItemOrigin.SYNC }) { - onNewSyncedEvent() - } timelineDiffProcessor.postDiffs(diffs) } .launchIn(coroutineScope) 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 ed38c328f6..813bf0ec11 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventOrTransactionIdExtension.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventOrTransactionIdExtension.kt index d247bb72d0..211e2ce39d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventOrTransactionIdExtension.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventOrTransactionIdExtension.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt index d923cd420c..6715169899 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,7 +17,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield -import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails import io.element.android.libraries.matrix.api.timeline.item.event.ReactionSender import io.element.android.libraries.matrix.api.timeline.item.event.Receipt import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin @@ -63,12 +64,12 @@ class EventTimelineItemMapper( } } -fun RustProfileDetails.map(): ProfileTimelineDetails { +fun RustProfileDetails.map(): ProfileDetails { return when (this) { - RustProfileDetails.Pending -> ProfileTimelineDetails.Pending - RustProfileDetails.Unavailable -> ProfileTimelineDetails.Unavailable - is RustProfileDetails.Error -> ProfileTimelineDetails.Error(message) - is RustProfileDetails.Ready -> ProfileTimelineDetails.Ready( + RustProfileDetails.Pending -> ProfileDetails.Pending + RustProfileDetails.Unavailable -> ProfileDetails.Unavailable + is RustProfileDetails.Error -> ProfileDetails.Error(message) + is RustProfileDetails.Ready -> ProfileDetails.Ready( displayName = displayName, displayNameAmbiguous = displayNameAmbiguous, avatarUrl = avatarUrl diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt index 1b941009e0..b1758fb734 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -31,9 +32,11 @@ import io.element.android.libraries.matrix.api.timeline.item.event.UnknownConten import io.element.android.libraries.matrix.api.timeline.item.event.UtdCause import io.element.android.libraries.matrix.impl.media.map import io.element.android.libraries.matrix.impl.poll.map +import io.element.android.libraries.matrix.impl.room.join.map import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableMap import org.matrix.rustcomponents.sdk.EmbeddedEventDetails +import org.matrix.rustcomponents.sdk.MsgLikeContent import org.matrix.rustcomponents.sdk.MsgLikeKind import org.matrix.rustcomponents.sdk.TimelineItemContent import org.matrix.rustcomponents.sdk.use @@ -66,37 +69,11 @@ class TimelineEventContentMapper( when (val kind = it.content.kind) { is MsgLikeKind.Message -> { val inReplyTo = it.content.inReplyTo - val threadSummary = it.content.threadSummary?.use { summary -> - val numberOfReplies = summary.numReplies().toLong() - val latestEvent = summary.latestEvent() - val details = when (latestEvent) { - is EmbeddedEventDetails.Unavailable -> AsyncData.Uninitialized - is EmbeddedEventDetails.Pending -> AsyncData.Loading() - is EmbeddedEventDetails.Error -> AsyncData.Failure(IllegalStateException(latestEvent.message)) - is EmbeddedEventDetails.Ready -> { - AsyncData.Success( - EmbeddedEventInfo( - eventOrTransactionId = latestEvent.eventOrTransactionId.map(), - content = map(latestEvent.content), - senderId = UserId(latestEvent.sender), - senderProfile = latestEvent.senderProfile.map(), - timestamp = latestEvent.timestamp.toLong(), - ) - ) - } - } - ThreadSummary( - latestEvent = details, - numberOfReplies = numberOfReplies, - ) - } - val threadRootId = it.content.threadRoot?.let(::ThreadId) - val threadInfo = when { - threadSummary != null -> EventThreadInfo.ThreadRoot(threadSummary) - threadRootId != null -> EventThreadInfo.ThreadResponse(threadRootId) - else -> null - } - eventMessageMapper.map(kind, inReplyTo, threadInfo) + eventMessageMapper.map( + message = kind, + inReplyTo = inReplyTo, + threadInfo = extractThreadInfo(it.content) + ) } is MsgLikeKind.Redacted -> { RedactedContent @@ -112,11 +89,13 @@ class TimelineEventContentMapper( }.toImmutableMap(), endTime = kind.endTime, isEdited = kind.hasBeenEdited, + threadInfo = extractThreadInfo(it.content), ) } is MsgLikeKind.UnableToDecrypt -> { UnableToDecryptContent( - data = kind.msg.map() + data = kind.msg.map(), + threadInfo = extractThreadInfo(it.content), ) } is MsgLikeKind.Sticker -> { @@ -125,6 +104,7 @@ class TimelineEventContentMapper( body = null, info = kind.info.map(), source = kind.source.map(), + threadInfo = extractThreadInfo(it.content), ) } is MsgLikeKind.Other -> UnknownContent @@ -157,6 +137,43 @@ class TimelineEventContentMapper( } } } + + private fun extractThreadInfo(content: MsgLikeContent): EventThreadInfo? { + val threadSummary = extractThreadSummary(content.threadSummary) + val threadRootId = content.threadRoot?.let(::ThreadId) + return when { + threadSummary != null -> EventThreadInfo.ThreadRoot(threadSummary) + threadRootId != null -> EventThreadInfo.ThreadResponse(threadRootId) + else -> null + } + } + + private fun extractThreadSummary(threadSummary: org.matrix.rustcomponents.sdk.ThreadSummary?): ThreadSummary? { + return threadSummary?.use { summary -> + val numberOfReplies = summary.numReplies().toLong() + val latestEvent = summary.latestEvent() + val details = when (latestEvent) { + is EmbeddedEventDetails.Unavailable -> AsyncData.Uninitialized + is EmbeddedEventDetails.Pending -> AsyncData.Loading() + is EmbeddedEventDetails.Error -> AsyncData.Failure(IllegalStateException(latestEvent.message)) + is EmbeddedEventDetails.Ready -> { + AsyncData.Success( + EmbeddedEventInfo( + eventOrTransactionId = latestEvent.eventOrTransactionId.map(), + content = map(latestEvent.content), + senderId = UserId(latestEvent.sender), + senderProfile = latestEvent.senderProfile.map(), + timestamp = latestEvent.timestamp.toLong(), + ) + ) + } + } + ThreadSummary( + latestEvent = details, + numberOfReplies = numberOfReplies, + ) + } + } } private fun RustMembershipChange.map(): MembershipChange { @@ -209,7 +226,7 @@ private fun RustOtherState.map(): OtherState { RustOtherState.RoomEncryption -> OtherState.RoomEncryption RustOtherState.RoomGuestAccess -> OtherState.RoomGuestAccess RustOtherState.RoomHistoryVisibility -> OtherState.RoomHistoryVisibility - RustOtherState.RoomJoinRules -> OtherState.RoomJoinRules + is RustOtherState.RoomJoinRules -> OtherState.RoomJoinRules(joinRule?.map()) is RustOtherState.RoomName -> OtherState.RoomName(name) is RustOtherState.RoomPinnedEvents -> OtherState.RoomPinnedEvents(change.map()) is RustOtherState.RoomPowerLevels -> OtherState.RoomUserPowerLevels(users) @@ -219,6 +236,8 @@ private fun RustOtherState.map(): OtherState { is RustOtherState.RoomTopic -> OtherState.RoomTopic(topic) RustOtherState.SpaceChild -> OtherState.SpaceChild RustOtherState.SpaceParent -> OtherState.SpaceParent + is RustOtherState.RoomCreate -> OtherState.RoomCreate + is RustOtherState.RoomHistoryVisibility -> OtherState.RoomHistoryVisibility } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt index d60d88f168..770de4f5be 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/virtual/VirtualTimelineItemMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/LastForwardIndicatorsPostProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/LastForwardIndicatorsPostProcessor.kt index c3e8237b72..68be5cbfa0 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/LastForwardIndicatorsPostProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/LastForwardIndicatorsPostProcessor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/LoadingIndicatorsPostProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/LoadingIndicatorsPostProcessor.kt index 6d42af54b5..6f20cfe465 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/LoadingIndicatorsPostProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/LoadingIndicatorsPostProcessor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessor.kt index 639f1b879f..397280231d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TypingNotificationPostProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TypingNotificationPostProcessor.kt index 4c888fa7f2..d56d53632a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TypingNotificationPostProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TypingNotificationPostProcessor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/reply/InReplyToMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/reply/InReplyToMapper.kt index 1b329bfe0a..93e12d07e2 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/reply/InReplyToMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/reply/InReplyToMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/LogEventLocation.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/LogEventLocation.kt index cf4f951a8c..c84e042b47 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/LogEventLocation.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/LogEventLocation.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt index 9df7f459ac..6f235cd0db 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.matrix.impl.tracing import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.api.tracing.LogLevel import io.element.android.libraries.matrix.api.tracing.TracingConfiguration @@ -20,7 +20,6 @@ import org.matrix.rustcomponents.sdk.reloadTracingFileWriter import timber.log.Timber @ContributesBinding(AppScope::class) -@Inject class RustTracingService(private val buildMeta: BuildMeta) : TracingService { override fun createTimberTree(target: String): Timber.Tree { return RustTracingTree(target = target, retrieveFromStackTrace = buildMeta.isDebuggable) @@ -61,5 +60,5 @@ fun TracingConfiguration.map(): org.matrix.rustcomponents.sdk.TracingConfigurati extraTargets = extraTargets, traceLogPacks = traceLogPacks.map(), writeToFiles = writesToFilesConfiguration.toTracingFileConfiguration(), - sentryDsn = null, + sentryDsn = sdkSentryDsn, ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingTree.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingTree.kt index cdd545197b..47e6c8f74f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingTree.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingTree.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/TraceLogPacksMapping.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/TraceLogPacksMapping.kt index 53876e9600..ec70fc90f8 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/TraceLogPacksMapping.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/TraceLogPacksMapping.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,6 +16,8 @@ fun TraceLogPack.map(): RustTraceLogPack = when (this) { TraceLogPack.EVENT_CACHE -> RustTraceLogPack.EVENT_CACHE TraceLogPack.TIMELINE -> RustTraceLogPack.TIMELINE TraceLogPack.NOTIFICATION_CLIENT -> RustTraceLogPack.NOTIFICATION_CLIENT + TraceLogPack.LATEST_EVENTS -> RustTraceLogPack.LATEST_EVENTS + TraceLogPack.SYNC_PROFILING -> RustTraceLogPack.SYNC_PROFILING } fun Collection.map(): List { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapper.kt index 172f3c4cb0..5a6481b4f8 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/CallbackFlow.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/CallbackFlow.kt index d01172a535..c7219e552e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/CallbackFlow.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/CallbackFlow.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/Disposables.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/Disposables.kt index e787cdc02f..0107d6cbf5 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/Disposables.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/Disposables.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/Error.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/Error.kt index 90681e8a5d..70fea1287e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/Error.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/Error.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,10 +14,10 @@ import timber.log.Timber fun logError(throwable: Throwable) { when (throwable) { is ClientException.Generic -> { - Timber.e("Error ${throwable.msg}", throwable) + Timber.e(throwable, "Error ${throwable.msg}") } else -> { - Timber.e("Error", throwable) + Timber.e(throwable, "Error") } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/MessageEventContent.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/MessageEventContent.kt index aa7bcb8785..3e320116c6 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/MessageEventContent.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/MessageEventContent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/SessionPathsProvider.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/SessionPathsProvider.kt index 41abd80a67..0030c6e23e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/SessionPathsProvider.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/SessionPathsProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/TaskHandle.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/TaskHandle.kt index 36f9bf4cd0..96b075c67f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/TaskHandle.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/TaskHandle.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/Token.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/Token.kt index 38d83d9cc2..815e134cf2 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/Token.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/util/Token.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt index 12e72eac0b..f613ce8dff 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -43,6 +44,7 @@ import org.matrix.rustcomponents.sdk.VerificationState import org.matrix.rustcomponents.sdk.VerificationStateListener import org.matrix.rustcomponents.sdk.use import timber.log.Timber +import java.util.concurrent.atomic.AtomicBoolean import kotlin.time.Duration.Companion.seconds import org.matrix.rustcomponents.sdk.SessionVerificationData as RustSessionVerificationData import org.matrix.rustcomponents.sdk.SessionVerificationRequestDetails as RustSessionVerificationRequestDetails @@ -65,9 +67,16 @@ class RustSessionVerificationService( private val recoveryState = MutableStateFlow(RecoveryState.UNKNOWN) + private val isInitialized = AtomicBoolean(false) + // Listen for changes in verification status and update accordingly private val verificationStateListenerTaskHandle = encryptionService.verificationStateListener(object : VerificationStateListener { override fun onUpdate(status: VerificationState) { + if (!isInitialized.get()) { + Timber.d("Discarding new verifications state: $status. E2EE is not initialised yet") + return + } + Timber.d("New verification state: $status") _sessionVerifiedStatus.value = status.map() } @@ -76,6 +85,11 @@ class RustSessionVerificationService( // In case we enter the recovery key instead we check changes in the recovery state, since the listener above won't be triggered private val recoveryStateListenerTaskHandle = encryptionService.recoveryStateListener(object : RecoveryStateListener { override fun onUpdate(status: RecoveryState) { + if (!isInitialized.get()) { + Timber.d("Discarding new recovery state: $status. E2EE is not initialised yet") + return + } + Timber.d("New recovery state: $status") // We could check the `RecoveryState`, but it's easier to just use the verification state directly recoveryState.value = status @@ -86,7 +100,7 @@ class RustSessionVerificationService( * The internal service that checks verification can only run after the initial sync. * This [StateFlow] will notify consumers when the service is ready to be used. */ - private val isReady = isSyncServiceReady.stateIn(sessionCoroutineScope, SharingStarted.Eagerly, false) + private val canVerify = isSyncServiceReady.stateIn(sessionCoroutineScope, SharingStarted.Eagerly, false) override val needsSessionVerification = sessionVerifiedStatus.map { verificationStatus -> verificationStatus == SessionVerifiedStatus.NotVerified @@ -98,14 +112,11 @@ class RustSessionVerificationService( private var listener: SessionVerificationServiceListener? = null + private val initializationMutex = Mutex() + init { // Instantiate the verification controller when possible, this is needed to get incoming verification requests - sessionCoroutineScope.launch { - tryOrNull { - encryptionService.waitForE2eeInitializationTasks() - initVerificationControllerIfNeeded() - } - } + sessionCoroutineScope.launch { ensureEncryptionIsInitialized() } } override fun setListener(listener: SessionVerificationServiceListener?) { @@ -113,13 +124,13 @@ class RustSessionVerificationService( } override suspend fun requestCurrentSessionVerification() = tryOrFail { - initVerificationControllerIfNeeded() + ensureEncryptionIsInitialized() verificationController.requestDeviceVerification() currentVerificationRequest = VerificationRequest.Outgoing.CurrentSession } override suspend fun requestUserVerification(userId: UserId) = tryOrFail { - initVerificationControllerIfNeeded() + ensureEncryptionIsInitialized() verificationController.requestUserVerification(userId.value) currentVerificationRequest = VerificationRequest.Outgoing.User(userId) } @@ -139,7 +150,7 @@ class RustSessionVerificationService( } override suspend fun acknowledgeVerificationRequest(verificationRequest: VerificationRequest.Incoming) = tryOrFail { - initVerificationControllerIfNeeded() + ensureEncryptionIsInitialized() verificationController.acknowledgeVerificationRequest( senderId = verificationRequest.details.senderProfile.userId.value, flowId = verificationRequest.details.flowId.value, @@ -224,7 +235,7 @@ class RustSessionVerificationService( override suspend fun reset(cancelAnyPendingVerificationAttempt: Boolean) { currentVerificationRequest = null - if (isReady.value && cancelAnyPendingVerificationAttempt) { + if (canVerify.value && cancelAnyPendingVerificationAttempt) { // Cancel any pending verification attempt tryOrNull { verificationController.cancelVerification() } } @@ -240,23 +251,28 @@ class RustSessionVerificationService( } } - private var initControllerMutex = Mutex() - - private suspend fun initVerificationControllerIfNeeded() = initControllerMutex.withLock { - if (!this::verificationController.isInitialized) { - tryOrFail { - verificationController = client.getSessionVerificationController() - verificationController.setDelegate(this) - } - } - } - private fun updateVerificationStatus() { runCatchingExceptions { _sessionVerifiedStatus.value = encryptionService.verificationState().map() Timber.d("New verification status: ${_sessionVerifiedStatus.value}") } } + + private suspend fun ensureEncryptionIsInitialized() = initializationMutex.withLock { + // We're keeping the separate checks instead of unconditionally calling the suspend methods + // so we can skip crossing the FFI layer when it's not needed + tryOrFail { + if (!isInitialized.get()) { + encryptionService.waitForE2eeInitializationTasks() + isInitialized.set(true) + } + + if (!this::verificationController.isInitialized) { + verificationController = client.getSessionVerificationController() + verificationController.setDelegate(this) + } + } + } } private fun VerificationState.map() = when (this) { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/SessionVerificationRequestDetails.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/SessionVerificationRequestDetails.kt index cd398fbfc1..d88f9fd6ce 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/SessionVerificationRequestDetails.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/SessionVerificationRequestDetails.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/DefaultCallWidgetSettingsProvider.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/DefaultCallWidgetSettingsProvider.kt index 00947f99a2..60a0bead33 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/DefaultCallWidgetSettingsProvider.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/DefaultCallWidgetSettingsProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.matrix.impl.widget import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.matrix.api.widget.CallAnalyticCredentialsProvider @@ -25,7 +25,6 @@ import uniffi.matrix_sdk.VirtualElementCallWidgetProperties import uniffi.matrix_sdk.Intent as CallIntent @ContributesBinding(AppScope::class) -@Inject class DefaultCallWidgetSettingsProvider( private val buildMeta: BuildMeta, private val callAnalyticsCredentialsProvider: CallAnalyticCredentialsProvider, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/MatrixWidgetSettings.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/MatrixWidgetSettings.kt index 74ac962b72..37a0b23e7f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/MatrixWidgetSettings.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/MatrixWidgetSettings.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/RustWidgetDriver.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/RustWidgetDriver.kt index c80313f5da..b908082ab6 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/RustWidgetDriver.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/RustWidgetDriver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -61,7 +62,7 @@ class RustWidgetDriver( override suspend fun send(message: String) { try { driverAndHandle.handle.send(message) - } catch (e: IllegalStateException) { + } catch (_: IllegalStateException) { // The handle is closed, ignore } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/workmanager/PerformDatabaseVacuumWorkManagerRequest.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/workmanager/PerformDatabaseVacuumWorkManagerRequest.kt new file mode 100644 index 0000000000..9c192bd96d --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/workmanager/PerformDatabaseVacuumWorkManagerRequest.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.workmanager + +import androidx.work.Constraints +import androidx.work.Data +import androidx.work.PeriodicWorkRequest +import androidx.work.WorkRequest +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.impl.workmanager.VacuumDatabaseWorker.Companion.SESSION_ID_PARAM +import io.element.android.libraries.workmanager.api.WorkManagerRequest +import io.element.android.libraries.workmanager.api.WorkManagerRequestType +import io.element.android.libraries.workmanager.api.workManagerTag +import java.util.concurrent.TimeUnit + +class PerformDatabaseVacuumWorkManagerRequest( + private val sessionId: SessionId, +) : WorkManagerRequest { + override fun build(): Result> { + val data = Data.Builder().putString(SESSION_ID_PARAM, sessionId.value).build() + val workRequest = PeriodicWorkRequest.Builder( + workerClass = VacuumDatabaseWorker::class, + // Run once a day + repeatInterval = 1, + repeatIntervalTimeUnit = TimeUnit.DAYS, + ) + .addTag(workManagerTag(sessionId, WorkManagerRequestType.DB_VACUUM)) + .setInputData(data) + // Only run when the device is idle to avoid impacting user experience + .setConstraints(Constraints.Builder().setRequiresDeviceIdle(true).build()) + .build() + + return Result.success(listOf(workRequest)) + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/workmanager/VacuumDatabaseWorker.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/workmanager/VacuumDatabaseWorker.kt new file mode 100644 index 0000000000..d52edddbb4 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/workmanager/VacuumDatabaseWorker.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.workmanager + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject +import dev.zacsweers.metro.ContributesIntoMap +import dev.zacsweers.metro.binding +import io.element.android.libraries.di.annotations.ApplicationContext +import io.element.android.libraries.matrix.api.MatrixClientProvider +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.workmanager.api.di.MetroWorkerFactory +import io.element.android.libraries.workmanager.api.di.WorkerKey +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.api.recordTransaction +import timber.log.Timber + +@AssistedInject +class VacuumDatabaseWorker( + @Assisted workerParams: WorkerParameters, + @ApplicationContext private val context: Context, + private val matrixClientProvider: MatrixClientProvider, + private val analyticsService: AnalyticsService, +) : CoroutineWorker(context, workerParams) { + companion object { + const val SESSION_ID_PARAM = "session_id" + } + + override suspend fun doWork(): Result { + Timber.d("Starting database vacuuming...") + val sessionId = inputData.getString(SESSION_ID_PARAM)?.let(::SessionId) ?: return Result.failure() + val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return Result.failure() + return analyticsService.recordTransaction("Vacuuming DBs", "vacuuming") { transaction -> + client.performDatabaseVacuum() + .fold( + onSuccess = { + Timber.d("Database vacuuming finished successfully") + Result.success() + }, + onFailure = { error -> + transaction.attachError(error) + Timber.e(error, "Database vacuuming failed") + Result.failure() + } + ) + } + } + + @ContributesIntoMap(AppScope::class, binding = binding>()) + @WorkerKey(VacuumDatabaseWorker::class) + @AssistedFactory + interface Factory : MetroWorkerFactory.WorkerInstanceFactory +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/FakeClientBuilderProvider.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/FakeClientBuilderProvider.kt index 89e0574fdf..97aedb90f4 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/FakeClientBuilderProvider.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/FakeClientBuilderProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/FakeFfiSqliteStoreBuilder.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/FakeFfiSqliteStoreBuilder.kt new file mode 100644 index 0000000000..8e3240f5be --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/FakeFfiSqliteStoreBuilder.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl + +import org.matrix.rustcomponents.sdk.NoHandle +import org.matrix.rustcomponents.sdk.SqliteStoreBuilder + +class FakeFfiSqliteStoreBuilder : SqliteStoreBuilder(NoHandle) { + override fun cacheSize(cacheSize: UInt?): SqliteStoreBuilder = this + override fun journalSizeLimit(limit: UInt?): SqliteStoreBuilder = this + override fun passphrase(passphrase: String?): SqliteStoreBuilder = this + override fun poolMaxSize(poolMaxSize: UInt?): SqliteStoreBuilder = this + override fun systemIsMemoryConstrained(): SqliteStoreBuilder = this +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegateTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegateTest.kt index 224c4118fb..7c63348298 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegateTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustClientSessionDelegateTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactoryTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactoryTest.kt index 92f9438322..fa69752afe 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactoryTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactoryTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,12 +14,16 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.impl.auth.FakeProxyProvider import io.element.android.libraries.matrix.impl.auth.FakeUserCertificatesProvider import io.element.android.libraries.matrix.impl.room.FakeTimelineEventTypeFilterFactory +import io.element.android.libraries.matrix.impl.storage.FakeSqliteStoreBuilderProvider import io.element.android.libraries.network.useragent.SimpleUserAgentProvider import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.sessionstorage.test.InMemorySessionStore import io.element.android.libraries.sessionstorage.test.aSessionData +import io.element.android.libraries.workmanager.api.WorkManagerRequest +import io.element.android.libraries.workmanager.test.FakeWorkManagerScheduler import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.services.toolbox.test.systemclock.FakeSystemClock +import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest @@ -28,9 +33,14 @@ import java.io.File class RustMatrixClientFactoryTest { @Test fun test() = runTest { - val sut = createRustMatrixClientFactory() + val scheduleVacuumLambda = lambdaRecorder {} + val workManagerScheduler = FakeWorkManagerScheduler(submitLambda = scheduleVacuumLambda) + val sut = createRustMatrixClientFactory(workManagerScheduler = workManagerScheduler) + val result = sut.create(aSessionData()) + assertThat(result.sessionId).isEqualTo(SessionId("@alice:server.org")) + scheduleVacuumLambda.assertions().isCalledOnce() result.destroy() } } @@ -41,6 +51,7 @@ fun TestScope.createRustMatrixClientFactory( updateUserProfileResult = { _, _, _ -> }, ), clientBuilderProvider: ClientBuilderProvider = FakeClientBuilderProvider(), + workManagerScheduler: FakeWorkManagerScheduler = FakeWorkManagerScheduler(), ) = RustMatrixClientFactory( cacheDirectory = cacheDirectory, appCoroutineScope = backgroundScope, @@ -54,4 +65,6 @@ fun TestScope.createRustMatrixClientFactory( featureFlagService = FakeFeatureFlagService(), timelineEventTypeFilterFactory = FakeTimelineEventTypeFilterFactory(), clientBuilderProvider = clientBuilderProvider, + sqliteStoreBuilderProvider = FakeSqliteStoreBuilderProvider(), + workManagerScheduler = workManagerScheduler, ) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt index dec0f4cd4f..fc459dba86 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,6 +11,7 @@ package io.element.android.libraries.matrix.impl import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.core.data.bytes import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiClient import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiSyncService @@ -21,6 +23,8 @@ import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.sessionstorage.test.InMemorySessionStore import io.element.android.libraries.sessionstorage.test.aSessionData +import io.element.android.libraries.workmanager.test.FakeWorkManagerScheduler +import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.services.toolbox.test.systemclock.FakeSystemClock import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value @@ -31,6 +35,7 @@ import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Test import org.matrix.rustcomponents.sdk.Client +import org.matrix.rustcomponents.sdk.StoreSizes import org.matrix.rustcomponents.sdk.UserProfile import java.io.File @@ -95,6 +100,20 @@ class RustMatrixClientTest { client.destroy() } + @Test + fun `getDatabaseSizes returns the database sizes`() = runTest { + val client = createRustMatrixClient( + client = FakeFfiClient(getStoreSizesResult = { StoreSizes(null, 10uL, 11uL, 12uL) }) + ) + + client.getDatabaseSizes().getOrThrow().run { + assertThat(cryptoStore).isNull() + assertThat(stateStore).isEqualTo(10.bytes) + assertThat(eventCacheStore).isEqualTo(11.bytes) + assertThat(mediaStore).isEqualTo(12.bytes) + } + } + private fun TestScope.createRustMatrixClient( client: Client = FakeFfiClient(), sessionStore: SessionStore = InMemorySessionStore( @@ -113,5 +132,7 @@ class RustMatrixClientTest { clock = FakeSystemClock(), timelineEventTypeFilterFactory = FakeTimelineEventTypeFilterFactory(), featureFlagService = FakeFeatureFlagService(), + analyticsService = FakeAnalyticsService(), + workManagerScheduler = FakeWorkManagerScheduler(submitLambda = {}), ) } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedExtKtTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedExtKtTest.kt index 8bf86c5978..68adfe00a2 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedExtKtTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedExtKtTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt index 4ff320dd32..e59329bd44 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationExceptionMappingTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationExceptionMappingTest.kt index c73ba1424b..a2425525bc 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationExceptionMappingTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/AuthenticationExceptionMappingTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,10 +17,10 @@ import org.matrix.rustcomponents.sdk.OidcException class AuthenticationExceptionMappingTest { @Test - fun `mapping an exception with no message returns 'Unknown error' message`() { + fun `mapping an exception with no message returns null message`() { val exception = Exception() val mappedException = exception.mapAuthenticationException() - assertThat(mappedException.message).isEqualTo("Unknown error") + assertThat(mappedException.message).isNull() } @Test @@ -46,7 +47,7 @@ class AuthenticationExceptionMappingTest { assertThat(ClientBuildException.Sdk("SDK issue").mapAuthenticationException()) .isException("SDK issue") assertThat(ClientBuildException.ServerUnreachable("Server unreachable").mapAuthenticationException()) - .isException("Server unreachable") + .isException("Server unreachable") assertThat(ClientBuildException.SlidingSync("Sliding Sync").mapAuthenticationException()) .isException("Sliding Sync") assertThat(ClientBuildException.WellKnownDeserializationException("WellKnown Deserialization").mapAuthenticationException()) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakePassphraseGenerator.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakePassphraseGenerator.kt index cc77221c19..a089920422 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakePassphraseGenerator.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakePassphraseGenerator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakeProxyProvider.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakeProxyProvider.kt index b056b2eda1..4fa0c06cd5 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakeProxyProvider.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakeProxyProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakeUserCertificatesProvider.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakeUserCertificatesProvider.kt index e2af963501..bf4f697eb6 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakeUserCertificatesProvider.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/FakeUserCertificatesProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetailsKtTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetailsKtTest.kt index eb3ac75cf6..a5c7b2dbc8 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetailsKtTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/HomeserverDetailsKtTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfigurationProviderTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfigurationProviderTest.kt index 96d1b46f93..095cf54946 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfigurationProviderTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/OidcConfigurationProviderTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustHomeserverLoginCompatibilityCheckerTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustHomeserverLoginCompatibilityCheckerTest.kt new file mode 100644 index 0000000000..50d1f3723b --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustHomeserverLoginCompatibilityCheckerTest.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.auth + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.impl.FakeClientBuilderProvider +import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiClient +import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiClientBuilder +import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiHomeserverLoginDetails +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class RustHomeserverLoginCompatibilityCheckerTest { + @Test + fun `check - is valid if it supports OIDC login`() = runTest { + val sut = createChecker { FakeFfiHomeserverLoginDetails(supportsOidcLogin = true) } + assertThat(sut.check("https://matrix.host.org").getOrNull()).isTrue() + } + + @Test + fun `check - is valid if it supports password login`() = runTest { + val sut = createChecker { FakeFfiHomeserverLoginDetails(supportsPasswordLogin = true) } + assertThat(sut.check("https://matrix.host.org").getOrNull()).isTrue() + } + + @Test + fun `check - is not valid if it only supports SSO login`() = runTest { + val sut = createChecker { FakeFfiHomeserverLoginDetails(supportsSsoLogin = true) } + assertThat(sut.check("https://matrix.host.org").getOrNull()).isFalse() + } + + @Test + fun `check - is not valid if fetching the data fails`() = runTest { + val sut = createChecker { error("Unexpected error!") } + assertThat(sut.check("https://matrix.host.org").isFailure).isTrue() + } + + private fun createChecker( + result: () -> FakeFfiHomeserverLoginDetails, + ) = RustHomeServerLoginCompatibilityChecker( + clientBuilderProvider = FakeClientBuilderProvider { + FakeFfiClientBuilder { + FakeFfiClient(homeserverLoginDetailsResult = result) + } + }, + userCertificatesProvider = FakeUserCertificatesProvider(), + ) +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationServiceTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationServiceTest.kt index 490c921aa9..f4ce7b1fdd 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationServiceTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationServiceTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapperTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapperTest.kt index 439ed109f3..0ef20c82a6 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapperTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrErrorMapperTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrLoginProgressExtensionsKtTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrLoginProgressExtensionsKtTest.kt index 669c599ff7..94d1833d02 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrLoginProgressExtensionsKtTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/QrLoginProgressExtensionsKtTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/SdkQrCodeLoginDataTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/SdkQrCodeLoginDataTest.kt index 1ba998fc41..a0c8f4598f 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/SdkQrCodeLoginDataTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/SdkQrCodeLoginDataTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/core/ProgressWatcherWrapperKtTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/core/ProgressWatcherWrapperKtTest.kt index 093bc8ecda..63b7c9a1b9 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/core/ProgressWatcherWrapperKtTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/core/ProgressWatcherWrapperKtTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupStateMapperTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupStateMapperTest.kt index d0dc2e526b..66e71ae288 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupStateMapperTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupStateMapperTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupUploadStateMapperTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupUploadStateMapperTest.kt index ac68884f13..8818607c87 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupUploadStateMapperTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/encryption/BackupUploadStateMapperTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/encryption/EnableRecoveryProgressMapperTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/encryption/EnableRecoveryProgressMapperTest.kt index b9ef597eb8..a0227c9332 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/encryption/EnableRecoveryProgressMapperTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/encryption/EnableRecoveryProgressMapperTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/encryption/RecoveryStateMapperTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/encryption/RecoveryStateMapperTest.kt index dd5b1ebb28..108351980d 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/encryption/RecoveryStateMapperTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/encryption/RecoveryStateMapperTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/EventTimelineItem.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/EventTimelineItem.kt index fbbb6c3375..c106b6b39e 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/EventTimelineItem.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/EventTimelineItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -20,14 +21,14 @@ import org.matrix.rustcomponents.sdk.ShieldState import org.matrix.rustcomponents.sdk.TimelineItemContent import uniffi.matrix_sdk_ui.EventItemOrigin -fun aRustEventTimelineItem( +internal fun aRustEventTimelineItem( isRemote: Boolean = true, eventOrTransactionId: EventOrTransactionId = EventOrTransactionId.EventId(AN_EVENT_ID.value), sender: String = A_USER_ID.value, senderProfile: ProfileDetails = ProfileDetails.Unavailable, isOwn: Boolean = true, isEditable: Boolean = true, - content: TimelineItemContent = aRustTimelineItemMessageContent(), + content: TimelineItemContent = aRustTimelineItemContentMsgLike(), timestamp: ULong = 0uL, debugInfo: EventTimelineItemDebugInfo = anEventTimelineItemDebugInfo(), localSendState: EventSendState? = null, diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/EventTimelineItemDebugInfo.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/EventTimelineItemDebugInfo.kt index 61c15cf77a..badd4c3aba 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/EventTimelineItemDebugInfo.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/EventTimelineItemDebugInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,7 @@ package io.element.android.libraries.matrix.impl.fixtures.factories import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfo -fun anEventTimelineItemDebugInfo( +internal fun anEventTimelineItemDebugInfo( model: String = "model", originalJson: String? = null, latestEditJson: String? = null, diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/NotificationItem.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/NotificationItem.kt index ee5e3ac4e2..672439ad6c 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/NotificationItem.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/NotificationItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,6 +12,8 @@ import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiTimelineEvent import io.element.android.libraries.matrix.test.A_ROOM_NAME import io.element.android.libraries.matrix.test.A_USER_NAME +import org.matrix.rustcomponents.sdk.Action +import org.matrix.rustcomponents.sdk.BatchNotificationResult import org.matrix.rustcomponents.sdk.JoinRule import org.matrix.rustcomponents.sdk.NotificationEvent import org.matrix.rustcomponents.sdk.NotificationItem @@ -19,13 +22,14 @@ import org.matrix.rustcomponents.sdk.NotificationSenderInfo import org.matrix.rustcomponents.sdk.NotificationStatus import org.matrix.rustcomponents.sdk.TimelineEvent -fun aRustNotificationItem( +internal fun aRustNotificationItem( event: NotificationEvent = aRustNotificationEventTimeline(), senderInfo: NotificationSenderInfo = aRustNotificationSenderInfo(), roomInfo: NotificationRoomInfo = aRustNotificationRoomInfo(), isNoisy: Boolean? = false, hasMention: Boolean? = false, threadId: ThreadId? = null, + actions: List? = null, ) = NotificationItem( event = event, senderInfo = senderInfo, @@ -33,15 +37,16 @@ fun aRustNotificationItem( isNoisy = isNoisy, hasMention = hasMention, threadId = threadId?.value, + actions = actions, ) -fun aRustBatchNotificationResult( +internal fun aRustBatchNotificationResultOk( notificationStatus: NotificationStatus = NotificationStatus.Event(aRustNotificationItem()), -) = org.matrix.rustcomponents.sdk.BatchNotificationResult.Ok( +) = BatchNotificationResult.Ok( status = notificationStatus, ) -fun aRustNotificationSenderInfo( +internal fun aRustNotificationSenderInfo( displayName: String? = A_USER_NAME, avatarUrl: String? = null, isNameAmbiguous: Boolean = false, @@ -51,7 +56,7 @@ fun aRustNotificationSenderInfo( isNameAmbiguous = isNameAmbiguous, ) -fun aRustNotificationRoomInfo( +internal fun aRustNotificationRoomInfo( displayName: String = A_ROOM_NAME, avatarUrl: String? = null, canonicalAlias: String? = null, @@ -60,6 +65,7 @@ fun aRustNotificationRoomInfo( isEncrypted: Boolean? = true, isDirect: Boolean = false, joinRule: JoinRule? = null, + isSpace: Boolean = false, ) = NotificationRoomInfo( displayName = displayName, avatarUrl = avatarUrl, @@ -69,9 +75,10 @@ fun aRustNotificationRoomInfo( isEncrypted = isEncrypted, isDirect = isDirect, joinRule = joinRule, + isSpace = isSpace, ) -fun aRustNotificationEventTimeline( +internal fun aRustNotificationEventTimeline( event: TimelineEvent = FakeFfiTimelineEvent(), ) = NotificationEvent.Timeline( event = event, diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomDescription.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomDescription.kt index 270d41ec18..14960e9f70 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomDescription.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomDescription.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -21,15 +22,13 @@ internal fun aRustRoomDescription( joinRule: PublicRoomJoinRule = PublicRoomJoinRule.PUBLIC, isWorldReadable: Boolean = true, joinedMembers: ULong = 2u, -): RoomDescription { - return RoomDescription( - roomId = roomId, - name = name, - topic = topic, - alias = alias, - avatarUrl = avatarUrl, - joinRule = joinRule, - isWorldReadable = isWorldReadable, - joinedMembers = joinedMembers, - ) -} +) = RoomDescription( + roomId = roomId, + name = name, + topic = topic, + alias = alias, + avatarUrl = avatarUrl, + joinRule = joinRule, + isWorldReadable = isWorldReadable, + joinedMembers = joinedMembers, +) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomHero.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomHero.kt index ee0123b8ad..6abb08c667 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomHero.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomHero.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,10 +14,8 @@ import org.matrix.rustcomponents.sdk.RoomHero internal fun aRustRoomHero( userId: UserId = A_USER_ID, -): RoomHero { - return RoomHero( - userId = userId.value, - displayName = "displayName", - avatarUrl = "avatarUrl", - ) -} +) = RoomHero( + userId = userId.value, + displayName = "displayName", + avatarUrl = "avatarUrl", +) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomInfo.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomInfo.kt index b076b33f80..298db5e722 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomInfo.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -21,7 +22,7 @@ import org.matrix.rustcomponents.sdk.RoomPowerLevels import org.matrix.rustcomponents.sdk.SuccessorRoom import uniffi.matrix_sdk_base.EncryptionState -fun aRustRoomInfo( +internal fun aRustRoomInfo( id: String = A_ROOM_ID.value, displayName: String? = A_ROOM_NAME, rawName: String? = A_ROOM_NAME, diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomMember.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomMember.kt index d606008989..77fa814cca 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomMember.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomMember.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,7 @@ import org.matrix.rustcomponents.sdk.PowerLevel import org.matrix.rustcomponents.sdk.RoomMember import uniffi.matrix_sdk.RoomMemberRole -fun aRustRoomMember( +internal fun aRustRoomMember( userId: UserId, displayName: String? = null, avatarUrl: String? = null, diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomNotificationSettings.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomNotificationSettings.kt index ca96d985f8..a66fb3f332 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomNotificationSettings.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomNotificationSettings.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,7 @@ package io.element.android.libraries.matrix.impl.fixtures.factories import org.matrix.rustcomponents.sdk.RoomNotificationMode import org.matrix.rustcomponents.sdk.RoomNotificationSettings -fun aRustRoomNotificationSettings( +internal fun aRustRoomNotificationSettings( mode: RoomNotificationMode = RoomNotificationMode.ALL_MESSAGES, isDefault: Boolean = true, ) = RoomNotificationSettings( diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomPowerLevels.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomPowerLevelsValues.kt similarity index 84% rename from libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomPowerLevels.kt rename to libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomPowerLevelsValues.kt index 79d1606957..1c1bbb42e3 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomPowerLevels.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomPowerLevelsValues.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -20,6 +21,7 @@ internal fun aRustRoomPowerLevelsValues( roomName: Long, roomAvatar: Long, roomTopic: Long, + spaceChild: Long, ) = RoomPowerLevelsValues( ban = ban, invite = invite, @@ -31,4 +33,5 @@ internal fun aRustRoomPowerLevelsValues( roomName = roomName, roomAvatar = roomAvatar, roomTopic = roomTopic, + spaceChild = spaceChild ) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomPreviewInfo.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomPreviewInfo.kt index c3bcd98497..aacb809a70 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomPreviewInfo.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomPreviewInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,20 +19,18 @@ internal fun aRustRoomPreviewInfo( canonicalAlias: String? = A_ROOM_ALIAS.value, membership: Membership? = Membership.JOINED, joinRule: JoinRule = JoinRule.Public, -): RoomPreviewInfo { - return RoomPreviewInfo( - roomId = A_ROOM_ID.value, - canonicalAlias = canonicalAlias, - name = "name", - topic = "topic", - avatarUrl = "avatarUrl", - numJoinedMembers = 1u, - numActiveMembers = 1u, - isDirect = false, - roomType = RoomType.Room, - isHistoryWorldReadable = true, - membership = membership, - joinRule = joinRule, - heroes = null, - ) -} +) = RoomPreviewInfo( + roomId = A_ROOM_ID.value, + canonicalAlias = canonicalAlias, + name = "name", + topic = "topic", + avatarUrl = "avatarUrl", + numJoinedMembers = 1u, + numActiveMembers = 1u, + isDirect = false, + roomType = RoomType.Room, + isHistoryWorldReadable = true, + membership = membership, + joinRule = joinRule, + heroes = null, +) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/SearchUsersResults.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/SearchUsersResults.kt index bfd232ae38..7782eb59e5 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/SearchUsersResults.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/SearchUsersResults.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/Session.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/Session.kt index b1301d0777..4671c457b0 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/Session.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/Session.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,14 +18,12 @@ internal fun aRustSession( proxy: SlidingSyncVersion = SlidingSyncVersion.NONE, accessToken: String = "accessToken", refreshToken: String = "refreshToken", -): Session { - return Session( - accessToken = accessToken, - refreshToken = refreshToken, - userId = A_USER_ID.value, - deviceId = A_DEVICE_ID.value, - homeserverUrl = A_HOMESERVER_URL, - oidcData = null, - slidingSyncVersion = proxy, - ) -} +) = Session( + accessToken = accessToken, + refreshToken = refreshToken, + userId = A_USER_ID.value, + deviceId = A_DEVICE_ID.value, + homeserverUrl = A_HOMESERVER_URL, + oidcData = null, + slidingSyncVersion = proxy, +) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/SpaceRoom.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/SpaceRoom.kt index 58a86ffe2e..50115055c2 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/SpaceRoom.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/SpaceRoom.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,7 +16,7 @@ import org.matrix.rustcomponents.sdk.RoomHero import org.matrix.rustcomponents.sdk.RoomType import org.matrix.rustcomponents.sdk.SpaceRoom -fun aRustSpaceRoom( +internal fun aRustSpaceRoom( roomId: RoomId = A_ROOM_ID, isDirect: Boolean = false, canonicalAlias: String? = null, diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/TimelineEventType.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/TimelineEventContentMessageLike.kt similarity index 72% rename from libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/TimelineEventType.kt rename to libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/TimelineEventContentMessageLike.kt index 7c70d54154..877f2056e9 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/TimelineEventType.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/TimelineEventContentMessageLike.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,17 +13,15 @@ import org.matrix.rustcomponents.sdk.FormattedBody import org.matrix.rustcomponents.sdk.MessageLikeEventContent import org.matrix.rustcomponents.sdk.MessageType import org.matrix.rustcomponents.sdk.TextMessageContent -import org.matrix.rustcomponents.sdk.TimelineEventType +import org.matrix.rustcomponents.sdk.TimelineEventContent -fun aRustTimelineEventTypeMessageLike( +internal fun aRustTimelineEventContentMessageLike( content: MessageLikeEventContent = aRustMessageLikeEventContentRoomMessage(), -): TimelineEventType.MessageLike { - return TimelineEventType.MessageLike( - content = content, - ) -} +) = TimelineEventContent.MessageLike( + content = content, +) -fun aRustMessageLikeEventContentRoomMessage( +internal fun aRustMessageLikeEventContentRoomMessage( messageType: MessageType = aRustMessageTypeText(), inReplyToEventId: String? = null, ) = MessageLikeEventContent.RoomMessage( @@ -30,13 +29,13 @@ fun aRustMessageLikeEventContentRoomMessage( inReplyToEventId = inReplyToEventId, ) -fun aRustMessageTypeText( +internal fun aRustMessageTypeText( content: TextMessageContent = aRustTextMessageContent(), ) = MessageType.Text( content = content, ) -fun aRustTextMessageContent( +internal fun aRustTextMessageContent( body: String = A_MESSAGE, formatted: FormattedBody? = null, ) = TextMessageContent( diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/EventTimelineItemContent.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/TimelineItemContentMsgLike.kt similarity index 82% rename from libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/EventTimelineItemContent.kt rename to libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/TimelineItemContentMsgLike.kt index b7ea8510c2..0db7590a5c 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/EventTimelineItemContent.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/TimelineItemContentMsgLike.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,7 +15,9 @@ import org.matrix.rustcomponents.sdk.MsgLikeKind import org.matrix.rustcomponents.sdk.TextMessageContent import org.matrix.rustcomponents.sdk.TimelineItemContent -fun aRustTimelineItemMessageContent(body: String = "Hello") = TimelineItemContent.MsgLike( +internal fun aRustTimelineItemContentMsgLike( + body: String = "Hello", +) = TimelineItemContent.MsgLike( content = MsgLikeContent( kind = MsgLikeKind.Message( content = MessageContent( diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UnableToDecryptInfo.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UnableToDecryptInfo.kt index 2239b5fffc..4faa914e8c 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UnableToDecryptInfo.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UnableToDecryptInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,14 +19,12 @@ internal fun aRustUnableToDecryptInfo( userTrustsOwnIdentity: Boolean = false, senderHomeserver: String = "", ownHomeserver: String = "", -): UnableToDecryptInfo { - return UnableToDecryptInfo( - eventId = eventId, - timeToDecryptMs = timeToDecryptMs, - cause = cause, - eventLocalAgeMillis = eventLocalAgeMillis, - userTrustsOwnIdentity = userTrustsOwnIdentity, - senderHomeserver = senderHomeserver, - ownHomeserver = ownHomeserver, - ) -} +) = UnableToDecryptInfo( + eventId = eventId, + timeToDecryptMs = timeToDecryptMs, + cause = cause, + eventLocalAgeMillis = eventLocalAgeMillis, + userTrustsOwnIdentity = userTrustsOwnIdentity, + senderHomeserver = senderHomeserver, + ownHomeserver = ownHomeserver, +) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UserProfile.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UserProfile.kt index ee1ab3d84e..8713e08f1c 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UserProfile.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UserProfile.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,7 @@ package io.element.android.libraries.matrix.impl.fixtures.factories import io.element.android.libraries.matrix.test.A_USER_ID import org.matrix.rustcomponents.sdk.UserProfile -fun aRustUserProfile( +internal fun aRustUserProfile( userId: String = A_USER_ID.value, displayName: String = "displayName", avatarUrl: String = "avatarUrl", diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiCheckCodeSender.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiCheckCodeSender.kt new file mode 100644 index 0000000000..a19c4e3766 --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiCheckCodeSender.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.fixtures.fakes + +import io.element.android.tests.testutils.lambda.lambdaError +import org.matrix.rustcomponents.sdk.CheckCodeSender +import org.matrix.rustcomponents.sdk.NoHandle + +class FakeFfiCheckCodeSender( + private val sendResult: (UByte) -> Unit = { _ -> lambdaError() } +) : CheckCodeSender(NoHandle) { + override suspend fun send(code: UByte) { + sendResult(code) + } +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClient.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClient.kt index 3f53d9d5e2..dc9d21cddc 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClient.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClient.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,7 +18,7 @@ import org.matrix.rustcomponents.sdk.ClientDelegate import org.matrix.rustcomponents.sdk.Encryption import org.matrix.rustcomponents.sdk.HomeserverLoginDetails import org.matrix.rustcomponents.sdk.IgnoredUsersListener -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.NotificationClient import org.matrix.rustcomponents.sdk.NotificationProcessSetup import org.matrix.rustcomponents.sdk.NotificationSettings @@ -27,11 +28,13 @@ import org.matrix.rustcomponents.sdk.RoomDirectorySearch import org.matrix.rustcomponents.sdk.Session import org.matrix.rustcomponents.sdk.SessionVerificationController import org.matrix.rustcomponents.sdk.SpaceService +import org.matrix.rustcomponents.sdk.StoreSizes import org.matrix.rustcomponents.sdk.SyncService import org.matrix.rustcomponents.sdk.SyncServiceBuilder import org.matrix.rustcomponents.sdk.TaskHandle import org.matrix.rustcomponents.sdk.UnableToDecryptDelegate import org.matrix.rustcomponents.sdk.UserProfile +import uniffi.matrix_sdk_base.MediaRetentionPolicy class FakeFfiClient( private val userId: String = A_USER_ID.value, @@ -44,8 +47,9 @@ class FakeFfiClient( private val withUtdHook: (UnableToDecryptDelegate) -> Unit = { lambdaError() }, private val getProfileResult: (String) -> UserProfile = { UserProfile(userId = userId, displayName = null, avatarUrl = null) }, private val homeserverLoginDetailsResult: () -> HomeserverLoginDetails = { lambdaError() }, + private val getStoreSizesResult: () -> StoreSizes = { lambdaError() }, private val closeResult: () -> Unit = {}, -) : Client(NoPointer) { +) : Client(NoHandle) { override fun userId(): String = userId override fun deviceId(): String = deviceId override suspend fun notificationClient(processSetup: NotificationProcessSetup) = notificationClient @@ -56,7 +60,7 @@ class FakeFfiClient( override suspend fun cachedAvatarUrl(): String? = null override suspend fun restoreSession(session: Session) = Unit override fun syncService(): SyncServiceBuilder = FakeFfiSyncServiceBuilder() - override fun spaceService(): SpaceService = FakeFfiSpaceService() + override suspend fun spaceService(): SpaceService = FakeFfiSpaceService() override fun roomDirectorySearch(): RoomDirectorySearch = FakeFfiRoomDirectorySearch() override suspend fun setPusher( identifiers: PusherIdentifiers, @@ -87,5 +91,11 @@ class FakeFfiClient( return homeserverLoginDetailsResult() } + override suspend fun setMediaRetentionPolicy(policy: MediaRetentionPolicy) {} + + override suspend fun getStoreSizes(): StoreSizes { + return getStoreSizesResult() + } + override fun close() = closeResult() } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClientBuilder.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClientBuilder.kt index d2dce3816c..fa10a63e42 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClientBuilder.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClientBuilder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,16 +11,17 @@ package io.element.android.libraries.matrix.impl.fixtures.fakes import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.ClientBuilder import org.matrix.rustcomponents.sdk.ClientSessionDelegate -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.RequestConfig import org.matrix.rustcomponents.sdk.SlidingSyncVersionBuilder +import org.matrix.rustcomponents.sdk.SqliteStoreBuilder import uniffi.matrix_sdk.BackupDownloadStrategy import uniffi.matrix_sdk_crypto.CollectStrategy import uniffi.matrix_sdk_crypto.DecryptionSettings class FakeFfiClientBuilder( val buildResult: () -> Client = { FakeFfiClient(withUtdHook = {}) } -) : ClientBuilder(NoPointer) { +) : ClientBuilder(NoHandle) { override fun addRootCertificates(certificates: List) = this override fun autoEnableBackups(autoEnableBackups: Boolean) = this override fun autoEnableCrossSigning(autoEnableCrossSigning: Boolean) = this @@ -29,7 +31,6 @@ class FakeFfiClientBuilder( override fun decryptionSettings(decryptionSettings: DecryptionSettings): ClientBuilder = this override fun disableSslVerification() = this override fun homeserverUrl(url: String) = this - override fun sessionPassphrase(passphrase: String?) = this override fun proxy(url: String) = this override fun requestConfig(config: RequestConfig) = this override fun roomKeyRecipientStrategy(strategy: CollectStrategy) = this @@ -42,5 +43,7 @@ class FakeFfiClientBuilder( override fun username(username: String) = this override fun enableShareHistoryOnInvite(enableShareHistoryOnInvite: Boolean): ClientBuilder = this override fun threadsEnabled(enabled: Boolean, threadSubscriptions: Boolean): ClientBuilder = this + override fun sqliteStore(config: SqliteStoreBuilder): ClientBuilder = this + override fun inMemoryStore(): ClientBuilder = this override suspend fun build() = buildResult() } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiEncryption.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiEncryption.kt index 177ab5b251..89cfd57fbc 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiEncryption.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiEncryption.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,13 +12,13 @@ import io.element.android.tests.testutils.simulateLongTask import org.matrix.rustcomponents.sdk.BackupState import org.matrix.rustcomponents.sdk.BackupStateListener import org.matrix.rustcomponents.sdk.Encryption -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.RecoveryState import org.matrix.rustcomponents.sdk.RecoveryStateListener import org.matrix.rustcomponents.sdk.TaskHandle import org.matrix.rustcomponents.sdk.VerificationStateListener -class FakeFfiEncryption : Encryption(NoPointer) { +class FakeFfiEncryption : Encryption(NoHandle) { override fun verificationStateListener(listener: VerificationStateListener): TaskHandle { return FakeFfiTaskHandle() } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiGrantLoginWithQrCodeHandler.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiGrantLoginWithQrCodeHandler.kt new file mode 100644 index 0000000000..cd0733695b --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiGrantLoginWithQrCodeHandler.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.fixtures.fakes + +import org.matrix.rustcomponents.sdk.GrantGeneratedQrLoginProgress +import org.matrix.rustcomponents.sdk.GrantGeneratedQrLoginProgressListener +import org.matrix.rustcomponents.sdk.GrantLoginWithQrCodeHandler +import org.matrix.rustcomponents.sdk.GrantQrLoginProgress +import org.matrix.rustcomponents.sdk.GrantQrLoginProgressListener +import org.matrix.rustcomponents.sdk.NoHandle +import org.matrix.rustcomponents.sdk.QrCodeData + +class FakeFfiGrantLoginWithQrCodeHandler( + private val generateResult: () -> Unit = {}, + private val scanResult: (QrCodeData) -> Unit = {}, +) : GrantLoginWithQrCodeHandler(NoHandle) { + private var generateProgressListener: GrantGeneratedQrLoginProgressListener? = null + private var scanProgressListener: GrantQrLoginProgressListener? = null + override suspend fun generate(progressListener: GrantGeneratedQrLoginProgressListener) { + generateProgressListener = progressListener + generateResult() + } + + fun emitGenerateProgress(progress: GrantGeneratedQrLoginProgress) { + generateProgressListener?.onUpdate(progress) + } + + override suspend fun scan(qrCodeData: QrCodeData, progressListener: GrantQrLoginProgressListener) { + scanProgressListener = progressListener + scanResult(qrCodeData) + } + + fun emitScanProgress(progress: GrantQrLoginProgress) { + scanProgressListener?.onUpdate(progress) + } +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiHomeserverLoginDetails.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiHomeserverLoginDetails.kt index 8977470365..ade3a2328f 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiHomeserverLoginDetails.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiHomeserverLoginDetails.kt @@ -1,21 +1,24 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.fixtures.fakes import org.matrix.rustcomponents.sdk.HomeserverLoginDetails -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle class FakeFfiHomeserverLoginDetails( private val url: String = "https://example.org", - private val supportsPasswordLogin: Boolean = true, - private val supportsOidcLogin: Boolean = false -) : HomeserverLoginDetails(NoPointer) { + private val supportsPasswordLogin: Boolean = false, + private val supportsOidcLogin: Boolean = false, + private val supportsSsoLogin: Boolean = false, +) : HomeserverLoginDetails(NoHandle) { override fun url(): String = url override fun supportsOidcLogin(): Boolean = supportsOidcLogin override fun supportsPasswordLogin(): Boolean = supportsPasswordLogin + override fun supportsSsoLogin(): Boolean = supportsSsoLogin } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiLazyTimelineItemProvider.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiLazyTimelineItemProvider.kt index 6149a9164d..8ee167d769 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiLazyTimelineItemProvider.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiLazyTimelineItemProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,14 +11,14 @@ package io.element.android.libraries.matrix.impl.fixtures.fakes import io.element.android.libraries.matrix.impl.fixtures.factories.anEventTimelineItemDebugInfo import org.matrix.rustcomponents.sdk.EventTimelineItemDebugInfo import org.matrix.rustcomponents.sdk.LazyTimelineItemProvider -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.SendHandle import org.matrix.rustcomponents.sdk.ShieldState class FakeFfiLazyTimelineItemProvider( private val debugInfo: EventTimelineItemDebugInfo = anEventTimelineItemDebugInfo(), private val shieldsState: ShieldState? = null, -) : LazyTimelineItemProvider(NoPointer) { +) : LazyTimelineItemProvider(NoHandle) { override fun getShields(strict: Boolean) = shieldsState override fun debugInfo() = debugInfo override fun getSendHandle(): SendHandle? = null diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiNotificationClient.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiNotificationClient.kt index d17f4f949c..782cf0effb 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiNotificationClient.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiNotificationClient.kt @@ -1,21 +1,22 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.fixtures.fakes import org.matrix.rustcomponents.sdk.BatchNotificationResult -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.NotificationClient import org.matrix.rustcomponents.sdk.NotificationItemsRequest class FakeFfiNotificationClient( var notificationItemResult: Map = emptyMap(), val closeResult: () -> Unit = { } -) : NotificationClient(NoPointer) { +) : NotificationClient(NoHandle) { override suspend fun getNotifications(requests: List): Map { return notificationItemResult } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiNotificationSettings.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiNotificationSettings.kt index 7a65b8d2cd..d2ed0ce8b8 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiNotificationSettings.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiNotificationSettings.kt @@ -1,21 +1,22 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.fixtures.fakes import io.element.android.libraries.matrix.impl.fixtures.factories.aRustRoomNotificationSettings -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.NotificationSettings import org.matrix.rustcomponents.sdk.NotificationSettingsDelegate import org.matrix.rustcomponents.sdk.RoomNotificationSettings class FakeFfiNotificationSettings( private val roomNotificationSettings: RoomNotificationSettings = aRustRoomNotificationSettings(), -) : NotificationSettings(NoPointer) { +) : NotificationSettings(NoHandle) { private var delegate: NotificationSettingsDelegate? = null override fun setDelegate(delegate: NotificationSettingsDelegate?) { diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiQrCodeData.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiQrCodeData.kt index 1be8b87a66..4070d1b2cd 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiQrCodeData.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiQrCodeData.kt @@ -1,20 +1,26 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.fixtures.fakes import io.element.android.tests.testutils.lambda.lambdaError -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.QrCodeData class FakeFfiQrCodeData( private val serverNameResult: () -> String? = { lambdaError() }, -) : QrCodeData(NoPointer) { + private val toBytesResult: () -> ByteArray = { lambdaError() }, +) : QrCodeData(NoHandle) { override fun serverName(): String? { return serverNameResult() } + + override fun toBytes(): ByteArray { + return toBytesResult() + } } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoom.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoom.kt index 41a0424991..953e42b132 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoom.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoom.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,8 +12,8 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.impl.fixtures.factories.aRustRoomInfo import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.tests.testutils.lambda.lambdaError -import org.matrix.rustcomponents.sdk.EventTimelineItem -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.LatestEventValue +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.RoomInfo import org.matrix.rustcomponents.sdk.RoomMembersIterator @@ -23,10 +24,10 @@ class FakeFfiRoom( private val getMembers: () -> RoomMembersIterator = { lambdaError() }, private val getMembersNoSync: () -> RoomMembersIterator = { lambdaError() }, private val leaveLambda: () -> Unit = { lambdaError() }, - private val latestEventLambda: () -> EventTimelineItem? = { lambdaError() }, + private val latestEventLambda: () -> LatestEventValue = { lambdaError() }, private val suggestedRoleForUserLambda: (String) -> RoomMemberRole = { lambdaError() }, private val roomInfo: RoomInfo = aRustRoomInfo(id = roomId.value), -) : Room(NoPointer) { +) : Room(NoHandle) { override fun id(): String { return roomId.value } @@ -47,7 +48,7 @@ class FakeFfiRoom( return roomInfo } - override suspend fun latestEvent(): EventTimelineItem? { + override suspend fun latestEvent(): LatestEventValue { return latestEventLambda() } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomDirectorySearch.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomDirectorySearch.kt index b090262e31..f941b325c3 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomDirectorySearch.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomDirectorySearch.kt @@ -1,14 +1,15 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.fixtures.fakes import io.element.android.tests.testutils.simulateLongTask -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.RoomDirectorySearch import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntriesListener import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntryUpdate @@ -16,7 +17,7 @@ import org.matrix.rustcomponents.sdk.TaskHandle class FakeFfiRoomDirectorySearch( var isAtLastPage: Boolean = false, -) : RoomDirectorySearch(NoPointer) { +) : RoomDirectorySearch(NoHandle) { override suspend fun isAtLastPage(): Boolean { return isAtLastPage } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomList.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomList.kt index d51a742368..f3d71208e7 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomList.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomList.kt @@ -1,13 +1,14 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.fixtures.fakes -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.RoomList -class FakeFfiRoomList : RoomList(NoPointer) +class FakeFfiRoomList : RoomList(NoHandle) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomListService.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomListService.kt index 604f5289a0..5f6519402e 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomListService.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomListService.kt @@ -1,13 +1,14 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.fixtures.fakes -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.RoomList import org.matrix.rustcomponents.sdk.RoomListService import org.matrix.rustcomponents.sdk.RoomListServiceStateListener @@ -15,7 +16,7 @@ import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicator import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicatorListener import org.matrix.rustcomponents.sdk.TaskHandle -class FakeFfiRoomListService : RoomListService(NoPointer) { +class FakeFfiRoomListService : RoomListService(NoHandle) { override suspend fun allRooms(): RoomList { return FakeFfiRoomList() } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomMembersIterator.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomMembersIterator.kt index 28ee4791e5..06dc79365f 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomMembersIterator.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomMembersIterator.kt @@ -1,19 +1,20 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.fixtures.fakes -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.RoomMember import org.matrix.rustcomponents.sdk.RoomMembersIterator class FakeFfiRoomMembersIterator( private var members: List? = null -) : RoomMembersIterator(NoPointer) { +) : RoomMembersIterator(NoHandle) { override fun len(): UInt { return members?.size?.toUInt() ?: 0u } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomPowerLevels.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomPowerLevels.kt index 32e7dc891d..c47c4406b6 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomPowerLevels.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiRoomPowerLevels.kt @@ -1,20 +1,21 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.fixtures.fakes -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.RoomPowerLevels import org.matrix.rustcomponents.sdk.RoomPowerLevelsValues class FakeFfiRoomPowerLevels( private val values: RoomPowerLevelsValues = defaultFfiRoomPowerLevelValues(), private val users: Map = emptyMap(), -) : RoomPowerLevels(NoPointer) { +) : RoomPowerLevels(NoHandle) { override fun values(): RoomPowerLevelsValues = values override fun userPowerLevels(): Map = users } @@ -24,10 +25,11 @@ fun defaultFfiRoomPowerLevelValues() = RoomPowerLevelsValues( invite = 0, kick = 50, eventsDefault = 0, + stateDefault = 50, redact = 50, - roomName = 100, - roomAvatar = 100, - roomTopic = 100, - stateDefault = 0, + roomName = 50, + roomAvatar = 50, + roomTopic = 50, + spaceChild = 50, usersDefault = 0, ) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSessionVerificationController.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSessionVerificationController.kt index 2ff1e1d6ac..c6aa5475af 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSessionVerificationController.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSessionVerificationController.kt @@ -1,16 +1,17 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.fixtures.fakes -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.SessionVerificationController import org.matrix.rustcomponents.sdk.SessionVerificationControllerDelegate -class FakeFfiSessionVerificationController : SessionVerificationController(NoPointer) { +class FakeFfiSessionVerificationController : SessionVerificationController(NoHandle) { override fun setDelegate(delegate: SessionVerificationControllerDelegate?) {} } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSpaceRoomList.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSpaceRoomList.kt index 171afd49fc..40c36271ea 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSpaceRoomList.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSpaceRoomList.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,7 @@ package io.element.android.libraries.matrix.impl.fixtures.fakes import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.simulateLongTask -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.SpaceListUpdate import org.matrix.rustcomponents.sdk.SpaceRoom import org.matrix.rustcomponents.sdk.SpaceRoomList @@ -22,7 +23,7 @@ class FakeFfiSpaceRoomList( private val paginateResult: () -> Unit = { lambdaError() }, private val paginationStateResult: () -> SpaceRoomListPaginationState = { lambdaError() }, private val roomsResult: () -> List = { lambdaError() }, -) : SpaceRoomList(NoPointer) { +) : SpaceRoomList(NoHandle) { private var spaceRoomListPaginationStateListener: SpaceRoomListPaginationStateListener? = null private var spaceRoomListEntriesListener: SpaceRoomListEntriesListener? = null diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSpaceService.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSpaceService.kt index 3dae78ae1b..ca9ec75a24 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSpaceService.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSpaceService.kt @@ -1,13 +1,14 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.fixtures.fakes -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.SpaceService -class FakeFfiSpaceService : SpaceService(NoPointer) +class FakeFfiSpaceService : SpaceService(NoHandle) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSyncService.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSyncService.kt index 5d32139c8e..d4d87d0ae8 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSyncService.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSyncService.kt @@ -1,13 +1,14 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.fixtures.fakes -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.RoomListService import org.matrix.rustcomponents.sdk.SyncService import org.matrix.rustcomponents.sdk.SyncServiceStateObserver @@ -15,7 +16,7 @@ import org.matrix.rustcomponents.sdk.TaskHandle class FakeFfiSyncService( private val roomListService: RoomListService = FakeFfiRoomListService(), -) : SyncService(NoPointer) { +) : SyncService(NoHandle) { override fun roomListService(): RoomListService = roomListService override fun state(listener: SyncServiceStateObserver): TaskHandle { return FakeFfiTaskHandle() diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSyncServiceBuilder.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSyncServiceBuilder.kt index f423d0295b..2cb67f2519 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSyncServiceBuilder.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiSyncServiceBuilder.kt @@ -1,17 +1,18 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.fixtures.fakes -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.SyncService import org.matrix.rustcomponents.sdk.SyncServiceBuilder -class FakeFfiSyncServiceBuilder : SyncServiceBuilder(NoPointer) { +class FakeFfiSyncServiceBuilder : SyncServiceBuilder(NoHandle) { override fun withOfflineMode(): SyncServiceBuilder = this override fun withSharePos(enable: Boolean): SyncServiceBuilder = this override suspend fun finish(): SyncService = FakeFfiSyncService() diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTaskHandle.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTaskHandle.kt index 66c51017df..24d7884547 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTaskHandle.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTaskHandle.kt @@ -1,16 +1,17 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.fixtures.fakes -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.TaskHandle -class FakeFfiTaskHandle : TaskHandle(NoPointer) { +class FakeFfiTaskHandle : TaskHandle(NoHandle) { override fun cancel() = Unit override fun destroy() = Unit } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTimeline.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTimeline.kt index 09d79f8790..d45330bd3f 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTimeline.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTimeline.kt @@ -1,13 +1,14 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.fixtures.fakes -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.PaginationStatusListener import org.matrix.rustcomponents.sdk.TaskHandle import org.matrix.rustcomponents.sdk.Timeline @@ -15,7 +16,7 @@ import org.matrix.rustcomponents.sdk.TimelineDiff import org.matrix.rustcomponents.sdk.TimelineListener import uniffi.matrix_sdk.RoomPaginationStatus -class FakeFfiTimeline : Timeline(NoPointer) { +class FakeFfiTimeline : Timeline(NoHandle) { private var listener: TimelineListener? = null override suspend fun addListener(listener: TimelineListener): TaskHandle { this.listener = listener diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTimelineEvent.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTimelineEvent.kt index 41eb9c798e..80eec5efe7 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTimelineEvent.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTimelineEvent.kt @@ -1,25 +1,26 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.fixtures.fakes -import io.element.android.libraries.matrix.impl.fixtures.factories.aRustTimelineEventTypeMessageLike +import io.element.android.libraries.matrix.impl.fixtures.factories.aRustTimelineEventContentMessageLike import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.TimelineEvent -import org.matrix.rustcomponents.sdk.TimelineEventType +import org.matrix.rustcomponents.sdk.TimelineEventContent open class FakeFfiTimelineEvent( val timestamp: ULong = A_FAKE_TIMESTAMP.toULong(), - val timelineEventType: TimelineEventType = aRustTimelineEventTypeMessageLike(), + val timelineEventContent: TimelineEventContent = aRustTimelineEventContentMessageLike(), val senderId: String = A_USER_ID_2.value, -) : TimelineEvent(NoPointer) { +) : TimelineEvent(NoHandle) { override fun timestamp(): ULong = timestamp - override fun eventType(): TimelineEventType = timelineEventType + override fun content(): TimelineEventContent = timelineEventContent override fun senderId(): String = senderId } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTimelineEventTypeFilter.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTimelineEventTypeFilter.kt index dcab0bba6e..067567c4e9 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTimelineEventTypeFilter.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTimelineEventTypeFilter.kt @@ -1,13 +1,14 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.fixtures.fakes -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.TimelineEventTypeFilter -class FakeFfiTimelineEventTypeFilter : TimelineEventTypeFilter(NoPointer) +class FakeFfiTimelineEventTypeFilter : TimelineEventTypeFilter(NoHandle) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTimelineItem.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTimelineItem.kt index a3f7a18c21..eb7f8a1462 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTimelineItem.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiTimelineItem.kt @@ -1,21 +1,22 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.fixtures.fakes import org.matrix.rustcomponents.sdk.EventTimelineItem -import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.NoHandle import org.matrix.rustcomponents.sdk.TimelineItem import org.matrix.rustcomponents.sdk.TimelineUniqueId import org.matrix.rustcomponents.sdk.VirtualTimelineItem class FakeFfiTimelineItem( private val asEventResult: EventTimelineItem? = null, -) : TimelineItem(NoPointer) { +) : TimelineItem(NoHandle) { override fun asEvent(): EventTimelineItem? = asEventResult override fun asVirtual(): VirtualTimelineItem? = null override fun fmtDebug(): String = "fmtDebug" diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultPassphraseGeneratorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultPassphraseGeneratorTest.kt index 2426aa6c31..465d920074 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultPassphraseGeneratorTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/keys/DefaultPassphraseGeneratorTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/FakeQrCodeDataParser.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/FakeQrCodeDataParser.kt new file mode 100644 index 0000000000..254258fcf7 --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/FakeQrCodeDataParser.kt @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.linknewdevice + +import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiQrCodeData +import org.matrix.rustcomponents.sdk.QrCodeData + +class FakeQrCodeDataParser : QrCodeDataParser { + override fun parse(data: ByteArray): QrCodeData { + return FakeFfiQrCodeData() + } +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustCheckCodeSenderTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustCheckCodeSenderTest.kt new file mode 100644 index 0000000000..5fb4698976 --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustCheckCodeSenderTest.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.linknewdevice + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiCheckCodeSender +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class RustCheckCodeSenderTest { + @Test + fun `send invokes the Ffi object`() = runTest { + val sendResult = lambdaRecorder { } + val sut = RustCheckCodeSender( + inner = FakeFfiCheckCodeSender( + sendResult = sendResult, + ), + sessionDispatcher = StandardTestDispatcher(testScheduler), + ) + sut.send(1.toUByte()) + sendResult.assertions().isCalledOnce().with(value(1.toUByte())) + } + + @Test + fun `validate always returns true for now`() = runTest { + val sut = RustCheckCodeSender( + inner = FakeFfiCheckCodeSender(), + sessionDispatcher = StandardTestDispatcher(testScheduler), + ) + val result = sut.validate(1.toUByte()) + assertThat(result).isTrue() + } +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkDesktopHandlerTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkDesktopHandlerTest.kt new file mode 100644 index 0000000000..a180e4d515 --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkDesktopHandlerTest.kt @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package io.element.android.libraries.matrix.impl.linknewdevice + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.linknewdevice.ErrorType +import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopStep +import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiGrantLoginWithQrCodeHandler +import io.element.android.libraries.matrix.test.QR_CODE_DATA +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.matrix.rustcomponents.sdk.GrantQrLoginProgress +import org.matrix.rustcomponents.sdk.HumanQrGrantLoginException +import org.matrix.rustcomponents.sdk.QrCodeDecodeException + +class RustLinkDesktopHandlerTest { + @Test + fun `handleScannedQrCode function works as expected`() = runTest { + val handler = FakeFfiGrantLoginWithQrCodeHandler() + val sut = createRustLinkDesktopHandler( + handler, + ) + sut.linkDesktopStep.test { + val initialItem = awaitItem() + assertThat(initialItem).isEqualTo(LinkDesktopStep.Uninitialized) + backgroundScope.launch { + sut.handleScannedQrCode(QR_CODE_DATA) + } + runCurrent() + // progress from the handler is mapped and emitted + listOf( + GrantQrLoginProgress.Starting to LinkDesktopStep.Starting, + GrantQrLoginProgress.SyncingSecrets to LinkDesktopStep.SyncingSecrets, + GrantQrLoginProgress.WaitingForAuth("aVerificationUri") + to LinkDesktopStep.WaitingForAuth("aVerificationUri"), + GrantQrLoginProgress.EstablishingSecureChannel(1.toUByte(), "1") + to LinkDesktopStep.EstablishingSecureChannel(1.toUByte(), "1"), + GrantQrLoginProgress.Done to LinkDesktopStep.Done, + ).forEach { (progress, expectedStep) -> + handler.emitScanProgress(progress) + assertThat(awaitItem()).isEqualTo(expectedStep) + } + } + } + + @Test + fun `when handleScannedQrCode throws QrCodeDecodeException, the handler emits error step`() = runTest { + val handler = FakeFfiGrantLoginWithQrCodeHandler( + scanResult = { throw QrCodeDecodeException.Crypto("Scan failed") } + ) + val sut = createRustLinkDesktopHandler( + handler, + ) + sut.linkDesktopStep.test { + val initialItem = awaitItem() + assertThat(initialItem).isEqualTo(LinkDesktopStep.Uninitialized) + backgroundScope.launch { + sut.handleScannedQrCode(QR_CODE_DATA) + } + runCurrent() + val errorState = awaitItem() + assertThat(errorState).isInstanceOf(LinkDesktopStep.InvalidQrCode::class.java) + } + } + + @Test + fun `when handleScannedQrCode throws HumanQrGrantLoginException, the handler emits error step`() = runTest { + val handler = FakeFfiGrantLoginWithQrCodeHandler( + scanResult = { throw HumanQrGrantLoginException.InvalidCheckCode("Invalid check code") } + ) + val sut = createRustLinkDesktopHandler( + handler, + ) + sut.linkDesktopStep.test { + val initialItem = awaitItem() + assertThat(initialItem).isEqualTo(LinkDesktopStep.Uninitialized) + backgroundScope.launch { + sut.handleScannedQrCode(QR_CODE_DATA) + } + runCurrent() + val errorState = awaitItem() + assertThat(errorState).isInstanceOf(LinkDesktopStep.Error::class.java) + val errorType = (errorState as LinkDesktopStep.Error).errorType + assertThat(errorType).isInstanceOf(ErrorType.InvalidCheckCode::class.java) + } + } + + private fun TestScope.createRustLinkDesktopHandler( + handler: FakeFfiGrantLoginWithQrCodeHandler = FakeFfiGrantLoginWithQrCodeHandler(), + ) = RustLinkDesktopHandler( + inner = handler, + sessionCoroutineScope = backgroundScope, + sessionDispatcher = StandardTestDispatcher(testScheduler), + qrCodeDataParser = FakeQrCodeDataParser(), + ) +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkMobileHandlerTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkMobileHandlerTest.kt new file mode 100644 index 0000000000..aa13996e8a --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/linknewdevice/RustLinkMobileHandlerTest.kt @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package io.element.android.libraries.matrix.impl.linknewdevice + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.linknewdevice.ErrorType +import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileStep +import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiCheckCodeSender +import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiGrantLoginWithQrCodeHandler +import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiQrCodeData +import io.element.android.libraries.matrix.test.QR_CODE_DATA_RECIPROCATE +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.matrix.rustcomponents.sdk.GrantGeneratedQrLoginProgress +import org.matrix.rustcomponents.sdk.HumanQrGrantLoginException + +class RustLinkMobileHandlerTest { + @Test + fun `start function works as expected`() = runTest { + val handler = FakeFfiGrantLoginWithQrCodeHandler() + val sut = createRustLinkMobileHandler( + handler, + ) + sut.linkMobileStep.test { + val initialItem = awaitItem() + assertThat(initialItem).isEqualTo(LinkMobileStep.Uninitialized) + backgroundScope.launch { + sut.start() + } + runCurrent() + // progress from the handler is mapped and emitted + listOf( + GrantGeneratedQrLoginProgress.Starting to LinkMobileStep.Starting::class.java, + GrantGeneratedQrLoginProgress.SyncingSecrets to LinkMobileStep.SyncingSecrets::class.java, + GrantGeneratedQrLoginProgress.WaitingForAuth("aVerificationUri") + to LinkMobileStep.WaitingForAuth::class.java, + GrantGeneratedQrLoginProgress.QrScanned(FakeFfiCheckCodeSender()) + to LinkMobileStep.QrScanned::class.java, + GrantGeneratedQrLoginProgress.QrReady(FakeFfiQrCodeData(toBytesResult = { QR_CODE_DATA_RECIPROCATE })) + to LinkMobileStep.QrReady::class.java, + GrantGeneratedQrLoginProgress.Done to LinkMobileStep.Done::class.java, + ).forEach { (progress, expectedStepClass) -> + handler.emitGenerateProgress(progress) + assertThat(awaitItem()).isInstanceOf(expectedStepClass) + } + } + } + + @Test + fun `when start throws HumanQrGrantLoginException, the handler emits error step`() = runTest { + val handler = FakeFfiGrantLoginWithQrCodeHandler( + generateResult = { throw HumanQrGrantLoginException.NotFound("Timeout") } + ) + val sut = createRustLinkMobileHandler( + handler, + ) + sut.linkMobileStep.test { + val initialItem = awaitItem() + assertThat(initialItem).isEqualTo(LinkMobileStep.Uninitialized) + backgroundScope.launch { + sut.start() + } + runCurrent() + val errorState = awaitItem() + assertThat(errorState).isInstanceOf(LinkMobileStep.Error::class.java) + val errorType = (errorState as LinkMobileStep.Error).errorType + assertThat(errorType).isInstanceOf(ErrorType.NotFound::class.java) + } + } + + private fun TestScope.createRustLinkMobileHandler( + handler: FakeFfiGrantLoginWithQrCodeHandler = FakeFfiGrantLoginWithQrCodeHandler(), + ) = RustLinkMobileHandler( + inner = handler, + sessionCoroutineScope = backgroundScope, + sessionDispatcher = StandardTestDispatcher(testScheduler), + ) +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/mapper/SessionKtTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/mapper/SessionKtTest.kt index 46a4d35c37..e5fc8b154f 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/mapper/SessionKtTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/mapper/SessionKtTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,7 +19,6 @@ import io.element.android.libraries.matrix.test.A_SECRET import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.sessionstorage.api.LoginType import org.junit.Test -import org.matrix.rustcomponents.sdk.SlidingSyncVersion import java.io.File class SessionKtTest { @@ -37,7 +37,6 @@ class SessionKtTest { assertThat(result.homeserverUrl).isEqualTo(A_HOMESERVER_URL) assertThat(result.isTokenValid).isTrue() assertThat(result.oidcData).isNull() - assertThat(result.slidingSyncProxy).isNull() assertThat(result.loginType).isEqualTo(LoginType.PASSWORD) assertThat(result.loginTimestamp).isNotNull() assertThat(result.passphrase).isEqualTo(A_SECRET) @@ -69,20 +68,6 @@ class SessionKtTest { assertThat(result.homeserverUrl).isEqualTo(A_HOMESERVER_URL_2) } - @Test - fun `toSessionData copy the sliding sync url if present`() { - val result = aRustSession( - proxy = SlidingSyncVersion.NATIVE - ).toSessionData( - isTokenValid = true, - loginType = LoginType.PASSWORD, - passphrase = A_SECRET, - sessionPaths = SessionPaths(File("/a/file"), File("/a/cache")), - homeserverUrl = A_HOMESERVER_URL_2, - ) - assertThat(result.slidingSyncProxy).isNull() - } - @Test fun `ExternalSession toSessionData compute the expected result`() { val result = anExternalSession().toSessionData( @@ -98,7 +83,6 @@ class SessionKtTest { assertThat(result.homeserverUrl).isEqualTo(A_HOMESERVER_URL) assertThat(result.isTokenValid).isTrue() assertThat(result.oidcData).isNull() - assertThat(result.slidingSyncProxy).isNull() assertThat(result.loginType).isEqualTo(LoginType.PASSWORD) assertThat(result.loginTimestamp).isNotNull() assertThat(result.passphrase).isEqualTo(A_SECRET) @@ -124,12 +108,10 @@ private fun anExternalSession( accessToken: String = "accessToken", refreshToken: String? = null, homeserverUrl: String = A_HOMESERVER_URL, - slidingSyncProxy: String? = null, ) = ExternalSession( userId = userId, deviceId = deviceId, accessToken = accessToken, refreshToken = refreshToken, homeserverUrl = homeserverUrl, - slidingSyncProxy = slidingSyncProxy, ) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/mapper/UserProfileMapperTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/mapper/UserProfileMapperTest.kt index ea2d971056..61e5886356 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/mapper/UserProfileMapperTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/mapper/UserProfileMapperTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/mxc/MxcToolsTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/mxc/DefaultMxcToolsTest.kt similarity index 75% rename from libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/mxc/MxcToolsTest.kt rename to libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/mxc/DefaultMxcToolsTest.kt index 97242af95c..56863c51c4 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/mxc/MxcToolsTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/mxc/DefaultMxcToolsTest.kt @@ -1,19 +1,20 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.libraries.matrix.api.mxc +package io.element.android.libraries.matrix.impl.mxc import com.google.common.truth.Truth.assertThat import org.junit.Test -class MxcToolsTest { +class DefaultMxcToolsTest { @Test fun `mxcUri2FilePath returns extracted path`() { - val mxcTools = MxcTools() + val mxcTools = DefaultMxcTools() val mxcUri = "mxc://server.org/abc123" val filePath = mxcTools.mxcUri2FilePath(mxcUri) assertThat(filePath).isEqualTo("server.org/abc123") @@ -21,7 +22,7 @@ class MxcToolsTest { @Test fun `mxcUri2FilePath returns null for invalid data`() { - val mxcTools = MxcTools() + val mxcTools = DefaultMxcTools() assertThat(mxcTools.mxcUri2FilePath("")).isNull() assertThat(mxcTools.mxcUri2FilePath("mxc://server.org")).isNull() assertThat(mxcTools.mxcUri2FilePath("mxc://server.org/")).isNull() diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationServiceTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationServiceTest.kt index 2b6717910b..eecc37a529 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationServiceTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationServiceTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.exception.NotificationResolverException import io.element.android.libraries.matrix.api.notification.NotificationContent import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType -import io.element.android.libraries.matrix.impl.fixtures.factories.aRustBatchNotificationResult +import io.element.android.libraries.matrix.impl.fixtures.factories.aRustBatchNotificationResultOk import io.element.android.libraries.matrix.impl.fixtures.factories.aRustNotificationEventTimeline import io.element.android.libraries.matrix.impl.fixtures.factories.aRustNotificationItem import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiNotificationClient @@ -31,13 +32,13 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import org.matrix.rustcomponents.sdk.NotificationClient import org.matrix.rustcomponents.sdk.NotificationStatus -import org.matrix.rustcomponents.sdk.TimelineEventType +import org.matrix.rustcomponents.sdk.TimelineEventContent class RustNotificationServiceTest { @Test fun test() = runTest { val notificationClient = FakeFfiNotificationClient( - notificationItemResult = mapOf(AN_EVENT_ID.value to aRustBatchNotificationResult()), + notificationItemResult = mapOf(AN_EVENT_ID.value to aRustBatchNotificationResultOk()), ) val sut = createRustNotificationService( notificationClient = notificationClient, @@ -57,18 +58,18 @@ class RustNotificationServiceTest { @Test fun `test mapping invalid item only drops that item`() = runTest { - val error = IllegalStateException("This event type is not supported") + val error = IllegalStateException("This event content is not supported") val faultyEvent = object : FakeFfiTimelineEvent() { - override fun eventType(): TimelineEventType { + override fun content(): TimelineEventContent { throw error } } val notificationClient = FakeFfiNotificationClient( notificationItemResult = mapOf( - AN_EVENT_ID.value to aRustBatchNotificationResult( + AN_EVENT_ID.value to aRustBatchNotificationResultOk( notificationStatus = NotificationStatus.Event(aRustNotificationItem(aRustNotificationEventTimeline(faultyEvent))) ), - AN_EVENT_ID_2.value to aRustBatchNotificationResult() + AN_EVENT_ID_2.value to aRustBatchNotificationResultOk() ), ) val sut = createRustNotificationService( diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsServiceTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsServiceTest.kt index d2dd425132..73b7b888b2 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsServiceTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsServiceTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/oidc/AccountManagementActionKtTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/oidc/AccountManagementActionKtTest.kt index c80f265a22..8115465679 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/oidc/AccountManagementActionKtTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/oidc/AccountManagementActionKtTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverterTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverterTest.kt index a63dda184e..dcd2c4594c 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverterTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultMatrixToConverterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/poll/PollKindKtTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/poll/PollKindKtTest.kt index cad975da5c..ed475dc1b4 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/poll/PollKindKtTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/poll/PollKindKtTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/pushers/RustPushersServiceTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/pushers/RustPushersServiceTest.kt index 22e015a27c..61b2965cdd 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/pushers/RustPushersServiceTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/pushers/RustPushersServiceTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/FakeTimelineEventTypeFilterFactory.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/FakeTimelineEventTypeFilterFactory.kt index d7d800ac23..52da51a123 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/FakeTimelineEventTypeFilterFactory.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/FakeTimelineEventTypeFilterFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/MessageEventTypeKtTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/MessageEventTypeKtTest.kt index 02e503c56e..654989a393 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/MessageEventTypeKtTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/MessageEventTypeKtTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RoomInfoExtTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RoomInfoExtTest.kt index b52959712d..86a50c3926 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RoomInfoExtTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RoomInfoExtTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RoomInfoMapperTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RoomInfoMapperTest.kt index a95720baef..5c1c4a2aa3 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RoomInfoMapperTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RoomInfoMapperTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RoomTypeKtTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RoomTypeKtTest.kt index 23447f6879..a6920b6efc 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RoomTypeKtTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RoomTypeKtTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.room diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoomTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoomTest.kt index 50d6c348b5..6508c04112 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoomTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoomTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,7 +16,6 @@ import io.element.android.libraries.matrix.api.room.RoomInfo import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels -import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiRoom import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiRoomListService @@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.test.A_DEVICE_ID import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.room.aRoomInfo +import io.element.android.libraries.matrix.test.room.defaultRoomPowerLevelValues import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.collections.immutable.persistentMapOf import kotlinx.coroutines.flow.SharingStarted @@ -125,7 +126,7 @@ class RustBaseRoomTest { val rustBaseRoom = createRustBaseRoom( initialRoomInfo = aRoomInfo( roomPowerLevels = RoomPowerLevels( - values = RoomPowerLevelsValues(50, 50, 50, 50, 50, 50, 50, 50), + values = defaultRoomPowerLevelValues(), users = persistentMapOf(A_USER_ID to 100L) ) ), @@ -173,8 +174,7 @@ class RustBaseRoomTest { dispatchers = dispatchers, ), roomMembershipObserver = roomMembershipObserver, - // Not using backgroundScope here, but the test scope - sessionCoroutineScope = this, + sessionCoroutineScope = backgroundScope, roomInfoMapper = RoomInfoMapper(), initialRoomInfo = initialRoomInfo, ) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/StateEventTypeTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/StateEventTypeTest.kt index 7bee4fbc80..428bb7db7a 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/StateEventTypeTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/StateEventTypeTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,53 +16,55 @@ import org.matrix.rustcomponents.sdk.StateEventType as RustStateEventType class StateEventTypeTest { @Test fun `mapping Rust type should work`() { - assertThat(RustStateEventType.CALL_MEMBER.map()).isEqualTo(StateEventType.CALL_MEMBER) - assertThat(RustStateEventType.POLICY_RULE_ROOM.map()).isEqualTo(StateEventType.POLICY_RULE_ROOM) - assertThat(RustStateEventType.POLICY_RULE_SERVER.map()).isEqualTo(StateEventType.POLICY_RULE_SERVER) - assertThat(RustStateEventType.POLICY_RULE_USER.map()).isEqualTo(StateEventType.POLICY_RULE_USER) - assertThat(RustStateEventType.ROOM_ALIASES.map()).isEqualTo(StateEventType.ROOM_ALIASES) - assertThat(RustStateEventType.ROOM_AVATAR.map()).isEqualTo(StateEventType.ROOM_AVATAR) - assertThat(RustStateEventType.ROOM_CANONICAL_ALIAS.map()).isEqualTo(StateEventType.ROOM_CANONICAL_ALIAS) - assertThat(RustStateEventType.ROOM_CREATE.map()).isEqualTo(StateEventType.ROOM_CREATE) - assertThat(RustStateEventType.ROOM_ENCRYPTION.map()).isEqualTo(StateEventType.ROOM_ENCRYPTION) - assertThat(RustStateEventType.ROOM_GUEST_ACCESS.map()).isEqualTo(StateEventType.ROOM_GUEST_ACCESS) - assertThat(RustStateEventType.ROOM_HISTORY_VISIBILITY.map()).isEqualTo(StateEventType.ROOM_HISTORY_VISIBILITY) - assertThat(RustStateEventType.ROOM_JOIN_RULES.map()).isEqualTo(StateEventType.ROOM_JOIN_RULES) - assertThat(RustStateEventType.ROOM_MEMBER_EVENT.map()).isEqualTo(StateEventType.ROOM_MEMBER_EVENT) - assertThat(RustStateEventType.ROOM_NAME.map()).isEqualTo(StateEventType.ROOM_NAME) - assertThat(RustStateEventType.ROOM_PINNED_EVENTS.map()).isEqualTo(StateEventType.ROOM_PINNED_EVENTS) - assertThat(RustStateEventType.ROOM_POWER_LEVELS.map()).isEqualTo(StateEventType.ROOM_POWER_LEVELS) - assertThat(RustStateEventType.ROOM_SERVER_ACL.map()).isEqualTo(StateEventType.ROOM_SERVER_ACL) - assertThat(RustStateEventType.ROOM_THIRD_PARTY_INVITE.map()).isEqualTo(StateEventType.ROOM_THIRD_PARTY_INVITE) - assertThat(RustStateEventType.ROOM_TOMBSTONE.map()).isEqualTo(StateEventType.ROOM_TOMBSTONE) - assertThat(RustStateEventType.ROOM_TOPIC.map()).isEqualTo(StateEventType.ROOM_TOPIC) - assertThat(RustStateEventType.SPACE_CHILD.map()).isEqualTo(StateEventType.SPACE_CHILD) - assertThat(RustStateEventType.SPACE_PARENT.map()).isEqualTo(StateEventType.SPACE_PARENT) + assertThat(RustStateEventType.CallMember.map()).isEqualTo(StateEventType.CallMember) + assertThat(RustStateEventType.PolicyRuleRoom.map()).isEqualTo(StateEventType.PolicyRuleRoom) + assertThat(RustStateEventType.PolicyRuleServer.map()).isEqualTo(StateEventType.PolicyRuleServer) + assertThat(RustStateEventType.PolicyRuleUser.map()).isEqualTo(StateEventType.PolicyRuleUser) + assertThat(RustStateEventType.RoomAliases.map()).isEqualTo(StateEventType.RoomAliases) + assertThat(RustStateEventType.RoomAvatar.map()).isEqualTo(StateEventType.RoomAvatar) + assertThat(RustStateEventType.RoomCanonicalAlias.map()).isEqualTo(StateEventType.RoomCanonicalAlias) + assertThat(RustStateEventType.RoomCreate.map()).isEqualTo(StateEventType.RoomCreate) + assertThat(RustStateEventType.RoomEncryption.map()).isEqualTo(StateEventType.RoomEncryption) + assertThat(RustStateEventType.RoomGuestAccess.map()).isEqualTo(StateEventType.RoomGuestAccess) + assertThat(RustStateEventType.RoomHistoryVisibility.map()).isEqualTo(StateEventType.RoomHistoryVisibility) + assertThat(RustStateEventType.RoomJoinRules.map()).isEqualTo(StateEventType.RoomJoinRules) + assertThat(RustStateEventType.RoomMemberEvent.map()).isEqualTo(StateEventType.RoomMemberEvent) + assertThat(RustStateEventType.RoomName.map()).isEqualTo(StateEventType.RoomName) + assertThat(RustStateEventType.RoomPinnedEvents.map()).isEqualTo(StateEventType.RoomPinnedEvents) + assertThat(RustStateEventType.RoomPowerLevels.map()).isEqualTo(StateEventType.RoomPowerLevels) + assertThat(RustStateEventType.RoomServerAcl.map()).isEqualTo(StateEventType.RoomServerAcl) + assertThat(RustStateEventType.RoomThirdPartyInvite.map()).isEqualTo(StateEventType.RoomThirdPartyInvite) + assertThat(RustStateEventType.RoomTombstone.map()).isEqualTo(StateEventType.RoomTombstone) + assertThat(RustStateEventType.RoomTopic.map()).isEqualTo(StateEventType.RoomTopic) + assertThat(RustStateEventType.SpaceChild.map()).isEqualTo(StateEventType.SpaceChild) + assertThat(RustStateEventType.SpaceParent.map()).isEqualTo(StateEventType.SpaceParent) + assertThat(RustStateEventType.Custom("foo").map()).isEqualTo(StateEventType.Custom("foo")) } @Test fun `mapping Kotlin type should work`() { - assertThat(StateEventType.CALL_MEMBER.map()).isEqualTo(RustStateEventType.CALL_MEMBER) - assertThat(StateEventType.POLICY_RULE_ROOM.map()).isEqualTo(RustStateEventType.POLICY_RULE_ROOM) - assertThat(StateEventType.POLICY_RULE_SERVER.map()).isEqualTo(RustStateEventType.POLICY_RULE_SERVER) - assertThat(StateEventType.POLICY_RULE_USER.map()).isEqualTo(RustStateEventType.POLICY_RULE_USER) - assertThat(StateEventType.ROOM_ALIASES.map()).isEqualTo(RustStateEventType.ROOM_ALIASES) - assertThat(StateEventType.ROOM_AVATAR.map()).isEqualTo(RustStateEventType.ROOM_AVATAR) - assertThat(StateEventType.ROOM_CANONICAL_ALIAS.map()).isEqualTo(RustStateEventType.ROOM_CANONICAL_ALIAS) - assertThat(StateEventType.ROOM_CREATE.map()).isEqualTo(RustStateEventType.ROOM_CREATE) - assertThat(StateEventType.ROOM_ENCRYPTION.map()).isEqualTo(RustStateEventType.ROOM_ENCRYPTION) - assertThat(StateEventType.ROOM_GUEST_ACCESS.map()).isEqualTo(RustStateEventType.ROOM_GUEST_ACCESS) - assertThat(StateEventType.ROOM_HISTORY_VISIBILITY.map()).isEqualTo(RustStateEventType.ROOM_HISTORY_VISIBILITY) - assertThat(StateEventType.ROOM_JOIN_RULES.map()).isEqualTo(RustStateEventType.ROOM_JOIN_RULES) - assertThat(StateEventType.ROOM_MEMBER_EVENT.map()).isEqualTo(RustStateEventType.ROOM_MEMBER_EVENT) - assertThat(StateEventType.ROOM_NAME.map()).isEqualTo(RustStateEventType.ROOM_NAME) - assertThat(StateEventType.ROOM_PINNED_EVENTS.map()).isEqualTo(RustStateEventType.ROOM_PINNED_EVENTS) - assertThat(StateEventType.ROOM_POWER_LEVELS.map()).isEqualTo(RustStateEventType.ROOM_POWER_LEVELS) - assertThat(StateEventType.ROOM_SERVER_ACL.map()).isEqualTo(RustStateEventType.ROOM_SERVER_ACL) - assertThat(StateEventType.ROOM_THIRD_PARTY_INVITE.map()).isEqualTo(RustStateEventType.ROOM_THIRD_PARTY_INVITE) - assertThat(StateEventType.ROOM_TOMBSTONE.map()).isEqualTo(RustStateEventType.ROOM_TOMBSTONE) - assertThat(StateEventType.ROOM_TOPIC.map()).isEqualTo(RustStateEventType.ROOM_TOPIC) - assertThat(StateEventType.SPACE_CHILD.map()).isEqualTo(RustStateEventType.SPACE_CHILD) - assertThat(StateEventType.SPACE_PARENT.map()).isEqualTo(RustStateEventType.SPACE_PARENT) + assertThat(StateEventType.CallMember.map()).isEqualTo(RustStateEventType.CallMember) + assertThat(StateEventType.PolicyRuleRoom.map()).isEqualTo(RustStateEventType.PolicyRuleRoom) + assertThat(StateEventType.PolicyRuleServer.map()).isEqualTo(RustStateEventType.PolicyRuleServer) + assertThat(StateEventType.PolicyRuleUser.map()).isEqualTo(RustStateEventType.PolicyRuleUser) + assertThat(StateEventType.RoomAliases.map()).isEqualTo(RustStateEventType.RoomAliases) + assertThat(StateEventType.RoomAvatar.map()).isEqualTo(RustStateEventType.RoomAvatar) + assertThat(StateEventType.RoomCanonicalAlias.map()).isEqualTo(RustStateEventType.RoomCanonicalAlias) + assertThat(StateEventType.RoomCreate.map()).isEqualTo(RustStateEventType.RoomCreate) + assertThat(StateEventType.RoomEncryption.map()).isEqualTo(RustStateEventType.RoomEncryption) + assertThat(StateEventType.RoomGuestAccess.map()).isEqualTo(RustStateEventType.RoomGuestAccess) + assertThat(StateEventType.RoomHistoryVisibility.map()).isEqualTo(RustStateEventType.RoomHistoryVisibility) + assertThat(StateEventType.RoomJoinRules.map()).isEqualTo(RustStateEventType.RoomJoinRules) + assertThat(StateEventType.RoomMemberEvent.map()).isEqualTo(RustStateEventType.RoomMemberEvent) + assertThat(StateEventType.RoomName.map()).isEqualTo(RustStateEventType.RoomName) + assertThat(StateEventType.RoomPinnedEvents.map()).isEqualTo(RustStateEventType.RoomPinnedEvents) + assertThat(StateEventType.RoomPowerLevels.map()).isEqualTo(RustStateEventType.RoomPowerLevels) + assertThat(StateEventType.RoomServerAcl.map()).isEqualTo(RustStateEventType.RoomServerAcl) + assertThat(StateEventType.RoomThirdPartyInvite.map()).isEqualTo(RustStateEventType.RoomThirdPartyInvite) + assertThat(StateEventType.RoomTombstone.map()).isEqualTo(RustStateEventType.RoomTombstone) + assertThat(StateEventType.RoomTopic.map()).isEqualTo(RustStateEventType.RoomTopic) + assertThat(StateEventType.SpaceChild.map()).isEqualTo(RustStateEventType.SpaceChild) + assertThat(StateEventType.SpaceParent.map()).isEqualTo(RustStateEventType.SpaceParent) + assertThat(StateEventType.Custom("foo").map()).isEqualTo(RustStateEventType.Custom("foo")) } } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoomTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoomTest.kt index d33148931e..52aaa62b97 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoomTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoomTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/location/AssetTypeKtTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/location/AssetTypeKtTest.kt index 9fce31142b..9b12d12a04 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/location/AssetTypeKtTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/location/AssetTypeKtTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberListFetcherTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberListFetcherTest.kt index b11a3a6cd5..c1c436c405 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberListFetcherTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberListFetcherTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapperTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapperTest.kt index 6c469c6eb8..09bfafdaae 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapperTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/member/RoomMemberMapperTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/powerlevels/RoomPowerLevelsValuesMapperTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/powerlevels/RoomPowerLevelsValuesMapperTest.kt index a2c6fca7a1..f298da8b42 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/powerlevels/RoomPowerLevelsValuesMapperTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/powerlevels/RoomPowerLevelsValuesMapperTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -28,6 +29,7 @@ class RoomPowerLevelsValuesMapperTest { roomName = 8, roomAvatar = 9, roomTopic = 10, + spaceChild = 11, ) ) ).isEqualTo( @@ -35,11 +37,13 @@ class RoomPowerLevelsValuesMapperTest { ban = 1, invite = 2, kick = 3, - sendEvents = 5, redactEvents = 4, + eventsDefault = 5, + stateDefault = 6, roomName = 8, roomAvatar = 9, roomTopic = 10, + spaceChild = 11, ) ) } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/preview/RoomPreviewInfoMapperTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/preview/RoomPreviewInfoMapperTest.kt index 4b832799cc..f32e018abe 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/preview/RoomPreviewInfoMapperTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/preview/RoomPreviewInfoMapperTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapperTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapperTest.kt index 74fc0c8565..cf9223b416 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapperTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapperTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessorTest.kt index eddb53361c..c59d15618d 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessorTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessorTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustBaseRoomDirectoryListTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustBaseRoomDirectoryListTest.kt index 460ef37e99..cf3c746ec0 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustBaseRoomDirectoryListTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustBaseRoomDirectoryListTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -42,7 +43,7 @@ class RustBaseRoomDirectoryListTest { ) val initialItem = awaitItem() assertThat(initialItem).isEqualTo( - RoomDirectoryList.State( + RoomDirectoryList.SearchResult( hasMoreToLoad = true, items = listOf(mapper.map(aRustRoomDescription())) ) @@ -57,7 +58,7 @@ class RustBaseRoomDirectoryListTest { ) val nextItem = awaitItem() assertThat(nextItem).isEqualTo( - RoomDirectoryList.State( + RoomDirectoryList.SearchResult( hasMoreToLoad = false, items = listOf( mapper.map(aRustRoomDescription()), @@ -66,7 +67,7 @@ class RustBaseRoomDirectoryListTest { ) val finalItem = awaitItem() assertThat(finalItem).isEqualTo( - RoomDirectoryList.State( + RoomDirectoryList.SearchResult( hasMoreToLoad = false, items = listOf( mapper.map(aRustRoomDescription()), diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustBaseRoomDirectoryServiceTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustBaseRoomDirectoryServiceTest.kt index cc39e00c9f..ed97152311 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustBaseRoomDirectoryServiceTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustBaseRoomDirectoryServiceTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactoryTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactoryTest.kt index 2d97f2c589..ba45b310ef 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactoryTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFactoryTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,6 +10,7 @@ package io.element.android.libraries.matrix.impl.roomlist import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiRoomList import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiRoomListService +import io.element.android.services.analytics.test.FakeAnalyticsService import kotlinx.coroutines.test.runTest import org.junit.Test import kotlin.coroutines.EmptyCoroutineContext @@ -19,6 +21,7 @@ class RoomListFactoryTest { val sut = RoomListFactory( innerRoomListService = FakeFfiRoomListService(), sessionCoroutineScope = backgroundScope, + analyticsService = FakeAnalyticsService(), ) sut.createRoomList( pageSize = 10, diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilterTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilterTest.kt index 932f9ba6be..9568de33e4 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilterTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListFilterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessorTest.kt index 2b54463dea..022aa19ee7 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessorTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomSummaryListProcessorTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -21,6 +22,7 @@ import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test +import org.matrix.rustcomponents.sdk.LatestEventValue import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate class RoomSummaryListProcessorTest { @@ -172,13 +174,13 @@ class RoomSummaryListProcessorTest { private fun aRustRoom(roomId: RoomId = A_ROOM_ID) = FakeFfiRoom( roomId = roomId, - latestEventLambda = { null }, + latestEventLambda = { LatestEventValue.None } ) private fun TestScope.createProcessor() = RoomSummaryListProcessor( summaries, FakeFfiRoomListService(), coroutineContext = StandardTestDispatcher(testScheduler), - roomSummaryDetailsFactory = RoomSummaryFactory(), + roomSummaryFactory = RoomSummaryFactory(), ) } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustBaseRoomListServiceTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustBaseRoomListServiceTest.kt index 561c1f245f..88b57035e8 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustBaseRoomListServiceTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomlist/RustBaseRoomListServiceTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.roomlist @@ -11,6 +12,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiRoomListService import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber +import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher @@ -49,6 +51,7 @@ private fun TestScope.createRustRoomListService( roomListFactory = RoomListFactory( innerRoomListService = roomListService, sessionCoroutineScope = backgroundScope, + analyticsService = FakeAnalyticsService(), ), roomSyncSubscriber = RoomSyncSubscriber( roomListService = roomListService, diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/server/DefaultUserServerResolverTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/server/DefaultUserServerResolverTest.kt index 73eeb9e81f..152475a0e7 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/server/DefaultUserServerResolverTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/server/DefaultUserServerResolverTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/spaces/RoomSummaryListProcessorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/spaces/RoomSummaryListProcessorTest.kt index ae05c7b4e9..d21e259d42 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/spaces/RoomSummaryListProcessorTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/spaces/RoomSummaryListProcessorTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceRoomListTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceRoomListTest.kt index 7a494ae8c3..e966309c87 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceRoomListTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/spaces/RustSpaceRoomListTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/storage/FakeSqliteStoreBuilder.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/storage/FakeSqliteStoreBuilder.kt new file mode 100644 index 0000000000..2f12587f5a --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/storage/FakeSqliteStoreBuilder.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.storage + +import org.matrix.rustcomponents.sdk.ClientBuilder + +class FakeSqliteStoreBuilder : SqliteStoreBuilder { + override fun passphrase(passphrase: String?): SqliteStoreBuilder = this + + override fun setupClientBuilder(clientBuilder: ClientBuilder): ClientBuilder { + return clientBuilder + } +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/storage/FakeSqliteStoreBuilderProvider.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/storage/FakeSqliteStoreBuilderProvider.kt new file mode 100644 index 0000000000..b196604ca4 --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/storage/FakeSqliteStoreBuilderProvider.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.storage + +import io.element.android.libraries.matrix.impl.paths.SessionPaths + +class FakeSqliteStoreBuilderProvider : SqliteStoreBuilderProvider { + override fun provide(sessionPaths: SessionPaths): SqliteStoreBuilder { + return FakeSqliteStoreBuilder() + } +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/sync/AppStateMapperKtTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/sync/AppStateMapperKtTest.kt index 7310b98e2f..38462ca3aa 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/sync/AppStateMapperKtTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/sync/AppStateMapperKtTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessorTest.kt index b82c8dfbfb..308d2f66a6 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessorTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineDiffProcessorTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -156,10 +157,12 @@ class MatrixTimelineDiffProcessorTest { } internal fun TestScope.createMatrixTimelineDiffProcessor( - timelineItems: MutableSharedFlow>, -): MatrixTimelineDiffProcessor { + timelineItems: MutableSharedFlow> = MutableSharedFlow(), + membershipChangeEventReceivedFlow: MutableSharedFlow = MutableSharedFlow(), + syncedEventReceivedFlow: MutableSharedFlow = MutableSharedFlow(), + ): MatrixTimelineDiffProcessor { val timelineEventContentMapper = TimelineEventContentMapper() - val timelineItemMapper = MatrixTimelineItemMapper( + val timelineItemFactory = MatrixTimelineItemMapper( fetchDetailsForEvent = { _ -> Result.success(Unit) }, coroutineScope = this, virtualTimelineItemMapper = VirtualTimelineItemMapper(), @@ -169,6 +172,8 @@ internal fun TestScope.createMatrixTimelineDiffProcessor( ) return MatrixTimelineDiffProcessor( timelineItems = timelineItems, - timelineItemFactory = timelineItemMapper, + membershipChangeEventReceivedFlow = membershipChangeEventReceivedFlow, + syncedEventReceivedFlow = syncedEventReceivedFlow, + timelineItemMapper = timelineItemFactory, ) } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/ReceiptTypeMapperKtTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/ReceiptTypeMapperKtTest.kt index 79e4f149bc..792a52b253 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/ReceiptTypeMapperKtTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/ReceiptTypeMapperKtTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.impl.timeline diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimelineTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimelineTest.kt index 8bcd56978e..ab46e8aa5f 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimelineTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimelineTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @file:OptIn(ExperimentalCoroutinesApi::class) @@ -96,7 +97,6 @@ private fun TestScope.createRustTimeline( coroutineScope: CoroutineScope = backgroundScope, dispatcher: CoroutineDispatcher = testCoroutineDispatchers().io, roomContentForwarder: RoomContentForwarder = RoomContentForwarder(FakeFfiRoomListService()), - onNewSyncedEvent: () -> Unit = {}, ): RustTimeline { return RustTimeline( inner = inner, @@ -106,6 +106,5 @@ private fun TestScope.createRustTimeline( coroutineScope = coroutineScope, dispatcher = dispatcher, roomContentForwarder = roomContentForwarder, - onNewSyncedEvent = onNewSyncedEvent, ) } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriberTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriberTest.kt index 1df51dc5c2..9a03374d7e 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriberTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/TimelineItemsSubscriberTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,8 +14,6 @@ import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.impl.fixtures.factories.aRustEventTimelineItem import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiTimeline import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiTimelineItem -import io.element.android.tests.testutils.lambda.lambdaError -import io.element.android.tests.testutils.lambda.lambdaRecorder import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.test.StandardTestDispatcher @@ -33,9 +32,12 @@ class TimelineItemsSubscriberTest { val timelineItems: MutableSharedFlow> = MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE) val timeline = FakeFfiTimeline() + val diffProcessor = createMatrixTimelineDiffProcessor( + timelineItems = timelineItems, + ) val timelineItemsSubscriber = createTimelineItemsSubscriber( timeline = timeline, - timelineItems = timelineItems, + timelineDiffProcessor = diffProcessor, ) timelineItems.test { timelineItemsSubscriber.subscribeIfNeeded() @@ -53,9 +55,12 @@ class TimelineItemsSubscriberTest { val timelineItems: MutableSharedFlow> = MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE) val timeline = FakeFfiTimeline() + val diffProcessor = createMatrixTimelineDiffProcessor( + timelineItems = timelineItems, + ) val timelineItemsSubscriber = createTimelineItemsSubscriber( timeline = timeline, - timelineItems = timelineItems, + timelineDiffProcessor = diffProcessor, ) timelineItems.test { timelineItemsSubscriber.subscribeIfNeeded() @@ -69,15 +74,16 @@ class TimelineItemsSubscriberTest { } @Test - fun `when timeline emits an item with SYNC origin, the callback onNewSyncedEvent is invoked`() = runTest { + fun `when timeline emits an item with SYNC origin`() = runTest { val timelineItems: MutableSharedFlow> = MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE) val timeline = FakeFfiTimeline() - val onNewSyncedEventRecorder = lambdaRecorder { } + val diffProcessor = createMatrixTimelineDiffProcessor( + timelineItems = timelineItems, + ) val timelineItemsSubscriber = createTimelineItemsSubscriber( timeline = timeline, - timelineItems = timelineItems, - onNewSyncedEvent = onNewSyncedEventRecorder, + timelineDiffProcessor = diffProcessor, ) timelineItems.test { timelineItemsSubscriber.subscribeIfNeeded() @@ -96,7 +102,6 @@ class TimelineItemsSubscriberTest { assertThat(final).isNotEmpty() timelineItemsSubscriber.unsubscribeIfNeeded() } - onNewSyncedEventRecorder.assertions().isCalledOnce() } @Test @@ -111,14 +116,12 @@ class TimelineItemsSubscriberTest { private fun TestScope.createTimelineItemsSubscriber( timeline: Timeline = FakeFfiTimeline(), - timelineItems: MutableSharedFlow> = MutableSharedFlow(replay = 1, extraBufferCapacity = Int.MAX_VALUE), - onNewSyncedEvent: () -> Unit = { lambdaError() }, + timelineDiffProcessor: MatrixTimelineDiffProcessor = createMatrixTimelineDiffProcessor(), ): TimelineItemsSubscriber { return TimelineItemsSubscriber( timelineCoroutineScope = backgroundScope, dispatcher = StandardTestDispatcher(testScheduler), timeline = timeline, - timelineDiffProcessor = createMatrixTimelineDiffProcessor(timelineItems), - onNewSyncedEvent = onNewSyncedEvent, + timelineDiffProcessor = timelineDiffProcessor, ) } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/Fixtures.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/Fixtures.kt index 63deb34530..50f8637096 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/Fixtures.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/Fixtures.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/LastForwardIndicatorsPostProcessorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/LastForwardIndicatorsPostProcessorTest.kt index 0d2dd743f5..3d68210ea4 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/LastForwardIndicatorsPostProcessorTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/LastForwardIndicatorsPostProcessorTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/LoadingIndicatorsPostProcessorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/LoadingIndicatorsPostProcessorTest.kt index 881d718392..371d936975 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/LoadingIndicatorsPostProcessorTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/LoadingIndicatorsPostProcessorTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessorTest.kt index bfb3660fc6..dbeba39973 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessorTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/RoomBeginningPostProcessorTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapperTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapperTest.kt index 390b318f65..3926b93d12 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapperTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapperTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/util/SessionPathsProviderTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/util/SessionPathsProviderTest.kt index 43e5a98e50..8edc95cdc6 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/util/SessionPathsProviderTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/util/SessionPathsProviderTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/build.gradle.kts b/libraries/matrix/test/build.gradle.kts index 962277cca5..ccb1a37a25 100644 --- a/libraries/matrix/test/build.gradle.kts +++ b/libraries/matrix/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,6 +19,7 @@ dependencies { api(projects.libraries.matrix.api) api(libs.coroutines.core) implementation(libs.coroutines.test) + implementation(projects.libraries.matrix.impl) implementation(projects.services.analytics.api) implementation(projects.tests.testutils) implementation(libs.kotlinx.collections.immutable) diff --git a/libraries/matrix/test/src/main/AndroidManifest.xml b/libraries/matrix/test/src/main/AndroidManifest.xml index 5c0a832239..608f16cc36 100644 --- a/libraries/matrix/test/src/main/AndroidManifest.xml +++ b/libraries/matrix/test/src/main/AndroidManifest.xml @@ -1,7 +1,8 @@ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index f0e296ea5d..a740e71b0c 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -1,14 +1,17 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.test import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.analytics.SdkStoreSizes import io.element.android.libraries.matrix.api.core.DeviceId +import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomIdOrAlias @@ -16,6 +19,8 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters import io.element.android.libraries.matrix.api.encryption.EncryptionService +import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopHandler +import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileHandler import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.media.MediaPreviewService import io.element.android.libraries.matrix.api.notification.NotificationService @@ -93,6 +98,9 @@ class FakeMatrixClient( private val deactivateAccountResult: (String, Boolean) -> Result = { _, _ -> lambdaError() }, private val currentSlidingSyncVersionLambda: () -> Result = { lambdaError() }, private val ignoreUserResult: (UserId) -> Result = { lambdaError() }, + private val canLinkNewDeviceResult: () -> Result = { lambdaError() }, + private val createLinkMobileHandlerResult: () -> Result = { lambdaError() }, + private val createLinkDesktopHandlerResult: () -> Result = { lambdaError() }, private var unIgnoreUserResult: (UserId) -> Result = { Result.success(Unit) }, private val canReportRoomLambda: () -> Boolean = { false }, private val isLivekitRtcSupportedLambda: () -> Boolean = { false }, @@ -101,6 +109,9 @@ class FakeMatrixClient( private val getJoinedRoomIdsResult: () -> Result> = { Result.success(emptySet()) }, private val getRecentEmojisLambda: () -> Result> = { Result.success(emptyList()) }, private val addRecentEmojiLambda: (String) -> Result = { Result.success(Unit) }, + private val markRoomAsFullyReadResult: (RoomId, EventId) -> Result = { _, _ -> lambdaError() }, + private val performDatabaseVacuumLambda: () -> Result = { lambdaError() }, + private val getDatabaseSizesLambda: () -> Result = { lambdaError() }, ) : MatrixClient { var setDisplayNameCalled: Boolean = false private set @@ -180,6 +191,10 @@ class FakeMatrixClient( return 0 } + override suspend fun getDatabaseSizes(): Result { + return getDatabaseSizesLambda() + } + override suspend fun clearCache() = simulateLongTask { clearCacheLambda() } @@ -344,4 +359,24 @@ class FakeMatrixClient( override suspend fun getRecentEmojis(): Result> { return getRecentEmojisLambda() } + + override suspend fun markRoomAsFullyRead(roomId: RoomId, eventId: EventId): Result { + return markRoomAsFullyReadResult(roomId, eventId) + } + + override suspend fun performDatabaseVacuum(): Result { + return performDatabaseVacuumLambda() + } + + override suspend fun canLinkNewDevice(): Result = simulateLongTask { + return canLinkNewDeviceResult() + } + + override fun createLinkDesktopHandler(): Result { + return createLinkDesktopHandlerResult() + } + + override fun createLinkMobileHandler(): Result { + return createLinkMobileHandlerResult() + } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClientProvider.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClientProvider.kt index 700653e506..228ae94088 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClientProvider.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClientProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeSdkMetadata.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeSdkMetadata.kt index c94708caf3..d10e96745d 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeSdkMetadata.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeSdkMetadata.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 f05f6958c0..dcacadd975 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 @@ -1,13 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.test -import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails +import androidx.annotation.ColorInt import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomAlias @@ -78,8 +79,6 @@ const val AN_ACCOUNT_PROVIDER = "matrix.org" const val AN_ACCOUNT_PROVIDER_2 = "element.io" const val AN_ACCOUNT_PROVIDER_3 = "other.io" -val A_HOMESERVER = MatrixHomeServerDetails(A_HOMESERVER_URL, supportsPasswordLogin = true, supportsOidcLogin = false) -val A_HOMESERVER_OIDC = MatrixHomeServerDetails(A_HOMESERVER_URL, supportsPasswordLogin = false, supportsOidcLogin = true) val A_ROOM_NOTIFICATION_MODE = RoomNotificationMode.MUTE const val AN_AVATAR_URL = "mxc://data" @@ -98,3 +97,34 @@ const val A_TIMESTAMP = 567L const val A_FORMATTED_DATE = "April 6, 1980 at 6:35 PM" const val A_LOGIN_HINT = "mxid:@alice:example.org" + +@ColorInt +const val A_COLOR_INT: Int = 0xFFFF0000.toInt() + +// From https://github.com/matrix-org/matrix-rust-sdk/blob/3a63838cdb50cde3d74da920186fbae0a2e6db37/crates/matrix-sdk-crypto/src/types/qr_login.rs#L275 +// Test vector for the QR code data, copied from the MSC. +@Suppress("ktlint:standard:argument-list-wrapping") +val QR_CODE_DATA = listOf( + 0x4D, 0x41, 0x54, 0x52, 0x49, 0x58, 0x02, 0x03, 0xd8, 0x86, 0x68, 0x6a, 0xb2, 0x19, 0x7b, + 0x78, 0x0e, 0x30, 0x0a, 0x9d, 0x4a, 0x21, 0x47, 0x48, 0x07, 0x00, 0xd7, 0x92, 0x9f, 0x39, + 0xab, 0x31, 0xb9, 0xe5, 0x14, 0x37, 0x02, 0x48, 0xed, 0x6b, 0x00, 0x47, 0x68, 0x74, 0x74, + 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x7a, 0x76, 0x6f, 0x75, 0x73, + 0x2e, 0x6c, 0x61, 0x62, 0x2e, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x64, 0x65, + 0x76, 0x2f, 0x65, 0x38, 0x64, 0x61, 0x36, 0x33, 0x35, 0x35, 0x2d, 0x35, 0x35, 0x30, 0x62, + 0x2d, 0x34, 0x61, 0x33, 0x32, 0x2d, 0x61, 0x31, 0x39, 0x33, 0x2d, 0x31, 0x36, 0x31, 0x39, + 0x64, 0x39, 0x38, 0x33, 0x30, 0x36, 0x36, 0x38, +).map { it.toByte() }.toByteArray() + +// Test vector for the QR code data, copied from the MSC, with the mode set to reciprocate. +@Suppress("ktlint:standard:argument-list-wrapping") +val QR_CODE_DATA_RECIPROCATE = listOf( + 0x4D, 0x41, 0x54, 0x52, 0x49, 0x58, 0x02, 0x04, 0xd8, 0x86, 0x68, 0x6a, 0xb2, 0x19, 0x7b, + 0x78, 0x0e, 0x30, 0x0a, 0x9d, 0x4a, 0x21, 0x47, 0x48, 0x07, 0x00, 0xd7, 0x92, 0x9f, 0x39, + 0xab, 0x31, 0xb9, 0xe5, 0x14, 0x37, 0x02, 0x48, 0xed, 0x6b, 0x00, 0x47, 0x68, 0x74, 0x74, + 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x7a, 0x76, 0x6f, 0x75, 0x73, + 0x2e, 0x6c, 0x61, 0x62, 0x2e, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x64, 0x65, + 0x76, 0x2f, 0x65, 0x38, 0x64, 0x61, 0x36, 0x33, 0x35, 0x35, 0x2d, 0x35, 0x35, 0x30, 0x62, + 0x2d, 0x34, 0x61, 0x33, 0x32, 0x2d, 0x61, 0x31, 0x39, 0x33, 0x2d, 0x31, 0x36, 0x31, 0x39, + 0x64, 0x39, 0x38, 0x33, 0x30, 0x36, 0x36, 0x38, 0x00, 0x0A, 0x6d, 0x61, 0x74, 0x72, 0x69, + 0x78, 0x2e, 0x6f, 0x72, 0x67, +).map { it.toByte() }.toByteArray() diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/analytics/FakeAnalyticsSdkManager.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/analytics/FakeAnalyticsSdkManager.kt new file mode 100644 index 0000000000..ab4fd48477 --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/analytics/FakeAnalyticsSdkManager.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.test.analytics + +import io.element.android.services.analytics.api.AnalyticsSdkManager +import io.element.android.services.analytics.api.AnalyticsSdkSpan +import io.element.android.services.analytics.api.NoopAnalyticsSdkSpan +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeAnalyticsSdkManager( + private val enableSdkAnalyticsLambda: ((Boolean) -> Unit) = { lambdaError() }, +) : AnalyticsSdkManager { + override fun enableSdkAnalytics(enabled: Boolean) { + enableSdkAnalyticsLambda(enabled) + } + + override fun startSpan(name: String, parentTraceId: String?): AnalyticsSdkSpan = NoopAnalyticsSdkSpan + override fun bridge(parentTraceId: String?): AnalyticsSdkSpan = NoopAnalyticsSdkSpan +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeHomeServerLoginCompatibilityChecker.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeHomeServerLoginCompatibilityChecker.kt new file mode 100644 index 0000000000..83c83e3012 --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeHomeServerLoginCompatibilityChecker.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.test.auth + +import io.element.android.libraries.matrix.api.auth.HomeServerLoginCompatibilityChecker + +class FakeHomeServerLoginCompatibilityChecker( + private val checkResult: (String) -> Result, +) : HomeServerLoginCompatibilityChecker { + override suspend fun check(url: String): Result { + return checkResult(url) + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeMatrixAuthenticationService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeMatrixAuthenticationService.kt index f1554df1d0..c4acccb55c 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeMatrixAuthenticationService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeMatrixAuthenticationService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -22,8 +23,6 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.simulateLongTask -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow val A_OIDC_DATA = OidcDetails(url = "a-url") @@ -31,13 +30,12 @@ class FakeMatrixAuthenticationService( var matrixClientResult: ((SessionId) -> Result)? = null, var loginWithQrCodeResult: (qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit) -> Result = lambdaRecorder Unit, Result> { _, _ -> Result.success(A_SESSION_ID) }, - private val importCreatedSessionLambda: (ExternalSession) -> Result = { lambdaError() } + private val importCreatedSessionLambda: (ExternalSession) -> Result = { lambdaError() }, + private val setHomeserverResult: (String) -> Result = { lambdaError() }, ) : MatrixAuthenticationService { - private val homeserver = MutableStateFlow(null) private var oidcError: Throwable? = null private var oidcCancelError: Throwable? = null private var loginError: Throwable? = null - private var changeServerError: Throwable? = null private var matrixClient: MatrixClient? = null private var onAuthenticationListener: ((MatrixClient) -> Unit)? = null @@ -53,16 +51,8 @@ class FakeMatrixAuthenticationService( } } - override fun getHomeserverDetails(): StateFlow { - return homeserver - } - - fun givenHomeserver(homeserver: MatrixHomeServerDetails) { - this.homeserver.value = homeserver - } - - override suspend fun setHomeserver(homeserver: String): Result = simulateLongTask { - changeServerError?.let { Result.failure(it) } ?: Result.success(Unit) + override suspend fun setHomeserver(homeserver: String): Result = simulateLongTask { + setHomeserverResult(homeserver) } override suspend fun login(username: String, password: String): Result = simulateLongTask { @@ -115,10 +105,6 @@ class FakeMatrixAuthenticationService( loginError = throwable } - fun givenChangeServerError(throwable: Throwable?) { - changeServerError = throwable - } - fun givenMatrixClient(matrixClient: MatrixClient) { this.matrixClient = matrixClient } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeOidcRedirectUrlProvider.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeOidcRedirectUrlProvider.kt index e8226009d4..47c9b0951d 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeOidcRedirectUrlProvider.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/FakeOidcRedirectUrlProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/MatrixHomeServerDetails.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/MatrixHomeServerDetails.kt new file mode 100644 index 0000000000..3b9573bcfc --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/MatrixHomeServerDetails.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.test.auth + +import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails +import io.element.android.libraries.matrix.test.A_HOMESERVER_URL + +fun aMatrixHomeServerDetails( + url: String = A_HOMESERVER_URL, + supportsPasswordLogin: Boolean = false, + supportsOidcLogin: Boolean = false, +) = MatrixHomeServerDetails( + url = url, + supportsPasswordLogin = supportsPasswordLogin, + supportsOidcLogin = supportsOidcLogin, +) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/qrlogin/FakeMatrixQrCodeLoginDataFactory.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/qrlogin/FakeMatrixQrCodeLoginDataFactory.kt index 344b567589..c601337ca3 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/qrlogin/FakeMatrixQrCodeLoginDataFactory.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/auth/qrlogin/FakeMatrixQrCodeLoginDataFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/core/BuildMeta.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/core/BuildMeta.kt index 73fd83889b..510e2bc535 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/core/BuildMeta.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/core/BuildMeta.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/core/FakeSendHandle.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/core/FakeSendHandle.kt index dcb148f77a..abbf5d360a 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/core/FakeSendHandle.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/core/FakeSendHandle.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt index b4199ff8e4..04e3779298 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeEncryptionService.kt @@ -1,12 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.test.encryption +import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.BackupUploadState @@ -24,7 +26,6 @@ import kotlinx.coroutines.flow.flowOf class FakeEncryptionService( var startIdentityResetLambda: () -> Result = { lambdaError() }, private val pinUserIdentityResult: (UserId) -> Result = { lambdaError() }, - private val isUserVerifiedResult: (UserId) -> Result = { lambdaError() }, private val withdrawVerificationResult: (UserId) -> Result = { lambdaError() }, private val getUserIdentityResult: (UserId) -> Result = { lambdaError() }, private val enableRecoveryLambda: (Boolean) -> Result = { lambdaError() }, @@ -34,7 +35,7 @@ class FakeEncryptionService( override val recoveryStateStateFlow: MutableStateFlow = MutableStateFlow(RecoveryState.UNKNOWN) override val enableRecoveryProgressStateFlow: MutableStateFlow = MutableStateFlow(EnableRecoveryProgress.Starting) override val isLastDevice: MutableStateFlow = MutableStateFlow(false) - override val hasDevicesToVerifyAgainst: MutableStateFlow = MutableStateFlow(true) + override val hasDevicesToVerifyAgainst: MutableStateFlow> = MutableStateFlow(AsyncData.Uninitialized) private var waitForBackupUploadSteadyStateFlow: Flow = flowOf() private var recoverFailure: Exception? = null @@ -84,7 +85,7 @@ class FakeEncryptionService( this.isLastDevice.value = isLastDevice } - fun emitHasDevicesToVerifyAgainst(hasDevicesToVerifyAgainst: Boolean) { + fun emitHasDevicesToVerifyAgainst(hasDevicesToVerifyAgainst: AsyncData) { this.hasDevicesToVerifyAgainst.value = hasDevicesToVerifyAgainst } @@ -137,11 +138,7 @@ class FakeEncryptionService( return withdrawVerificationResult(userId) } - override suspend fun isUserVerified(userId: UserId): Result = simulateLongTask { - isUserVerifiedResult(userId) - } - - override suspend fun getUserIdentity(userId: UserId): Result = simulateLongTask { + override suspend fun getUserIdentity(userId: UserId, fallbackToServer: Boolean): Result = simulateLongTask { return getUserIdentityResult(userId) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeIdentityResetHandle.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeIdentityResetHandle.kt index e3579adda9..06ffeb547c 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeIdentityResetHandle.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeIdentityResetHandle.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/linknewdevice/FakeCheckCodeSender.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/linknewdevice/FakeCheckCodeSender.kt new file mode 100644 index 0000000000..a0692b6f5c --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/linknewdevice/FakeCheckCodeSender.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.test.linknewdevice + +import io.element.android.libraries.matrix.api.linknewdevice.CheckCodeSender +import io.element.android.tests.testutils.lambda.lambdaError +import io.element.android.tests.testutils.simulateLongTask + +class FakeCheckCodeSender( + private val validateResult: (UByte) -> Boolean = { lambdaError() }, + private val sendResult: (UByte) -> Result = { lambdaError() }, +) : CheckCodeSender { + override suspend fun validate(code: UByte): Boolean = simulateLongTask { + validateResult(code) + } + + override suspend fun send(code: UByte): Result = simulateLongTask { + sendResult(code) + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/linknewdevice/FakeLinkDesktopHandler.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/linknewdevice/FakeLinkDesktopHandler.kt new file mode 100644 index 0000000000..0bf9dafd01 --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/linknewdevice/FakeLinkDesktopHandler.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.test.linknewdevice + +import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopHandler +import io.element.android.libraries.matrix.api.linknewdevice.LinkDesktopStep +import io.element.android.tests.testutils.lambda.lambdaError +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeLinkDesktopHandler( + private val handleScannedQrCodeResult: (ByteArray) -> Unit = { lambdaError() }, +) : LinkDesktopHandler { + private val mutableLinkDesktopStep: MutableStateFlow = MutableStateFlow(LinkDesktopStep.Uninitialized) + override val linkDesktopStep: StateFlow + get() = mutableLinkDesktopStep.asStateFlow() + + override suspend fun handleScannedQrCode(data: ByteArray) { + handleScannedQrCodeResult(data) + } + + suspend fun emitStep(step: LinkDesktopStep) { + mutableLinkDesktopStep.emit(step) + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/linknewdevice/FakeLinkMobileHandler.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/linknewdevice/FakeLinkMobileHandler.kt new file mode 100644 index 0000000000..de704ea2dd --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/linknewdevice/FakeLinkMobileHandler.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.test.linknewdevice + +import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileHandler +import io.element.android.libraries.matrix.api.linknewdevice.LinkMobileStep +import io.element.android.tests.testutils.lambda.lambdaError +import io.element.android.tests.testutils.simulateLongTask +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeLinkMobileHandler( + private val startResult: () -> Unit = { lambdaError() }, +) : LinkMobileHandler { + private val mutableLinkMobileStep: MutableStateFlow = MutableStateFlow(LinkMobileStep.Uninitialized) + override val linkMobileStep: StateFlow + get() = mutableLinkMobileStep.asStateFlow() + + override suspend fun start() = simulateLongTask { + startResult() + } + + suspend fun emitStep(step: LinkMobileStep) { + mutableLinkMobileStep.emit(step) + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMatrixMediaLoader.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMatrixMediaLoader.kt index 4e915432a5..3b2dc36603 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMatrixMediaLoader.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMatrixMediaLoader.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaFile.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaFile.kt index c2049862ed..88572c8e4c 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaFile.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaFile.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaPreviewService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaPreviewService.kt index d8e9114817..47ddb82357 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaPreviewService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaPreviewService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaUploadHandler.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaUploadHandler.kt index 5df91dd41f..63b184a81f 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaUploadHandler.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/FakeMediaUploadHandler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/MediaSource.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/MediaSource.kt index d7324e8b42..501749e5f4 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/MediaSource.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/media/MediaSource.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/mxc/FakeMxcTools.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/mxc/FakeMxcTools.kt new file mode 100644 index 0000000000..c348cd351c --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/mxc/FakeMxcTools.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.test.mxc + +import io.element.android.libraries.matrix.api.mxc.MxcTools +import io.element.android.libraries.matrix.impl.mxc.DefaultMxcTools + +class FakeMxcTools( + private val delegate: MxcTools = DefaultMxcTools() +) : MxcTools by delegate diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/FakeNotificationService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/FakeNotificationService.kt index 4e17265f7a..e09d71b069 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/FakeNotificationService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/FakeNotificationService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt index 2bfd54954b..b6b6cb66e9 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/NotificationData.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -20,6 +21,7 @@ import io.element.android.libraries.matrix.test.A_USER_NAME_2 fun aNotificationData( content: NotificationContent = NotificationContent.MessageLike.RoomEncrypted, isDirect: Boolean = false, + isSpace: Boolean = false, hasMention: Boolean = false, threadId: ThreadId? = null, timestamp: Long = A_TIMESTAMP, @@ -39,6 +41,7 @@ fun aNotificationData( roomDisplayName = roomDisplayName, isDirect = isDirect, isDm = false, + isSpace = isSpace, isEncrypted = false, isNoisy = false, timestamp = timestamp, diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt index c5c09981fc..564cd231b2 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notificationsettings/FakeNotificationSettingsService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,8 +12,8 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationSettings -import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_NOTIFICATION_MODE +import io.element.android.tests.testutils.lambda.lambdaError import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow @@ -23,6 +24,8 @@ class FakeNotificationSettingsService( initialEncryptedGroupDefaultMode: RoomNotificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, initialOneToOneDefaultMode: RoomNotificationMode = RoomNotificationMode.ALL_MESSAGES, initialEncryptedOneToOneDefaultMode: RoomNotificationMode = RoomNotificationMode.ALL_MESSAGES, + private val getRawPushRulesResult: () -> Result = { lambdaError() }, + private val getRoomsWithUserDefinedRulesResult: () -> Result> = { lambdaError() }, ) : NotificationSettingsService { private val notificationSettingsStateFlow = MutableStateFlow(Unit) private var defaultGroupRoomNotificationMode: RoomNotificationMode = initialGroupDefaultMode @@ -151,8 +154,8 @@ class FakeNotificationSettingsService( return Result.success(Unit) } - override suspend fun getRoomsWithUserDefinedRules(): Result> { - return Result.success(if (roomNotificationModeIsDefault) listOf() else listOf(A_ROOM_ID.value)) + override suspend fun getRoomsWithUserDefinedRules(): Result> { + return getRoomsWithUserDefinedRulesResult() } override suspend fun canHomeServerPushEncryptedEventsToDevice(): Result { @@ -178,4 +181,8 @@ class FakeNotificationSettingsService( fun givenCanHomeServerPushEncryptedEventsToDeviceResult(result: Result) { canHomeServerPushEncryptedEventsToDeviceResult = result } + + override suspend fun getRawPushRules(): Result { + return getRawPushRulesResult() + } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkBuilder.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkBuilder.kt index 598243ed70..461d59cf1d 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkBuilder.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkBuilder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkParser.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkParser.kt index c00ab8f113..65aa7f9333 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkParser.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/permalink/FakePermalinkParser.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/pushers/FakePushersService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/pushers/FakePushersService.kt index fafc1e9b6d..ec1efee542 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/pushers/FakePushersService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/pushers/FakePushersService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeBaseRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeBaseRoom.kt index 31309beecf..78765d1ec4 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeBaseRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeBaseRoom.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,18 +15,18 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.BaseRoom -import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.RoomInfo import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembersState -import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.draft.ComposerDraft +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.simulateLongTask import kotlinx.coroutines.CoroutineScope @@ -39,6 +40,7 @@ class FakeBaseRoom( override val sessionId: SessionId = A_SESSION_ID, override val roomId: RoomId = A_ROOM_ID, initialRoomInfo: RoomInfo = aRoomInfo(), + private val roomPermissions: RoomPermissions = FakeRoomPermissions(), override val roomCoroutineScope: CoroutineScope = TestScope(), private var roomPermalinkResult: () -> Result = { lambdaError() }, private var eventPermalinkResult: (EventId) -> Result = { lambdaError() }, @@ -47,16 +49,6 @@ class FakeBaseRoom( private val userRoleResult: () -> Result = { lambdaError() }, private val getUpdatedMemberResult: (UserId) -> Result = { lambdaError() }, private val joinRoomResult: () -> Result = { lambdaError() }, - private val canInviteResult: (UserId) -> Result = { lambdaError() }, - private val canKickResult: (UserId) -> Result = { lambdaError() }, - private val canBanResult: (UserId) -> Result = { lambdaError() }, - private val canRedactOwnResult: (UserId) -> Result = { lambdaError() }, - private val canRedactOtherResult: (UserId) -> Result = { lambdaError() }, - private val canSendStateResult: (UserId, StateEventType) -> Result = { _, _ -> lambdaError() }, - private val canUserSendMessageResult: (UserId, MessageEventType) -> Result = { _, _ -> lambdaError() }, - private val canUserTriggerRoomNotificationResult: (UserId) -> Result = { lambdaError() }, - private val canUserJoinCallResult: (UserId) -> Result = { lambdaError() }, - private val canUserPinUnpinResult: (UserId) -> Result = { lambdaError() }, private val setIsFavoriteResult: (Boolean) -> Result = { lambdaError() }, private val markAsReadResult: (ReceiptType) -> Result = { Result.success(Unit) }, private val powerLevelsResult: () -> Result = { lambdaError() }, @@ -128,6 +120,10 @@ class FakeBaseRoom( return userRoleResult() } + override suspend fun roomPermissions(): Result { + return Result.success(roomPermissions) + } + override suspend fun getPermalink(): Result { return roomPermalinkResult() } @@ -152,46 +148,6 @@ class FakeBaseRoom( return forgetResult() } - override suspend fun canUserBan(userId: UserId): Result { - return canBanResult(userId) - } - - override suspend fun canUserKick(userId: UserId): Result { - return canKickResult(userId) - } - - override suspend fun canUserInvite(userId: UserId): Result { - return canInviteResult(userId) - } - - override suspend fun canUserRedactOwn(userId: UserId): Result { - return canRedactOwnResult(userId) - } - - override suspend fun canUserRedactOther(userId: UserId): Result { - return canRedactOtherResult(userId) - } - - override suspend fun canUserSendState(userId: UserId, type: StateEventType): Result { - return canSendStateResult(userId, type) - } - - override suspend fun canUserSendMessage(userId: UserId, type: MessageEventType): Result { - return canUserSendMessageResult(userId, type) - } - - override suspend fun canUserTriggerRoomNotification(userId: UserId): Result { - return canUserTriggerRoomNotificationResult(userId) - } - - override suspend fun canUserJoinCall(userId: UserId): Result { - return canUserJoinCallResult(userId) - } - - override suspend fun canUserPinUnpin(userId: UserId): Result { - return canUserPinUnpinResult(userId) - } - override suspend fun setIsFavorite(isFavorite: Boolean): Result { return setIsFavoriteResult(isFavorite) } @@ -255,9 +211,11 @@ fun defaultRoomPowerLevelValues() = RoomPowerLevelsValues( ban = 50, invite = 0, kick = 50, - sendEvents = 0, + eventsDefault = 0, + stateDefault = 50, redactEvents = 50, - roomName = 100, - roomAvatar = 100, - roomTopic = 100 + roomName = 50, + roomAvatar = 50, + roomTopic = 50, + spaceChild = 50, ) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeJoinedRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeJoinedRoom.kt index 4681937b42..2d9694c909 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeJoinedRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeJoinedRoom.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeNotJoinedRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeNotJoinedRoom.kt index 7691aa58c6..6c1c7c2ea9 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeNotJoinedRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeNotJoinedRoom.kt @@ -1,13 +1,13 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.test.room -import androidx.compose.runtime.Immutable import io.element.android.libraries.matrix.api.room.BaseRoom import io.element.android.libraries.matrix.api.room.NotJoinedRoom import io.element.android.libraries.matrix.api.room.RoomMembershipDetails @@ -15,7 +15,6 @@ import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.simulateLongTask -@Immutable class FakeNotJoinedRoom( override val localRoom: BaseRoom? = null, override val previewInfo: RoomPreviewInfo = aRoomPreviewInfo(), diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/LatestEventValueFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/LatestEventValueFixture.kt new file mode 100644 index 0000000000..32d6cc927c --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/LatestEventValueFixture.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.test.room + +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.roomlist.LatestEventValue +import io.element.android.libraries.matrix.api.timeline.item.event.EventContent +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails +import io.element.android.libraries.matrix.test.A_USER_ID +import io.element.android.libraries.matrix.test.timeline.aMessageContent +import io.element.android.libraries.matrix.test.timeline.aProfileDetails + +fun aRemoteLatestEvent( + content: EventContent = aMessageContent(), + timestamp: Long = 0L, + isOwn: Boolean = false, + senderId: UserId = A_USER_ID, + senderProfile: ProfileDetails = aProfileDetails(), +): LatestEventValue.Remote { + return LatestEventValue.Remote( + timestamp = timestamp, + content = content, + senderId = senderId, + senderProfile = senderProfile, + isOwn = isOwn, + ) +} + +fun aLocalLatestEvent( + content: EventContent = aMessageContent(), + timestamp: Long = 0L, + isSending: Boolean = false, + senderId: UserId = A_USER_ID, + senderProfile: ProfileDetails = aProfileDetails(), +): LatestEventValue.Local { + return LatestEventValue.Local( + timestamp = timestamp, + content = content, + senderId = senderId, + senderProfile = senderProfile, + isSending = isSending, + ) +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomInfoFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomInfoFixture.kt index 6955942146..71ed8b83be 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomInfoFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomInfoFixture.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -52,7 +53,7 @@ fun aRoomInfo( notificationCount: Long = 0, userDefinedNotificationMode: RoomNotificationMode? = null, hasRoomCall: Boolean = false, - roomPowerLevels: RoomPowerLevels = RoomPowerLevels( + roomPowerLevels: RoomPowerLevels? = RoomPowerLevels( values = defaultRoomPowerLevelValues(), users = persistentMapOf(), ), diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomMemberFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomMemberFixture.kt index 9dc72247f0..f6bc0c5ec2 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomMemberFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomMemberFixture.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomPreviewInfoFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomPreviewInfoFixture.kt index 74a58354a5..6d1f6da928 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomPreviewInfoFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomPreviewInfoFixture.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt index c15d29eff8..8568e3c916 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,28 +18,24 @@ import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility import io.element.android.libraries.matrix.api.room.join.JoinRule -import io.element.android.libraries.matrix.api.room.message.RoomMessage import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom +import io.element.android.libraries.matrix.api.roomlist.LatestEventValue import io.element.android.libraries.matrix.api.roomlist.RoomSummary -import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem import io.element.android.libraries.matrix.api.user.MatrixUser -import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_NAME import io.element.android.libraries.matrix.test.A_ROOM_RAW_NAME import io.element.android.libraries.matrix.test.A_ROOM_TOPIC -import io.element.android.libraries.matrix.test.A_USER_ID -import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toImmutableList fun aRoomSummary( info: RoomInfo = aRoomInfo(), - lastMessage: RoomMessage? = aRoomMessage(), + latestEventValue: LatestEventValue = aRemoteLatestEvent(), ) = RoomSummary( info = info, - lastMessage = lastMessage, + latestEvent = latestEventValue, ) fun aRoomSummary( @@ -78,7 +75,7 @@ fun aRoomSummary( numUnreadNotifications: Long = 0, numUnreadMentions: Long = 0, historyVisibility: RoomHistoryVisibility = RoomHistoryVisibility.Joined, - lastMessage: RoomMessage? = aRoomMessage(), + latestEvent: LatestEventValue = aRemoteLatestEvent(), roomVersion: String? = "11", privilegedCreatorRole: Boolean = false, ) = RoomSummary( @@ -119,17 +116,5 @@ fun aRoomSummary( roomVersion = roomVersion, privilegedCreatorRole = privilegedCreatorRole, ), - lastMessage = lastMessage, -) - -fun aRoomMessage( - eventId: EventId = AN_EVENT_ID, - event: EventTimelineItem = anEventTimelineItem(), - userId: UserId = A_USER_ID, - timestamp: Long = 0L, -) = RoomMessage( - eventId = eventId, - event = event, - sender = userId, - originServerTs = timestamp, + latestEvent = latestEvent, ) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/alias/FakeRoomAliasHelper.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/alias/FakeRoomAliasHelper.kt index 7d31f7b03f..d2ba6afced 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/alias/FakeRoomAliasHelper.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/alias/FakeRoomAliasHelper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/join/FakeJoinRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/join/FakeJoinRoom.kt index 05438d1ef5..e2689304f4 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/join/FakeJoinRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/join/FakeJoinRoom.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/knock/FakeKnockRequest.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/knock/FakeKnockRequest.kt index 617804092c..9fd77063ae 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/knock/FakeKnockRequest.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/knock/FakeKnockRequest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/powerlevels/FakeRoomPermissions.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/powerlevels/FakeRoomPermissions.kt new file mode 100644 index 0000000000..b78dc9ba1b --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/powerlevels/FakeRoomPermissions.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.test.room.powerlevels + +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.room.MessageEventType +import io.element.android.libraries.matrix.api.room.StateEventType +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions + +data class FakeRoomPermissions( + private val canBan: Boolean = false, + private val canInvite: Boolean = false, + private val canKick: Boolean = false, + private val canPinUnpin: Boolean = false, + private val canRedactOther: Boolean = false, + private val canRedactOwn: Boolean = false, + private val canTriggerRoomNotification: Boolean = false, + private val canSendMessage: (MessageEventType) -> Boolean = { false }, + private val canSendState: (StateEventType) -> Boolean = { false }, + private val canUserBan: (UserId) -> Boolean = { false }, + private val canUserInvite: (UserId) -> Boolean = { false }, + private val canUserKick: (UserId) -> Boolean = { false }, + private val canUserPinUnpin: (UserId) -> Boolean = { false }, + private val canUserRedactOther: (UserId) -> Boolean = { false }, + private val canUserRedactOwn: (UserId) -> Boolean = { false }, + private val canUserTriggerRoomNotification: (UserId) -> Boolean = { false }, + private val canUserSendMessage: (UserId, MessageEventType) -> Boolean = { _, _ -> false }, + private val canUserSendState: (UserId, StateEventType) -> Boolean = { _, _ -> false }, +) : RoomPermissions { + override fun canOwnUserBan(): Boolean = canBan + override fun canOwnUserInvite(): Boolean = canInvite + override fun canOwnUserKick(): Boolean = canKick + override fun canOwnUserPinUnpin(): Boolean = canPinUnpin + override fun canOwnUserRedactOther(): Boolean = canRedactOther + override fun canOwnUserRedactOwn(): Boolean = canRedactOwn + override fun canOwnUserSendMessage(message: MessageEventType): Boolean = canSendMessage(message) + override fun canOwnUserSendState(stateEvent: StateEventType): Boolean = canSendState(stateEvent) + + override fun canOwnUserTriggerRoomNotification(): Boolean = canTriggerRoomNotification + override fun canUserBan(userId: UserId): Boolean = canUserBan(userId) + override fun canUserInvite(userId: UserId): Boolean = canUserInvite(userId) + override fun canUserKick(userId: UserId): Boolean = canUserKick(userId) + override fun canUserPinUnpin(userId: UserId): Boolean = canUserPinUnpin(userId) + override fun canUserRedactOther(userId: UserId): Boolean = canUserRedactOther(userId) + override fun canUserRedactOwn(userId: UserId): Boolean = canUserRedactOwn(userId) + override fun canUserSendMessage(userId: UserId, message: MessageEventType): Boolean = canUserSendMessage(userId, message) + override fun canUserSendState(userId: UserId, stateEvent: StateEventType): Boolean = canUserSendState(userId, stateEvent) + override fun canUserTriggerRoomNotification(userId: UserId): Boolean = canUserTriggerRoomNotification(userId) + + override fun close() { + // no-op for the fake + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryList.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryList.kt index 33c8bc83b5..78a664454f 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryList.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryList.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,7 +13,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow class FakeRoomDirectoryList( - override val state: Flow = emptyFlow(), + override val state: Flow = emptyFlow(), val filterLambda: (String?, Int, String?) -> Result = { _, _, _ -> Result.success(Unit) }, val loadMoreLambda: () -> Result = { Result.success(Unit) } ) : RoomDirectoryList { diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt index bc73dc4d0f..17634a2565 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/RoomDescriptionFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/RoomDescriptionFixture.kt index 4ea3465279..df52be34cf 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/RoomDescriptionFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/RoomDescriptionFixture.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt index dd1fcfe1ba..b184438c4e 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/FakeRoomListService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/SimplePagedRoomList.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/SimplePagedRoomList.kt index 6b5beca964..a63212c005 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/SimplePagedRoomList.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/SimplePagedRoomList.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeLeaveSpaceHandle.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeLeaveSpaceHandle.kt index b19d3f1344..83017d9d48 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeLeaveSpaceHandle.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeLeaveSpaceHandle.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeSpaceRoomList.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeSpaceRoomList.kt index 6eac3dd0f4..70d919dc21 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeSpaceRoomList.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeSpaceRoomList.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeSpaceService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeSpaceService.kt index d768b175d0..eaa36ee750 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeSpaceService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/spaces/FakeSpaceService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt index 5469e5b4aa..5bb85fa488 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt index 6ebd9f9f50..4451de6276 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -47,8 +48,17 @@ class FakeTimeline( ) ), override val membershipChangeEventReceived: Flow = MutableSharedFlow(), + override val onSyncedEventReceived: Flow = MutableSharedFlow(), private val cancelSendResult: (TransactionId) -> Result = { lambdaError() }, override val mode: Timeline.Mode = Timeline.Mode.Live, + private val markAsReadResult: (ReceiptType) -> Result = { lambdaError() }, + private val getLatestEventIdResult: () -> Result = { lambdaError() }, + var sendReadReceiptLambda: ( + eventId: EventId, + receiptType: ReceiptType, + ) -> Result = { _, _ -> + lambdaError() + } ) : Timeline { var sendMessageLambda: ( body: String, @@ -148,7 +158,7 @@ class FakeTimeline( imageInfo: ImageInfo, body: String?, formattedBody: String?, - inReplyToEventId: EventId??, + inReplyToEventId: EventId?, ) -> Result = { _, _, _, _, _, _ -> Result.success(FakeMediaUploadHandler()) } @@ -159,7 +169,7 @@ class FakeTimeline( imageInfo: ImageInfo, caption: String?, formattedCaption: String?, - inReplyToEventId: EventId??, + inReplyToEventId: EventId?, ): Result = simulateLongTask { sendImageLambda( file, @@ -177,7 +187,7 @@ class FakeTimeline( videoInfo: VideoInfo, body: String?, formattedBody: String?, - inReplyToEventId: EventId??, + inReplyToEventId: EventId?, ) -> Result = { _, _, _, _, _, _ -> Result.success(FakeMediaUploadHandler()) } @@ -188,7 +198,7 @@ class FakeTimeline( videoInfo: VideoInfo, caption: String?, formattedCaption: String?, - inReplyToEventId: EventId??, + inReplyToEventId: EventId?, ): Result = simulateLongTask { sendVideoLambda( file, @@ -205,7 +215,7 @@ class FakeTimeline( audioInfo: AudioInfo, caption: String?, formattedCaption: String?, - inReplyToEventId: EventId??, + inReplyToEventId: EventId?, ) -> Result = { _, _, _, _, _ -> Result.success(FakeMediaUploadHandler()) } @@ -215,7 +225,7 @@ class FakeTimeline( audioInfo: AudioInfo, caption: String?, formattedCaption: String?, - inReplyToEventId: EventId??, + inReplyToEventId: EventId?, ): Result = simulateLongTask { sendAudioLambda( file, @@ -231,7 +241,7 @@ class FakeTimeline( fileInfo: FileInfo, caption: String?, formattedCaption: String?, - inReplyToEventId: EventId??, + inReplyToEventId: EventId?, ) -> Result = { _, _, _, _, _ -> Result.success(FakeMediaUploadHandler()) } @@ -241,7 +251,7 @@ class FakeTimeline( fileInfo: FileInfo, caption: String?, formattedCaption: String?, - inReplyToEventId: EventId??, + inReplyToEventId: EventId?, ): Result = simulateLongTask { sendFileLambda( file, @@ -256,7 +266,7 @@ class FakeTimeline( file: File, audioInfo: AudioInfo, waveform: List, - inReplyToEventId: EventId??, + inReplyToEventId: EventId?, ) -> Result = { _, _, _, _ -> Result.success(FakeMediaUploadHandler()) } @@ -265,7 +275,7 @@ class FakeTimeline( file: File, audioInfo: AudioInfo, waveform: List, - inReplyToEventId: EventId??, + inReplyToEventId: EventId?, ): Result = simulateLongTask { sendVoiceMessageLambda( file, @@ -281,7 +291,7 @@ class FakeTimeline( description: String?, zoomLevel: Int?, assetType: AssetType?, - inReplyToEventId: EventId??, + inReplyToEventId: EventId?, ) -> Result = { _, _, _, _, _, _ -> lambdaError() } @@ -292,7 +302,7 @@ class FakeTimeline( description: String?, zoomLevel: Int?, assetType: AssetType?, - inReplyToEventId: EventId??, + inReplyToEventId: EventId?, ): Result = simulateLongTask { sendLocationLambda( body, @@ -397,18 +407,15 @@ class FakeTimeline( ) } - var sendReadReceiptLambda: ( - eventId: EventId, - receiptType: ReceiptType, - ) -> Result = { _, _ -> - lambdaError() - } - override suspend fun sendReadReceipt( eventId: EventId, receiptType: ReceiptType, ): Result = sendReadReceiptLambda(eventId, receiptType) + override suspend fun markAsRead(receiptType: ReceiptType): Result { + return markAsReadResult(receiptType) + } + var paginateLambda: (direction: Timeline.PaginationDirection) -> Result = { Result.success(false) } @@ -431,6 +438,10 @@ class FakeTimeline( return unpinEventLambda(eventId) } + override suspend fun getLatestEventId(): Result { + return getLatestEventIdResult() + } + var closeCounter = 0 private set diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimelineProvider.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimelineProvider.kt new file mode 100644 index 0000000000..b1632554d7 --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimelineProvider.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.test.timeline + +import io.element.android.libraries.matrix.api.timeline.Timeline +import io.element.android.libraries.matrix.api.timeline.TimelineProvider +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeTimelineProvider( + initialTimeline: Timeline? = null, +) : TimelineProvider { + private val timelineFlow = MutableStateFlow(initialTimeline) + + override fun activeTimelineFlow(): StateFlow { + return timelineFlow.asStateFlow() + } +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/LiveTimelineProvider.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/LiveTimelineProvider.kt index 1723b56490..60cee84ea9 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/LiveTimelineProvider.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/LiveTimelineProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt index b564c21b30..24c26a6734 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/TimelineFixture.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -26,7 +27,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield import io.element.android.libraries.matrix.api.timeline.item.event.MessageType import io.element.android.libraries.matrix.api.timeline.item.event.PollContent import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent -import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails import io.element.android.libraries.matrix.api.timeline.item.event.Receipt import io.element.android.libraries.matrix.api.timeline.item.event.SendHandleProvider import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent @@ -52,7 +53,7 @@ fun anEventTimelineItem( reactions: ImmutableList = persistentListOf(), receipts: ImmutableList = persistentListOf(), sender: UserId = A_USER_ID, - senderProfile: ProfileTimelineDetails = aProfileTimelineDetails(), + senderProfile: ProfileDetails = aProfileDetails(), timestamp: Long = 0L, content: EventContent = aProfileChangeMessageContent(), debugInfoProvider: TimelineItemDebugInfoProvider = TimelineItemDebugInfoProvider { aTimelineItemDebugInfo() }, @@ -78,11 +79,11 @@ fun anEventTimelineItem( sendHandleProvider = sendHandleProvider, ) -fun aProfileTimelineDetails( +fun aProfileDetails( displayName: String? = A_USER_NAME, displayNameAmbiguous: Boolean = false, avatarUrl: String? = null -): ProfileTimelineDetails = ProfileTimelineDetails.Ready( +): ProfileDetails = ProfileDetails.Ready( displayName = displayName, displayNameAmbiguous = displayNameAmbiguous, avatarUrl = avatarUrl, @@ -122,11 +123,13 @@ fun aStickerContent( info: ImageInfo, mediaSource: MediaSource, body: String? = null, + threadInfo: EventThreadInfo? = null, ) = StickerContent( filename = filename, body = body, info = info, source = mediaSource, + threadInfo = threadInfo, ) fun aTimelineItemDebugInfo( @@ -147,6 +150,7 @@ fun aPollContent( votes: ImmutableMap> = persistentMapOf(), endTime: ULong? = null, isEdited: Boolean = false, + threadInfo: EventThreadInfo? = null, ) = PollContent( question = question, kind = kind, @@ -155,4 +159,5 @@ fun aPollContent( votes = votes, endTime = endTime, isEdited = isEdited, + threadInfo = threadInfo, ) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/item/event/Fixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/item/event/Fixture.kt index c9e7c304d5..5e659d3c5c 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/item/event/Fixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/item/event/Fixture.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/tracing/FakeTracingService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/tracing/FakeTracingService.kt index 52ffe8f32d..0b57c0a0b1 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/tracing/FakeTracingService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/tracing/FakeTracingService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt index d8788e4026..f4f12a435e 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeCallWidgetSettingsProvider.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeCallWidgetSettingsProvider.kt index 6caa6e1570..a91868c4b6 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeCallWidgetSettingsProvider.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeCallWidgetSettingsProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeMatrixWidgetDriver.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeMatrixWidgetDriver.kt index 9262858252..5540ba83e8 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeMatrixWidgetDriver.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/widget/FakeMatrixWidgetDriver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixmedia/api/build.gradle.kts b/libraries/matrixmedia/api/build.gradle.kts new file mode 100644 index 0000000000..90c7ee635e --- /dev/null +++ b/libraries/matrixmedia/api/build.gradle.kts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.matrix.ui.media.api" +} + +dependencies { + implementation(projects.libraries.designsystem) + implementation(projects.libraries.matrix.api) + implementation(libs.coil.compose) +} diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatatarDataExt.kt b/libraries/matrixmedia/api/src/main/kotlin/io/element/android/libraries/matrix/ui/media/Avatar.kt similarity index 60% rename from libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatatarDataExt.kt rename to libraries/matrixmedia/api/src/main/kotlin/io/element/android/libraries/matrix/ui/media/Avatar.kt index 5426b7d505..4d4dd402d6 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatatarDataExt.kt +++ b/libraries/matrixmedia/api/src/main/kotlin/io/element/android/libraries/matrix/ui/media/Avatar.kt @@ -1,15 +1,13 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.ui.media -import io.element.android.libraries.designsystem.components.avatar.AvatarData -import io.element.android.libraries.matrix.api.media.MediaSource - /** * The size in pixel of the thumbnail to generate for the avatar. * This is not the size of the avatar displayed in the UI but the size to get from the servers. @@ -24,10 +22,3 @@ import io.element.android.libraries.matrix.api.media.MediaSource * Let's always use the same size so coil caching works properly. */ const val AVATAR_THUMBNAIL_SIZE_IN_PIXEL = 240L - -internal fun AvatarData.toMediaRequestData(): MediaRequestData { - return MediaRequestData( - source = url?.let { MediaSource(it) }, - kind = MediaRequestData.Kind.Thumbnail(AVATAR_THUMBNAIL_SIZE_IN_PIXEL) - ) -} diff --git a/libraries/matrixmedia/api/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderHolder.kt b/libraries/matrixmedia/api/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderHolder.kt new file mode 100644 index 0000000000..62b169744b --- /dev/null +++ b/libraries/matrixmedia/api/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderHolder.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.ui.media + +import coil3.ImageLoader +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.SessionId + +interface ImageLoaderHolder { + fun get(): ImageLoader + fun get(client: MatrixClient): ImageLoader + fun remove(sessionId: SessionId) +} diff --git a/libraries/matrixmedia/api/src/main/kotlin/io/element/android/libraries/matrix/ui/media/InitialsAvatarBitmapGenerator.kt b/libraries/matrixmedia/api/src/main/kotlin/io/element/android/libraries/matrix/ui/media/InitialsAvatarBitmapGenerator.kt new file mode 100644 index 0000000000..e6ff5d762d --- /dev/null +++ b/libraries/matrixmedia/api/src/main/kotlin/io/element/android/libraries/matrix/ui/media/InitialsAvatarBitmapGenerator.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.ui.media + +import android.graphics.Bitmap +import io.element.android.libraries.designsystem.components.avatar.AvatarData + +/** + * Generates a bitmap for an initials avatar based on the provided [io.element.android.libraries.designsystem.components.avatar.AvatarData]. + */ +interface InitialsAvatarBitmapGenerator { + fun generateBitmap( + size: Int, + avatarData: AvatarData, + useDarkTheme: Boolean, + fontSizePercentage: Float = 0.5f, + ): Bitmap? +} diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestData.kt b/libraries/matrixmedia/api/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestData.kt similarity index 89% rename from libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestData.kt rename to libraries/matrixmedia/api/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestData.kt index 5bca419a84..47841a573b 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestData.kt +++ b/libraries/matrixmedia/api/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestData.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,7 @@ import io.element.android.libraries.matrix.api.media.MediaSource /** * Can be use with [coil3.compose.AsyncImage] to load a [MediaSource]. - * This will go internally through our [CoilMediaFetcher]. + * This will go internally through our CoilMediaFetcher. * * Example of usage: * AsyncImage( diff --git a/libraries/matrixmedia/impl/build.gradle.kts b/libraries/matrixmedia/impl/build.gradle.kts new file mode 100644 index 0000000000..82afc2f62c --- /dev/null +++ b/libraries/matrixmedia/impl/build.gradle.kts @@ -0,0 +1,33 @@ +import extension.setupDependencyInjection +import extension.testCommonDependencies + +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-compose-library") +} + +android { + namespace = "io.element.android.libraries.matrix.ui.media.impl" +} + +setupDependencyInjection() + +dependencies { + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.matrixmedia.api) + implementation(projects.libraries.designsystem) + implementation(libs.coil.compose) + implementation(libs.coil.gif) + implementation(libs.coil.network.okhttp) + + testCommonDependencies(libs, true) + testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.dateformatter.test) + testImplementation(projects.libraries.sessionStorage.test) +} diff --git a/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarDataExt.kt b/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarDataExt.kt new file mode 100644 index 0000000000..0b1a09bdf9 --- /dev/null +++ b/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarDataExt.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.ui.media + +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.matrix.api.media.MediaSource + +internal fun AvatarData.toMediaRequestData(): MediaRequestData { + return MediaRequestData( + source = url?.let { MediaSource(it) }, + kind = MediaRequestData.Kind.Thumbnail(AVATAR_THUMBNAIL_SIZE_IN_PIXEL) + ) +} diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarDataFetcherFactory.kt b/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarDataFetcherFactory.kt similarity index 89% rename from libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarDataFetcherFactory.kt rename to libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarDataFetcherFactory.kt index c852be62f5..b6093cd327 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarDataFetcherFactory.kt +++ b/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarDataFetcherFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt b/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt similarity index 89% rename from libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt rename to libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt index 9bcae48a6a..06321f64a9 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt +++ b/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/CoilMediaFetcher.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -26,14 +27,15 @@ internal class CoilMediaFetcher( private val mediaData: MediaRequestData, ) : Fetcher { override suspend fun fetch(): FetchResult? { - if (mediaData.source == null) { + val source = mediaData.source + if (source == null) { Timber.e("MediaData source is null") return null } - return when (mediaData.kind) { - is MediaRequestData.Kind.Content -> fetchContent(mediaData.source) - is MediaRequestData.Kind.Thumbnail -> fetchThumbnail(mediaData.source, mediaData.kind) - is MediaRequestData.Kind.File -> fetchFile(mediaData.source, mediaData.kind) + return when (val kind = mediaData.kind) { + is MediaRequestData.Kind.Content -> fetchContent(source) + is MediaRequestData.Kind.Thumbnail -> fetchThumbnail(source, kind) + is MediaRequestData.Kind.File -> fetchFile(source, kind) } } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderHolder.kt b/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/DefaultImageLoaderHolder.kt similarity index 78% rename from libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderHolder.kt rename to libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/DefaultImageLoaderHolder.kt index 6720eaae0f..a0957a3b13 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderHolder.kt +++ b/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/DefaultImageLoaderHolder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,26 +11,22 @@ package io.element.android.libraries.matrix.ui.media import coil3.ImageLoader import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.sessionstorage.api.observer.SessionListener import io.element.android.libraries.sessionstorage.api.observer.SessionObserver -interface ImageLoaderHolder { - fun get(client: MatrixClient): ImageLoader - fun remove(sessionId: SessionId) -} - @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) -@Inject class DefaultImageLoaderHolder( - private val loggedInImageLoaderFactory: LoggedInImageLoaderFactory, + private val imageLoaderFactory: ImageLoaderFactory, private val sessionObserver: SessionObserver, ) : ImageLoaderHolder { private val map = mutableMapOf() + private val notLoggedInImageLoader by lazy { + imageLoaderFactory.newImageLoader() + } init { observeSessions() @@ -37,18 +34,20 @@ class DefaultImageLoaderHolder( private fun observeSessions() { sessionObserver.addListener(object : SessionListener { - override suspend fun onSessionCreated(userId: String) = Unit - - override suspend fun onSessionDeleted(userId: String) { + override suspend fun onSessionDeleted(userId: String, wasLastSession: Boolean) { remove(SessionId(userId)) } }) } + override fun get(): ImageLoader { + return notLoggedInImageLoader + } + override fun get(client: MatrixClient): ImageLoader { return synchronized(map) { map.getOrPut(client.sessionId) { - loggedInImageLoaderFactory + imageLoaderFactory .newImageLoader(client.matrixMediaLoader) } } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/InitialsAvatarBitmapGenerator.kt b/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/DefaultInitialsAvatarBitmapGenerator.kt similarity index 67% rename from libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/InitialsAvatarBitmapGenerator.kt rename to libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/DefaultInitialsAvatarBitmapGenerator.kt index e0e09a07e7..71c894f305 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/InitialsAvatarBitmapGenerator.kt +++ b/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/DefaultInitialsAvatarBitmapGenerator.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -23,6 +24,8 @@ import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.unit.dp import androidx.core.graphics.createBitmap import coil3.Bitmap +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding import io.element.android.compound.theme.AvatarColors import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.SemanticColors @@ -34,60 +37,35 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Text -/** - * Generates a bitmap for an initials avatar based on the provided [AvatarData]. - */ -class InitialsAvatarBitmapGenerator( - useDarkTheme: Boolean = false, - private val fontSizePercentage: Float = 0.5f, -) { - private val compoundColors: SemanticColors = if (useDarkTheme) { - compoundColorsDark - } else { - compoundColorsLight - } - +@ContributesBinding(AppScope::class) +class DefaultInitialsAvatarBitmapGenerator : InitialsAvatarBitmapGenerator { // List of predefined avatar colors to use for initials avatars, in light mode - private val allAvatarColors: List = listOf( - AvatarColors( - background = compoundColors.bgDecorative1, - foreground = compoundColors.textDecorative1, - ), - AvatarColors( - background = compoundColors.bgDecorative2, - foreground = compoundColors.textDecorative2, - ), - AvatarColors( - background = compoundColors.bgDecorative3, - foreground = compoundColors.textDecorative3, - ), - AvatarColors( - background = compoundColors.bgDecorative4, - foreground = compoundColors.textDecorative4, - ), - AvatarColors( - background = compoundColors.bgDecorative5, - foreground = compoundColors.textDecorative5, - ), - AvatarColors( - background = compoundColors.bgDecorative6, - foreground = compoundColors.textDecorative6, - ), - ) + private val lightAvatarColors: List = compoundColorsLight.buildAvatarColors() + + // List of predefined avatar colors to use for initials avatars, in dark mode + private val darkAvatarColors: List = compoundColorsDark.buildAvatarColors() /** * Generates a bitmap for an avatar with no URL, using the initials from the [AvatarData]. * @param size The size of the bitmap to generate, in pixels. * @param avatarData The [AvatarData] containing the initials and other information. + * @param useDarkTheme Whether the theme is dark. + * @param fontSizePercentage The percentage of the avatar size to use for the font size. */ - fun generateBitmap(size: Int, avatarData: AvatarData): Bitmap? { + override fun generateBitmap( + size: Int, + avatarData: AvatarData, + useDarkTheme: Boolean, + fontSizePercentage: Float, + ): Bitmap? { if (avatarData.url != null) { // This generator is only for initials avatars, not for avatars with URLs return null } // Get the color pair to use for the initials avatar - val avatarColors = allAvatarColors[avatarData.id.sumOf { it.code } % allAvatarColors.size] + val colors = if (useDarkTheme) darkAvatarColors else lightAvatarColors + val avatarColors = colors[avatarData.id.sumOf { it.code } % colors.size] val bitmap = createBitmap(size, size) Canvas(bitmap).run { @@ -115,20 +93,32 @@ class InitialsAvatarBitmapGenerator( } } +private fun SemanticColors.buildAvatarColors(): List = listOf( + AvatarColors(background = bgDecorative1, foreground = textDecorative1), + AvatarColors(background = bgDecorative2, foreground = textDecorative2), + AvatarColors(background = bgDecorative3, foreground = textDecorative3), + AvatarColors(background = bgDecorative4, foreground = textDecorative4), + AvatarColors(background = bgDecorative5, foreground = textDecorative5), + AvatarColors(background = bgDecorative6, foreground = textDecorative6), +) + @Composable @PreviewsDayNight internal fun InitialsAvatarBitmapGeneratorPreview() = ElementPreview { Column( verticalArrangement = Arrangement.spacedBy(4.dp) ) { + val generator = remember { DefaultInitialsAvatarBitmapGenerator() } repeat(6) { index -> val avatarData = remember { AvatarData(id = index.toString(), name = Char('0'.code + index).toString(), size = AvatarSize.IncomingCall) } val isLightTheme = ElementTheme.isLightTheme val bitmap = remember(isLightTheme) { - val generator = InitialsAvatarBitmapGenerator(useDarkTheme = !isLightTheme) - generator.generateBitmap(512, avatarData)?.asImageBitmap() + generator.generateBitmap( + size = 512, + avatarData = avatarData, + useDarkTheme = !isLightTheme, + )?.asImageBitmap() } - bitmap?.let { Image(bitmap = it, contentDescription = null, modifier = Modifier.size(48.dp)) } ?: Text("No avatar generated") diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderFactories.kt b/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderFactories.kt similarity index 60% rename from libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderFactories.kt rename to libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderFactories.kt index 54a1a21e22..e67e630e97 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderFactories.kt +++ b/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/ImageLoaderFactories.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,33 +16,40 @@ import coil3.gif.GifDecoder import coil3.network.okhttp.OkHttpNetworkFetcherFactory import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Provider import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import okhttp3.OkHttpClient -interface LoggedInImageLoaderFactory { +interface ImageLoaderFactory { + fun newImageLoader(): ImageLoader fun newImageLoader(matrixMediaLoader: MatrixMediaLoader): ImageLoader } @ContributesBinding(AppScope::class) -@Inject -class DefaultLoggedInImageLoaderFactory( +class DefaultImageLoaderFactory( @ApplicationContext private val context: Context, private val okHttpClient: Provider, -) : LoggedInImageLoaderFactory { +) : ImageLoaderFactory { + private val okHttpNetworkFetcherFactory = OkHttpNetworkFetcherFactory( + callFactory = { + // Use newBuilder, see https://coil-kt.github.io/coil/network/#using-a-custom-okhttpclient + okHttpClient().newBuilder().build() + } + ) + + override fun newImageLoader(): ImageLoader { + return ImageLoader.Builder(context) + .components { + add(okHttpNetworkFetcherFactory) + } + .build() + } + override fun newImageLoader(matrixMediaLoader: MatrixMediaLoader): ImageLoader { return ImageLoader.Builder(context) .components { - add( - OkHttpNetworkFetcherFactory( - callFactory = { - // Use newBuilder, see https://coil-kt.github.io/coil/network/#using-a-custom-okhttpclient - okHttpClient().newBuilder().build() - } - ) - ) + add(okHttpNetworkFetcherFactory) // Add gif support if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { add(AnimatedImageDecoder.Factory()) @@ -56,24 +64,3 @@ class DefaultLoggedInImageLoaderFactory( .build() } } - -@Inject -class NotLoggedInImageLoaderFactory( - @ApplicationContext private val context: Context, - private val okHttpClient: Provider, -) { - fun newImageLoader(): ImageLoader { - return ImageLoader.Builder(context) - .components { - add( - OkHttpNetworkFetcherFactory( - callFactory = { - // Use newBuilder, see https://coil-kt.github.io/coil/network/#using-a-custom-okhttpclient - okHttpClient().newBuilder().build() - } - ) - ) - } - .build() - } -} diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataFetcherFactory.kt b/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataFetcherFactory.kt similarity index 88% rename from libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataFetcherFactory.kt rename to libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataFetcherFactory.kt index bc6cb7663c..f0cfd2047b 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataFetcherFactory.kt +++ b/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataFetcherFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataKeyer.kt b/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataKeyer.kt similarity index 83% rename from libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataKeyer.kt rename to libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataKeyer.kt index c6423cfb0f..488803e933 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataKeyer.kt +++ b/libraries/matrixmedia/impl/src/main/kotlin/io/element/android/libraries/matrix/ui/media/MediaRequestDataKeyer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -24,6 +25,5 @@ internal class MediaRequestDataKeyer : Keyer { } private fun MediaRequestData.toKey(): String? { - if (source == null) return null - return "${source.url}_$kind" + return source?.let { "${it.url}_$kind" } } diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/media/DefaultImageLoaderHolderTest.kt b/libraries/matrixmedia/impl/src/test/kotlin/io/element/android/libraries/matrix/ui/media/DefaultImageLoaderHolderTest.kt similarity index 73% rename from libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/media/DefaultImageLoaderHolderTest.kt rename to libraries/matrixmedia/impl/src/test/kotlin/io/element/android/libraries/matrix/ui/media/DefaultImageLoaderHolderTest.kt index a30305176b..dd9127a4ae 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/media/DefaultImageLoaderHolderTest.kt +++ b/libraries/matrixmedia/impl/src/test/kotlin/io/element/android/libraries/matrix/ui/media/DefaultImageLoaderHolderTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,6 +14,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.sessionstorage.api.observer.SessionObserver import io.element.android.libraries.sessionstorage.test.observer.FakeSessionObserver import io.element.android.libraries.sessionstorage.test.observer.NoOpSessionObserver import io.element.android.tests.testutils.lambda.lambdaRecorder @@ -29,9 +31,10 @@ class DefaultImageLoaderHolderTest { val context = InstrumentationRegistry.getInstrumentation().context val lambda = lambdaRecorder { ImageLoader.Builder(context).build() } - val holder = DefaultImageLoaderHolder( - loggedInImageLoaderFactory = FakeLoggedInImageLoaderFactory(lambda), - sessionObserver = NoOpSessionObserver() + val holder = createDefaultImageLoaderHolder( + imageLoaderFactory = FakeImageLoaderFactory( + newMatrixImageLoaderLambda = lambda, + ), ) val client = FakeMatrixClient() val imageLoader1 = holder.get(client) @@ -49,8 +52,10 @@ class DefaultImageLoaderHolderTest { lambdaRecorder { ImageLoader.Builder(context).build() } val sessionObserver = FakeSessionObserver() val holder = DefaultImageLoaderHolder( - loggedInImageLoaderFactory = FakeLoggedInImageLoaderFactory(lambda), - sessionObserver = sessionObserver + imageLoaderFactory = FakeImageLoaderFactory( + newMatrixImageLoaderLambda = lambda, + ), + sessionObserver = sessionObserver, ) assertThat(sessionObserver.listeners.size).isEqualTo(1) val client = FakeMatrixClient() @@ -68,10 +73,20 @@ class DefaultImageLoaderHolderTest { lambdaRecorder { ImageLoader.Builder(context).build() } val sessionObserver = FakeSessionObserver() DefaultImageLoaderHolder( - loggedInImageLoaderFactory = FakeLoggedInImageLoaderFactory(lambda), - sessionObserver = sessionObserver + imageLoaderFactory = FakeImageLoaderFactory( + newMatrixImageLoaderLambda = lambda, + ), + sessionObserver = sessionObserver, ) assertThat(sessionObserver.listeners.size).isEqualTo(1) sessionObserver.onSessionCreated(A_SESSION_ID.value) } } + +private fun createDefaultImageLoaderHolder( + imageLoaderFactory: ImageLoaderFactory = FakeImageLoaderFactory(), + sessionObserver: SessionObserver = NoOpSessionObserver(), +) = DefaultImageLoaderHolder( + imageLoaderFactory = imageLoaderFactory, + sessionObserver = sessionObserver, +) diff --git a/libraries/matrixmedia/impl/src/test/kotlin/io/element/android/libraries/matrix/ui/media/FakeImageLoaderFactory.kt b/libraries/matrixmedia/impl/src/test/kotlin/io/element/android/libraries/matrix/ui/media/FakeImageLoaderFactory.kt new file mode 100644 index 0000000000..bcafad0737 --- /dev/null +++ b/libraries/matrixmedia/impl/src/test/kotlin/io/element/android/libraries/matrix/ui/media/FakeImageLoaderFactory.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.ui.media + +import coil3.ImageLoader +import io.element.android.libraries.matrix.api.media.MatrixMediaLoader +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeImageLoaderFactory( + private val newImageLoaderLambda: () -> ImageLoader = { lambdaError() }, + private val newMatrixImageLoaderLambda: (MatrixMediaLoader) -> ImageLoader = { lambdaError() }, +) : ImageLoaderFactory { + override fun newImageLoader(): ImageLoader { + return newImageLoaderLambda() + } + + override fun newImageLoader(matrixMediaLoader: MatrixMediaLoader): ImageLoader { + return newMatrixImageLoaderLambda(matrixMediaLoader) + } +} diff --git a/libraries/matrixmedia/test/build.gradle.kts b/libraries/matrixmedia/test/build.gradle.kts new file mode 100644 index 0000000000..5b8e966d08 --- /dev/null +++ b/libraries/matrixmedia/test/build.gradle.kts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.matrix.ui.media.test" +} + +dependencies { + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.matrixmedia.api) + implementation(projects.libraries.designsystem) + implementation(projects.tests.testutils) + implementation(libs.coil.compose) +} diff --git a/libraries/matrixmedia/test/src/main/kotlin/io/element/android/libraries/matrix/ui/media/test/FakeImageLoader.kt b/libraries/matrixmedia/test/src/main/kotlin/io/element/android/libraries/matrix/ui/media/test/FakeImageLoader.kt new file mode 100644 index 0000000000..d3cd7388e5 --- /dev/null +++ b/libraries/matrixmedia/test/src/main/kotlin/io/element/android/libraries/matrix/ui/media/test/FakeImageLoader.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.ui.media.test + +import coil3.ComponentRegistry +import coil3.ImageLoader +import coil3.disk.DiskCache +import coil3.memory.MemoryCache +import coil3.request.Disposable +import coil3.request.ImageRequest +import coil3.request.ImageResult + +class FakeImageLoader : ImageLoader { + private val executedRequests = mutableListOf() + + override val defaults: ImageRequest.Defaults + get() = error("Not implemented") + override val components: ComponentRegistry + get() = error("Not implemented") + override val memoryCache: MemoryCache? + get() = error("Not implemented") + override val diskCache: DiskCache? + get() = error("Not implemented") + + override fun enqueue(request: ImageRequest): Disposable { + error("Not implemented") + } + + override suspend fun execute(request: ImageRequest): ImageResult { + executedRequests.add(request) + error("Not implemented") + } + + override fun shutdown() { + error("Not implemented") + } + + override fun newBuilder(): ImageLoader.Builder { + error("Not implemented") + } + + fun getExecutedRequestsData(): List { + return executedRequests.map { it.data } + } +} diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeImageLoaderHolder.kt b/libraries/matrixmedia/test/src/main/kotlin/io/element/android/libraries/matrix/ui/media/test/FakeImageLoaderHolder.kt similarity index 57% rename from libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeImageLoaderHolder.kt rename to libraries/matrixmedia/test/src/main/kotlin/io/element/android/libraries/matrix/ui/media/test/FakeImageLoaderHolder.kt index 4c92dc8c18..59b9efda7d 100644 --- a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeImageLoaderHolder.kt +++ b/libraries/matrixmedia/test/src/main/kotlin/io/element/android/libraries/matrix/ui/media/test/FakeImageLoaderHolder.kt @@ -1,21 +1,27 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.libraries.push.test.notifications +package io.element.android.libraries.matrix.ui.media.test import coil3.ImageLoader import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder -class FakeImageLoaderHolder : ImageLoaderHolder { - private val fakeImageLoader = FakeImageLoader() +class FakeImageLoaderHolder( + val fakeImageLoader: ImageLoader = FakeImageLoader(), +) : ImageLoaderHolder { + override fun get(): ImageLoader { + return fakeImageLoader + } + override fun get(client: MatrixClient): ImageLoader { - return fakeImageLoader.getImageLoader() + return fakeImageLoader } override fun remove(sessionId: SessionId) { diff --git a/libraries/matrixmedia/test/src/main/kotlin/io/element/android/libraries/matrix/ui/media/test/FakeInitialsAvatarBitmapGenerator.kt b/libraries/matrixmedia/test/src/main/kotlin/io/element/android/libraries/matrix/ui/media/test/FakeInitialsAvatarBitmapGenerator.kt new file mode 100644 index 0000000000..2d0df756ac --- /dev/null +++ b/libraries/matrixmedia/test/src/main/kotlin/io/element/android/libraries/matrix/ui/media/test/FakeInitialsAvatarBitmapGenerator.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.ui.media.test + +import coil3.Bitmap +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.matrix.ui.media.InitialsAvatarBitmapGenerator +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeInitialsAvatarBitmapGenerator( + private val generateBitmapResult: (Int, AvatarData, Boolean, Float) -> Bitmap? = { _, _, _, _ -> lambdaError() } +) : InitialsAvatarBitmapGenerator { + override fun generateBitmap( + size: Int, + avatarData: AvatarData, + useDarkTheme: Boolean, + fontSizePercentage: Float, + ): Bitmap? { + return generateBitmapResult(size, avatarData, useDarkTheme, fontSizePercentage) + } +} diff --git a/libraries/matrixui/build.gradle.kts b/libraries/matrixui/build.gradle.kts index 96045f3a93..5513dc240e 100644 --- a/libraries/matrixui/build.gradle.kts +++ b/libraries/matrixui/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -29,14 +30,13 @@ dependencies { implementation(projects.libraries.architecture) implementation(projects.libraries.androidutils) implementation(projects.libraries.matrix.api) + implementation(projects.libraries.matrixmedia.api) implementation(projects.libraries.designsystem) implementation(projects.libraries.core) implementation(projects.libraries.uiStrings) implementation(projects.libraries.testtags) implementation(libs.coil.compose) - implementation(libs.coil.gif) - implementation(libs.coil.network.okhttp) - implementation(libs.jsoup) + implementation(libs.matrix.richtexteditor) implementation(projects.libraries.previewutils) testCommonDependencies(libs, true) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnail.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnail.kt index 7e105a8af7..8ffdc1b003 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnail.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnail.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnailInfoProvider.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnailInfoProvider.kt index 42d2f65b59..ea38a979ae 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnailInfoProvider.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AttachmentThumbnailInfoProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarActionBottomSheet.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarActionBottomSheet.kt index fa9cf5156c..4628833bb4 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarActionBottomSheet.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/AvatarActionBottomSheet.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/CheckableUserRow.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/CheckableUserRow.kt index 17d5243972..917b03f309 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/CheckableUserRow.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/CheckableUserRow.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,8 +11,9 @@ package io.element.android.libraries.matrix.ui.components import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.ui.Alignment @@ -19,10 +21,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.Role import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import io.element.android.libraries.designsystem.atomic.atoms.SelectedIndicatorAtom import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.preview.ElementThemedPreview +import io.element.android.libraries.designsystem.theme.components.Checkbox import io.element.android.libraries.designsystem.theme.components.HorizontalDivider import io.element.android.libraries.matrix.ui.model.getAvatarData @@ -62,11 +64,12 @@ fun CheckableUserRow( ) } } - SelectedIndicatorAtom( - modifier = Modifier.padding(end = 24.dp), + Checkbox( + onCheckedChange = onCheckedChange, checked = checked, enabled = enabled, ) + Spacer(modifier = Modifier.width(4.dp)) } } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/CreateDmConfirmationBottomSheet.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/CreateDmConfirmationBottomSheet.kt index b0a19924b3..95935947db 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/CreateDmConfirmationBottomSheet.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/CreateDmConfirmationBottomSheet.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt index ad06985ab7..284ffcbab1 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableAvatarView.kt @@ -1,34 +1,38 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.ui.components -import android.net.Uri -import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.CompositingStrategy +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp -import androidx.core.net.toUri import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.designsystem.components.avatar.Avatar @@ -47,37 +51,58 @@ import io.element.android.libraries.ui.strings.CommonStrings fun EditableAvatarView( matrixId: String, displayName: String?, - avatarUrl: Uri?, + avatarUrl: String?, avatarSize: AvatarSize, avatarType: AvatarType, onAvatarClick: () -> Unit, modifier: Modifier = Modifier, + enabled: Boolean = true, ) { - Column( - modifier = modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, + val a11yAvatar = stringResource(CommonStrings.a11y_avatar) + val editIconRadius = 15.dp + val parentHeight = avatarSize.dp + val parentWidth = avatarSize.dp + editIconRadius / 2f + Box( + modifier = modifier + .wrapContentSize() + .size(height = parentHeight, width = parentWidth) + .clickable( + enabled = enabled, + interactionSource = remember { MutableInteractionSource() }, + onClickLabel = stringResource(CommonStrings.a11y_edit_avatar), + onClick = onAvatarClick, + indication = ripple(bounded = false), + ) + .testTag(TestTags.editAvatar) + .clearAndSetSemantics { + contentDescription = a11yAvatar + }, ) { - val a11yAvatar = stringResource(CommonStrings.a11y_avatar) Box( modifier = Modifier - .clickable( - interactionSource = remember { MutableInteractionSource() }, - onClickLabel = stringResource(CommonStrings.a11y_edit_avatar), - onClick = onAvatarClick, - indication = ripple(bounded = false), - ) - .testTag(TestTags.editAvatar) - .clearAndSetSemantics { - contentDescription = a11yAvatar - }, + .graphicsLayer { + compositingStrategy = CompositingStrategy.Offscreen + } + .drawWithContent { + drawContent() + drawCircle( + color = Color.Black, + center = Offset( + x = parentWidth.toPx() - editIconRadius.toPx(), + y = size.height - editIconRadius.toPx(), + ), + radius = (editIconRadius + 4.dp).toPx(), + blendMode = BlendMode.Clear, + ) + } ) { - when (avatarUrl?.scheme) { - null, "mxc" -> { + when { + avatarUrl == null || avatarUrl.startsWith("mxc://") -> { Avatar( avatarData = AvatarData( id = matrixId, name = displayName, - url = avatarUrl?.toString(), + url = avatarUrl, size = avatarSize, ), avatarType = avatarType, @@ -91,48 +116,42 @@ fun EditableAvatarView( ) } } - - Box( - modifier = Modifier - .align(Alignment.BottomEnd) - .clip(CircleShape) - .background(ElementTheme.colors.iconPrimary) - .size(24.dp), - contentAlignment = Alignment.Center, - ) { - Icon( - modifier = Modifier.size(16.dp), - imageVector = CompoundIcons.EditSolid(), - contentDescription = null, - tint = ElementTheme.colors.iconOnSolidPrimary, - ) - } } + Icon( + modifier = Modifier + .align(Alignment.BottomEnd) + .size(editIconRadius * 2) + .border(1.dp, ElementTheme.colors.borderInteractiveSecondary, CircleShape) + .padding(6.dp), + imageVector = CompoundIcons.Edit(), + contentDescription = null, + tint = ElementTheme.colors.iconPrimary, + ) } } @PreviewsDayNight @Composable internal fun EditableAvatarViewPreview( - @PreviewParameter(EditableAvatarViewUriProvider::class) uri: Uri? + @PreviewParameter(EditableAvatarViewUriProvider::class) uri: String? ) = ElementPreview( drawableFallbackForImages = CommonDrawables.sample_avatar, ) { EditableAvatarView( matrixId = "id", - displayName = "A room", + displayName = "Room", avatarUrl = uri, - avatarSize = AvatarSize.EditRoomDetails, + avatarSize = AvatarSize.RoomDetailsHeader, avatarType = AvatarType.User, onAvatarClick = {}, ) } -open class EditableAvatarViewUriProvider : PreviewParameterProvider { - override val values: Sequence +open class EditableAvatarViewUriProvider : PreviewParameterProvider { + override val values: Sequence get() = sequenceOf( null, - "mxc://matrix.org/123456".toUri(), - "https://example.com/avatar.jpg".toUri(), + "mxc://matrix.org/123456", + "https://example.com/avatar.jpg", ) } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableOrgAvatar.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableOrgAvatar.kt index 433e036d97..81c3e05263 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableOrgAvatar.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/EditableOrgAvatar.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt index 960bdb9098..13747a1a3d 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/InviteSenderView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/JoinButton.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/JoinButton.kt index 0d5b94dc64..e162feb6cd 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/JoinButton.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/JoinButton.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeader.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeader.kt index cb97c1e9f4..5b44b50ce9 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeader.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeader.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeaderPlaceholder.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeaderPlaceholder.kt index cdf6e1098c..08f8a37cc1 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeaderPlaceholder.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserHeaderPlaceholder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserProvider.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserProvider.kt index ce0d8b2eec..4d5a1cd222 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserProvider.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserRow.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserRow.kt index 70d22bc644..cf89074737 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserRow.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/MatrixUserRow.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/OrganizationHeader.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/OrganizationHeader.kt index ebd5b5969c..374ebd7a1a 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/OrganizationHeader.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/OrganizationHeader.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectRoomInfoProvider.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectRoomInfoProvider.kt index 04562eb13c..731d499445 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectRoomInfoProvider.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectRoomInfoProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedItem.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedItem.kt index d72d786b8d..29d2f469a0 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedItem.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedItem.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedRoom.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedRoom.kt index 59542a5338..b7484903da 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedRoom.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedRoom.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUser.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUser.kt index d8648c903e..b118dbcbe3 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUser.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUser.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersRowList.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersRowList.kt index 1f2d1ac12a..ecb86a52f8 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersRowList.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SelectedUsersRowList.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceHeaderRootView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceHeaderRootView.kt index 9f41171dd9..1e8fb864a7 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceHeaderRootView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceHeaderRootView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -41,7 +42,7 @@ fun SpaceHeaderRootView( verticalArrangement = Arrangement.spacedBy(16.dp) ) { BigIcon( - style = BigIcon.Style.Default(CompoundIcons.WorkspaceSolid()) + style = BigIcon.Style.Default(CompoundIcons.SpaceSolid()) ) Text( text = stringResource(CommonStrings.screen_space_list_title), diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceHeaderView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceHeaderView.kt index e9e151f027..ae185d2225 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceHeaderView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceHeaderView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceInfoRow.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceInfoRow.kt index d0d14bcdd4..052d9862a6 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceInfoRow.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceInfoRow.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -107,7 +108,7 @@ internal fun SpaceInfoRowPreview() = ElementPreview { SpaceInfoRow( leftText = "Element space", rightText = numberOfRooms(16), - iconVector = CompoundIcons.Workspace(), + iconVector = CompoundIcons.Space(), ) SpaceInfoRow( visibility = SpaceRoomVisibility.Private, diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceMembersView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceMembersView.kt index 0c7bc9a37d..202ea79d87 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceMembersView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceMembersView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceRoomItemView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceRoomItemView.kt index 5a9c49ceb9..61cb3f9839 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceRoomItemView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceRoomItemView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,6 +11,7 @@ package io.element.android.libraries.matrix.ui.components import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.IntrinsicSize @@ -56,6 +58,9 @@ import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList +/** + * Figma reference: https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=3643-2079&m=dev + */ @Composable fun SpaceRoomItemView( spaceRoom: SpaceRoom, @@ -67,42 +72,58 @@ fun SpaceRoomItemView( trailingAction: @Composable (() -> Unit)? = null, bottomAction: @Composable (() -> Unit)? = null, ) { - SpaceRoomItemScaffold( - modifier = modifier, - avatarData = spaceRoom.getAvatarData(AvatarSize.SpaceListItem), - isSpace = spaceRoom.isSpace, - hideAvatars = hideAvatars, - heroes = spaceRoom.heroes - .map { hero -> hero.getAvatarData(AvatarSize.SpaceListItem) } - .toImmutableList(), - onClick = onClick, - onLongClick = onLongClick, - trailingAction = trailingAction, + val clickModifier = Modifier + .combinedClickable( + onClick = onClick, + onLongClick = onLongClick, + onLongClickLabel = stringResource(CommonStrings.action_open_context_menu), + indication = ripple(), + interactionSource = remember { MutableInteractionSource() } + ) + .onKeyboardContextMenuAction { onLongClick } + Column( + modifier = modifier + .then(clickModifier) + .padding(horizontal = 16.dp, vertical = 12.dp), ) { - NameAndIndicatorRow( - name = spaceRoom.displayName, - showIndicator = showUnreadIndicator - ) - Spacer(modifier = Modifier.height(1.dp)) - SubtitleRow( - visibilityIcon = spaceRoom.visibilityIcon(), - subtitle = spaceRoom.subtitle() - ) - Spacer(modifier = Modifier.height(1.dp)) - val info = spaceRoom.info() - if (info.isNotBlank()) { - Text( - modifier = Modifier.weight(1f), - style = ElementTheme.typography.fontBodyMdRegular, - text = info, - color = ElementTheme.colors.textSecondary, - maxLines = 1, - overflow = TextOverflow.Ellipsis + SpaceRoomItemScaffold( + avatarData = spaceRoom.getAvatarData(AvatarSize.SpaceListItem), + isSpace = spaceRoom.isSpace, + hideAvatars = hideAvatars, + heroes = spaceRoom.heroes + .map { hero -> hero.getAvatarData(AvatarSize.SpaceListItem) } + .toImmutableList(), + trailingAction = trailingAction, + ) { + NameAndIndicatorRow( + name = spaceRoom.displayName, + showIndicator = showUnreadIndicator ) + Spacer(modifier = Modifier.height(1.dp)) + SubtitleRow( + visibilityIcon = spaceRoom.visibilityIcon(), + subtitle = spaceRoom.subtitle() + ) + Spacer(modifier = Modifier.height(1.dp)) + val info = spaceRoom.info() + if (info.isNotBlank()) { + Text( + modifier = Modifier.weight(1f), + style = ElementTheme.typography.fontBodyMdRegular, + text = info, + color = ElementTheme.colors.textSecondary, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } } if (bottomAction != null) { Spacer(modifier = Modifier.height(12.dp)) - bottomAction() + // Match the padding of the text content (avatar + spacer) + Box(modifier = Modifier.padding(start = AvatarSize.SpaceListItem.dp + 16.dp)) { + bottomAction() + } + Spacer(modifier = Modifier.height(4.dp)) } } } @@ -170,28 +191,16 @@ private fun SpaceRoomItemScaffold( avatarData: AvatarData, isSpace: Boolean, heroes: ImmutableList, - onClick: () -> Unit, - onLongClick: () -> Unit, hideAvatars: Boolean, modifier: Modifier = Modifier, trailingAction: @Composable (() -> Unit)? = null, content: @Composable ColumnScope.() -> Unit, ) { - val clickModifier = Modifier - .combinedClickable( - onClick = onClick, - onLongClick = onLongClick, - onLongClickLabel = stringResource(CommonStrings.action_open_context_menu), - indication = ripple(), - interactionSource = remember { MutableInteractionSource() } - ) - .onKeyboardContextMenuAction { onLongClick } Row( modifier = modifier .fillMaxWidth() - .then(clickModifier) - .padding(horizontal = 16.dp, vertical = 8.dp) .height(IntrinsicSize.Min), + verticalAlignment = Alignment.CenterVertically, ) { Avatar( avatarData = avatarData, @@ -249,7 +258,6 @@ internal fun SpaceRoomItemViewPreview(@PreviewParameter(SpaceRoomProvider::class hideAvatars = false, onClick = {}, onLongClick = {}, - modifier = Modifier.fillMaxWidth(), bottomAction = if (spaceRoom.state == CurrentUserMembership.INVITED) { { InviteButtonsRowMolecule({}, {}) } } else { diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceRoomProvider.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceRoomProvider.kt index 4ffda58239..db63bba779 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceRoomProvider.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/SpaceRoomProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UnresolvedUserRow.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UnresolvedUserRow.kt index c36c188165..32fa641a63 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UnresolvedUserRow.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UnresolvedUserRow.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable @@ -44,8 +44,7 @@ fun UnresolvedUserRow( Row( modifier = modifier .fillMaxWidth() - .heightIn(min = 56.dp) - .padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 8.dp), + .padding(start = 16.dp, top = 12.dp, end = 16.dp, bottom = 12.dp), verticalAlignment = Alignment.CenterVertically ) { Avatar( diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UnsavedAvatar.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UnsavedAvatar.kt index 1e041a69fe..104a418360 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UnsavedAvatar.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UnsavedAvatar.kt @@ -1,13 +1,13 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.ui.components -import android.net.Uri import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -43,7 +43,7 @@ import io.element.android.libraries.designsystem.theme.temporaryColorBgSpecial */ @Composable fun UnsavedAvatar( - avatarUri: Uri?, + avatarUri: String?, avatarSize: AvatarSize, avatarType: AvatarType, modifier: Modifier = Modifier, @@ -86,8 +86,8 @@ internal fun UnsavedAvatarPreview() = ElementPreview { horizontalArrangement = Arrangement.spacedBy(8.dp), ) { UnsavedAvatar(null, AvatarSize.EditRoomDetails, AvatarType.User) - UnsavedAvatar(Uri.EMPTY, AvatarSize.EditRoomDetails, AvatarType.User) + UnsavedAvatar("", AvatarSize.EditRoomDetails, AvatarType.User) UnsavedAvatar(null, AvatarSize.EditRoomDetails, AvatarType.Space()) - UnsavedAvatar(Uri.EMPTY, AvatarSize.EditRoomDetails, AvatarType.Space()) + UnsavedAvatar("", AvatarSize.EditRoomDetails, AvatarType.Space()) } } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UserRow.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UserRow.kt index 628fee7cb2..9bcf0b323f 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UserRow.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/components/UserRow.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.libraries.matrix.ui.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -36,8 +36,7 @@ internal fun UserRow( Row( modifier = modifier .fillMaxWidth() - .heightIn(min = 56.dp) - .padding(start = 16.dp, top = 4.dp, end = 16.dp, bottom = 4.dp), + .padding(start = 16.dp, top = 12.dp, end = 16.dp, bottom = 12.dp), verticalAlignment = Alignment.CenterVertically ) { Avatar( diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarAction.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarAction.kt index c17a7eaa69..c9fad122cc 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarAction.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/media/AvatarAction.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/RoomMemberProfilesCache.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/RoomMemberProfilesCache.kt index 58dac492dc..4a9f4f98a4 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/RoomMemberProfilesCache.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/RoomMemberProfilesCache.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/RoomNamesCache.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/RoomNamesCache.kt index f9253d351f..f8d3402850 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/RoomNamesCache.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/RoomNamesCache.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocument.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocument.kt index 1950d68aa6..ee9de51681 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocument.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocument.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,7 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat -import org.jsoup.Jsoup +import io.element.android.wysiwyg.utils.HtmlToDomParser import org.jsoup.nodes.Document /** @@ -33,9 +34,9 @@ fun FormattedBody.toHtmlDocument( ?.trimEnd() ?.let { formattedBody -> val dom = if (prefix != null) { - Jsoup.parse("$prefix $formattedBody") + HtmlToDomParser.document("$prefix $formattedBody") } else { - Jsoup.parse(formattedBody) + HtmlToDomParser.document(formattedBody) } // Prepend `@` to mentions diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainText.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainText.kt index 7e37be8614..d58d12b785 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainText.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainText.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -54,8 +55,15 @@ private class PlainTextNodeVisitor : NodeVisitor { private val builder = StringBuilder() override fun head(node: Node, depth: Int) { - if (node is TextNode && node.text().isNotBlank()) { - builder.append(node.text()) + if (node is TextNode) { + // If the text node is blank, only add a single whitespace char if there wasn't already one + if (node.text().isBlank()) { + if (builder.lastOrNull()?.isWhitespace() == false) { + builder.append(" ") + } + } else { + builder.append(node.text()) + } } else if (node is Element && node.tagName() == "li") { val index = node.elementSiblingIndex() + 1 val isOrdered = node.parent()?.nodeName()?.lowercase() == "ol" diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetails.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetails.kt index dadb48e63c..3179c748ea 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetails.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetails.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,7 +15,7 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.timeline.item.event.EventContent import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent -import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.ui.messages.toPlainText @@ -24,7 +25,7 @@ sealed interface InReplyToDetails { data class Ready( val eventId: EventId, val senderId: UserId, - val senderProfile: ProfileTimelineDetails, + val senderProfile: ProfileDetails, val eventContent: EventContent?, val textContent: String?, ) : InReplyToDetails diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailsProvider.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailsProvider.kt index f43c409f0e..0727b0b7ec 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailsProvider.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailsProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -23,7 +24,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.MessageConten import io.element.android.libraries.matrix.api.timeline.item.event.MessageType import io.element.android.libraries.matrix.api.timeline.item.event.NoticeMessageType import io.element.android.libraries.matrix.api.timeline.item.event.PollContent -import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent import io.element.android.libraries.matrix.api.timeline.item.event.StickerMessageType import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType @@ -88,6 +89,7 @@ open class InReplyToDetailsProvider : PreviewParameterProvider votes = persistentMapOf(), endTime = null, isEdited = false, + threadInfo = null, ), ).map { aInReplyToDetails( @@ -115,7 +117,7 @@ class InReplyToDetailsInformativeProvider : InReplyToDetailsProvider() { override val values: Sequence get() = sequenceOf( RedactedContent, - UnableToDecryptContent(UnableToDecryptContent.Data.Unknown), + UnableToDecryptContent(data = UnableToDecryptContent.Data.Unknown, threadInfo = null), ).map { aInReplyToDetails( eventContent = it, @@ -160,7 +162,7 @@ fun aProfileTimelineDetailsReady( displayName: String? = "Sender", displayNameAmbiguous: Boolean = false, avatarUrl: String? = null, -) = ProfileTimelineDetails.Ready( +) = ProfileDetails.Ready( displayName = displayName, displayNameAmbiguous = displayNameAmbiguous, avatarUrl = avatarUrl, diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadata.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadata.kt index 6c5e0d675a..4d0a0f045b 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadata.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadata.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToView.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToView.kt index 1b8e8bfd26..fc5d6577c7 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToView.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -41,7 +42,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail import io.element.android.libraries.matrix.ui.messages.sender.SenderName @@ -73,7 +74,7 @@ fun InReplyToView( @Composable private fun ReplyToReadyContent( senderId: UserId, - senderProfile: ProfileTimelineDetails, + senderProfile: ProfileDetails, metadata: InReplyToMetadata?, modifier: Modifier = Modifier, ) { diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/sender/SenderName.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/sender/SenderName.kt index d59e9cf24f..cdca0937c1 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/sender/SenderName.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/sender/SenderName.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -22,13 +23,13 @@ import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails // https://www.figma.com/file/Ni6Ii8YKtmXCKYNE90cC67/Timeline-(new)?type=design&node-id=917-80169&mode=design&t=A0CJCBbMqR8NOwUQ-0 @Composable fun SenderName( senderId: UserId, - senderProfile: ProfileTimelineDetails, + senderProfile: ProfileDetails, senderNameMode: SenderNameMode, modifier: Modifier = Modifier, ) { @@ -38,12 +39,12 @@ fun SenderName( verticalAlignment = Alignment.CenterVertically, ) { when (senderProfile) { - is ProfileTimelineDetails.Error, - ProfileTimelineDetails.Pending, - ProfileTimelineDetails.Unavailable -> { + is ProfileDetails.Error, + ProfileDetails.Pending, + ProfileDetails.Unavailable -> { MainText(text = senderId.value, mode = senderNameMode) } - is ProfileTimelineDetails.Ready -> { + is ProfileDetails.Ready -> { val displayName = senderProfile.displayName if (displayName.isNullOrEmpty()) { MainText(text = senderId.value, mode = senderNameMode) @@ -120,7 +121,7 @@ internal fun SenderNamePreview( ) = ElementPreview { SenderName( senderId = senderNameData.userId, - senderProfile = senderNameData.profileTimelineDetails, + senderProfile = senderNameData.profileDetails, senderNameMode = senderNameData.senderNameMode, ) } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/sender/SenderNameDataProvider.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/sender/SenderNameDataProvider.kt index b791ae2ca1..96ad91b869 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/sender/SenderNameDataProvider.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/sender/SenderNameDataProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,11 +11,11 @@ package io.element.android.libraries.matrix.ui.messages.sender import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails data class SenderNameData( val userId: UserId, - val profileTimelineDetails: ProfileTimelineDetails, + val profileDetails: ProfileDetails, val senderNameMode: SenderNameMode, ) @@ -37,7 +38,7 @@ open class SenderNameDataProvider : PreviewParameterProvider { SenderNameData( senderNameMode = senderNameMode, userId = UserId("@alice:${senderNameMode.javaClass.simpleName.lowercase()}"), - profileTimelineDetails = ProfileTimelineDetails.Unavailable, + profileDetails = ProfileDetails.Unavailable, ), ) } @@ -48,7 +49,7 @@ private fun aSenderNameData( displayNameAmbiguous: Boolean = false, ) = SenderNameData( userId = UserId("@alice:${senderNameMode.javaClass.simpleName.lowercase()}"), - profileTimelineDetails = ProfileTimelineDetails.Ready( + profileDetails = ProfileDetails.Ready( displayName = "Alice ${senderNameMode.javaClass.simpleName}", displayNameAmbiguous = displayNameAmbiguous, avatarUrl = null diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/sender/SenderNameMode.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/sender/SenderNameMode.kt index 0f1a6bb551..503c152553 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/sender/SenderNameMode.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/messages/sender/SenderNameMode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/InviteSender.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/InviteSender.kt index 778d535fc6..dd8655af26 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/InviteSender.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/InviteSender.kt @@ -1,15 +1,14 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.matrix.ui.model import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle @@ -21,7 +20,6 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.ui.R -@Immutable data class InviteSender( val userId: UserId, val displayName: String, @@ -31,7 +29,7 @@ data class InviteSender( @Composable fun annotatedString(): AnnotatedString { return stringResource(R.string.screen_invites_invited_you, displayName, userId.value).let { text -> - val senderNameStart = LocalContext.current.getString(R.string.screen_invites_invited_you).indexOf("%1\$s") + val senderNameStart = stringResource(R.string.screen_invites_invited_you).indexOf($$"%1$s") AnnotatedString( text = text, spanStyles = listOf( diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/MatrixUserExtensions.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/MatrixUserExtensions.kt index 72f69932aa..83a707347d 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/MatrixUserExtensions.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/MatrixUserExtensions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/RoomInfoExtension.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/RoomInfoExtension.kt index a6520d5580..3a03d8329b 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/RoomInfoExtension.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/RoomInfoExtension.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -20,16 +21,27 @@ fun RoomInfo.getAvatarData(size: AvatarSize) = AvatarData( size = size, ) +/** + * Returns the power level of the user in the room. + * If the user is a creator and [RoomInfo.privilegedCreatorRole] is true, returns the power level of [RoomMember.Role.Owner]. + * Otherwise, checks the room's power levels for the user's power level. + * If no specific power level is set for the user, defaults to 0. + */ +fun RoomInfo.powerLevelOf(userId: UserId): Long { + return if (privilegedCreatorRole && creators.contains(userId)) { + RoomMember.Role.Owner(isCreator = true).powerLevel + } else { + roomPowerLevels?.powerLevelOf(userId = userId) ?: 0L + } +} + /** * Returns the role of the user in the room. - * If the user is a creator, returns [RoomMember.Role.Owner]. + * If the user is a creator and [RoomInfo.privilegedCreatorRole] is true, returns [RoomMember.Role.Owner]. * Otherwise, checks the power levels and returns the corresponding role. * If no specific power level is set for the user, defaults to [RoomMember.Role.User]. */ fun RoomInfo.roleOf(userId: UserId): RoomMember.Role { - return if (creators.contains(userId)) { - RoomMember.Role.Owner(isCreator = true) - } else { - roomPowerLevels?.roleOf(userId) ?: RoomMember.Role.User - } + val powerLevel = powerLevelOf(userId = userId) + return RoomMember.Role.forPowerLevel(powerLevel) } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/RoomMemberExtension.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/RoomMemberExtension.kt index beba22507c..9ed27acc01 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/RoomMemberExtension.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/RoomMemberExtension.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/SelectRoomInfo.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/SelectRoomInfo.kt index 63a0b1cc77..49c8415fc7 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/SelectRoomInfo.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/SelectRoomInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/SpaceExtension.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/SpaceExtension.kt index 1c3111a2a2..195dfdb40e 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/SpaceExtension.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/model/SpaceExtension.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -31,7 +32,7 @@ val SpaceRoomVisibility.icon: ImageVector return when (this) { SpaceRoomVisibility.Private -> CompoundIcons.LockSolid() SpaceRoomVisibility.Public -> CompoundIcons.Public() - SpaceRoomVisibility.Restricted -> CompoundIcons.Workspace() + SpaceRoomVisibility.Restricted -> CompoundIcons.Space() } } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/LoadingRoomState.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/LoadingRoomState.kt index ef266f44f2..cbbe79b480 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/LoadingRoomState.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/LoadingRoomState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,6 +16,7 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.JoinedRoom import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asFlow @@ -38,16 +40,21 @@ open class LoadingRoomStateProvider : PreviewParameterProvider @Inject class LoadingRoomStateFlowFactory(private val matrixClient: MatrixClient) { - fun create(lifecycleScope: CoroutineScope, roomId: RoomId): StateFlow = - getJoinedRoomFlow(roomId) - .map { room -> - if (room != null) { - LoadingRoomState.Loaded(room) - } else { - LoadingRoomState.Error + fun create(lifecycleScope: CoroutineScope, roomId: RoomId, joinedRoom: JoinedRoom?): StateFlow { + return if (joinedRoom != null) { + MutableStateFlow(LoadingRoomState.Loaded(joinedRoom)) + } else { + getJoinedRoomFlow(roomId) + .map { room -> + if (room != null) { + LoadingRoomState.Loaded(room) + } else { + LoadingRoomState.Error + } } - } - .stateIn(lifecycleScope, SharingStarted.Eagerly, LoadingRoomState.Loading) + .stateIn(lifecycleScope, SharingStarted.Eagerly, LoadingRoomState.Loading) + } + } private fun getJoinedRoomFlow(roomId: RoomId): Flow = suspend { matrixClient.getJoinedRoom(roomId = roomId) diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembers.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembers.kt index 445fa156a1..098117af74 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembers.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembers.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt deleted file mode 100644 index ad97d30cfe..0000000000 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.matrix.ui.room - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.produceState -import io.element.android.libraries.matrix.api.room.BaseRoom -import io.element.android.libraries.matrix.api.room.MessageEventType -import io.element.android.libraries.matrix.api.room.RoomMember -import io.element.android.libraries.matrix.api.room.isDm -import io.element.android.libraries.matrix.api.room.powerlevels.canBan -import io.element.android.libraries.matrix.api.room.powerlevels.canHandleKnockRequests -import io.element.android.libraries.matrix.api.room.powerlevels.canInvite -import io.element.android.libraries.matrix.api.room.powerlevels.canKick -import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther -import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn -import io.element.android.libraries.matrix.api.room.powerlevels.canSendMessage -import io.element.android.libraries.matrix.ui.model.roleOf - -@Composable -fun BaseRoom.canSendMessageAsState(type: MessageEventType, updateKey: Long): State { - return produceState(initialValue = true, key1 = updateKey) { - value = canSendMessage(type).getOrElse { true } - } -} - -@Composable -fun BaseRoom.canInviteAsState(updateKey: Long): State { - return produceState(initialValue = false, key1 = updateKey) { - value = canInvite().getOrElse { false } - } -} - -@Composable -fun BaseRoom.canRedactOwnAsState(updateKey: Long): State { - return produceState(initialValue = false, key1 = updateKey) { - value = canRedactOwn().getOrElse { false } - } -} - -@Composable -fun BaseRoom.canRedactOtherAsState(updateKey: Long): State { - return produceState(initialValue = false, key1 = updateKey) { - value = canRedactOther().getOrElse { false } - } -} - -@Composable -fun BaseRoom.canCall(updateKey: Long): State { - return produceState(initialValue = false, key1 = updateKey) { - value = canUserJoinCall(sessionId).getOrElse { false } - } -} - -@Composable -fun BaseRoom.canPinUnpin(updateKey: Long): State { - return produceState(initialValue = false, key1 = updateKey) { - value = canUserPinUnpin(sessionId).getOrElse { false } - } -} - -@Composable -fun BaseRoom.isDmAsState(): State { - return produceState(initialValue = false) { - roomInfoFlow.collect { value = it.isDm } - } -} - -@Composable -fun BaseRoom.canKickAsState(updateKey: Long): State { - return produceState(initialValue = false, key1 = updateKey) { - value = canKick().getOrElse { false } - } -} - -@Composable -fun BaseRoom.canBanAsState(updateKey: Long): State { - return produceState(initialValue = false, key1 = updateKey) { - value = canBan().getOrElse { false } - } -} - -@Composable -fun BaseRoom.canHandleKnockRequestsAsState(updateKey: Long): State { - return produceState(initialValue = false, key1 = updateKey) { - value = canHandleKnockRequests().getOrElse { false } - } -} - -@Composable -fun BaseRoom.userPowerLevelAsState(updateKey: Long): State { - return produceState(initialValue = 0, key1 = updateKey) { - value = userRole(sessionId) - .getOrDefault(RoomMember.Role.User) - .powerLevel - } -} - -@Composable -fun BaseRoom.isOwnUserAdmin(): Boolean { - val roomInfo by roomInfoFlow.collectAsState() - val role = roomInfo.roleOf(sessionId) - return role == RoomMember.Role.Admin || role is RoomMember.Role.Owner -} - -@Composable -fun BaseRoom.rawName(): String? { - val roomInfo by roomInfoFlow.collectAsState() - return roomInfo.rawName -} - -@Composable -fun BaseRoom.topic(): String? { - val roomInfo by roomInfoFlow.collectAsState() - return roomInfo.topic -} - -@Composable -fun BaseRoom.avatarUrl(): String? { - val roomInfo by roomInfoFlow.collectAsState() - return roomInfo.avatarUrl -} diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/ObserveRoomMemberIdentityStateChange.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/ObserveRoomMemberIdentityStateChange.kt index 0e46463807..980c31bfa1 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/ObserveRoomMemberIdentityStateChange.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/ObserveRoomMemberIdentityStateChange.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/PowerLevelRoomMemberComparator.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/PowerLevelRoomMemberComparator.kt index cd0f8ad591..d5a7cfaa65 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/PowerLevelRoomMemberComparator.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/PowerLevelRoomMemberComparator.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/RoomMemberExtensions.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/RoomMemberExtensions.kt index 9bf9e15d1f..9e5d70dc5f 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/RoomMemberExtensions.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/RoomMemberExtensions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressField.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressField.kt index f97e2ff766..4166f37cd7 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressField.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressField.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidity.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidity.kt index fef36ccd20..005f5f8097 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidity.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidity.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidityEffect.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidityEffect.kt index e6ef9de2d3..d8b9b90c1d 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidityEffect.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/address/RoomAddressValidityEffect.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/safety/Avatars.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/safety/Avatars.kt index 7e8573a34c..5d36b308c7 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/safety/Avatars.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/safety/Avatars.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/main/res/values-hr/translations.xml b/libraries/matrixui/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..333d8fd7b7 --- /dev/null +++ b/libraries/matrixui/src/main/res/values-hr/translations.xml @@ -0,0 +1,7 @@ + + + "Pošalji pozivnicu" + "Želite li započeti razgovor s korisnikom %1$s?" + "Želite li poslati pozivnicu?" + "Pozvao vas je korisnik %1$s (%2$s)" + diff --git a/libraries/matrixui/src/main/res/values-uz/translations.xml b/libraries/matrixui/src/main/res/values-uz/translations.xml index 069f0b8372..63add2d3c0 100644 --- a/libraries/matrixui/src/main/res/values-uz/translations.xml +++ b/libraries/matrixui/src/main/res/values-uz/translations.xml @@ -1,4 +1,7 @@ + "Taklif yuborish" + "%1$s bilan chatni boshlashni xohlaysizmi?" + "Taklif yuborilsinmi?" "%1$s(%2$s ) sizni taklif qildi" diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/media/FakeLoggedInImageLoaderFactory.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/media/FakeLoggedInImageLoaderFactory.kt deleted file mode 100644 index 56ee9ee653..0000000000 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/media/FakeLoggedInImageLoaderFactory.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.matrix.ui.media - -import coil3.ImageLoader -import io.element.android.libraries.matrix.api.media.MatrixMediaLoader - -class FakeLoggedInImageLoaderFactory( - private val newImageLoaderLambda: (MatrixMediaLoader) -> ImageLoader -) : LoggedInImageLoaderFactory { - override fun newImageLoader(matrixMediaLoader: MatrixMediaLoader): ImageLoader { - return newImageLoaderLambda(matrixMediaLoader) - } -} diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocumentTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocumentTest.kt index ede520c15c..ad427999e2 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocumentTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/ToHtmlDocumentTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainTextTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainTextTest.kt index 9444081f7f..607f825401 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainTextTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/ToPlainTextTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -44,7 +45,7 @@ class ToPlainTextTest { val formattedBody = FormattedBody( format = MessageFormat.HTML, body = """ - Hello world + Hello formatted world
  • This is an unordered list.
  1. This is an ordered list.

@@ -52,7 +53,7 @@ class ToPlainTextTest { ) assertThat(formattedBody.toPlainText(permalinkParser = FakePermalinkParser())).isEqualTo( """ - Hello world + Hello formatted world • This is an unordered list. 1. This is an ordered list. """.trimIndent() diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailTest.kt index 6b9683b0e0..9507a30763 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToDetailTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,7 +18,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageTy import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser -import io.element.android.libraries.matrix.test.timeline.aProfileTimelineDetails +import io.element.android.libraries.matrix.test.timeline.aProfileDetails import io.element.android.libraries.matrix.test.timeline.item.event.aRoomMembershipContent import org.junit.Test @@ -46,7 +47,7 @@ class InReplyToDetailTest { val inReplyTo = InReplyTo.Ready( eventId = AN_EVENT_ID, senderId = A_USER_ID, - senderProfile = aProfileTimelineDetails(), + senderProfile = aProfileDetails(), content = aRoomMembershipContent( userId = A_USER_ID, change = MembershipChange.INVITED, @@ -64,7 +65,7 @@ class InReplyToDetailTest { val inReplyTo = InReplyTo.Ready( eventId = AN_EVENT_ID, senderId = A_USER_ID, - senderProfile = aProfileTimelineDetails(), + senderProfile = aProfileDetails(), content = MessageContent( body = "**Hello!**", inReplyTo = null, @@ -89,7 +90,7 @@ class InReplyToDetailTest { val inReplyTo = InReplyTo.Ready( eventId = AN_EVENT_ID, senderId = A_USER_ID, - senderProfile = aProfileTimelineDetails(), + senderProfile = aProfileDetails(), content = MessageContent( body = "**Hello!**", inReplyTo = null, diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt index c96e55c6fa..004e622211 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/messages/reply/InReplyToMetadataKtTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -28,7 +29,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageT import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessageType import io.element.android.libraries.matrix.api.timeline.item.event.OtherState import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent -import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails +import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent import io.element.android.libraries.matrix.api.timeline.item.event.StateContent import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent @@ -41,7 +42,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.media.aMediaSource import io.element.android.libraries.matrix.test.timeline.aMessageContent import io.element.android.libraries.matrix.test.timeline.aPollContent -import io.element.android.libraries.matrix.test.timeline.aProfileTimelineDetails +import io.element.android.libraries.matrix.test.timeline.aProfileDetails import io.element.android.libraries.matrix.test.timeline.item.event.aRoomMembershipContent import io.element.android.libraries.matrix.ui.components.A_BLUR_HASH import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo @@ -133,7 +134,8 @@ class InReplyToMetadataKtTest { filename = "filename", body = "body", info = anImageInfo(), - source = aMediaSource(url = "url") + source = aMediaSource(url = "url"), + threadInfo = null, ) ).metadata(hideImage = false) }.test { @@ -160,7 +162,8 @@ class InReplyToMetadataKtTest { filename = "filename", body = "body", info = anImageInfo(), - source = aMediaSource(url = "url") + source = aMediaSource(url = "url"), + threadInfo = null, ) ).metadata(hideImage = true) }.test { @@ -444,7 +447,10 @@ class InReplyToMetadataKtTest { fun `unable to decrypt content`() = runTest { moleculeFlow(RecompositionMode.Immediate) { anInReplyToDetailsReady( - eventContent = UnableToDecryptContent(UnableToDecryptContent.Data.Unknown) + eventContent = UnableToDecryptContent( + data = UnableToDecryptContent.Data.Unknown, + threadInfo = null, + ), ).metadata(hideImage = false) }.test { awaitItem().let { @@ -509,7 +515,7 @@ class InReplyToMetadataKtTest { fun `state content`() = runTest { moleculeFlow(RecompositionMode.Immediate) { anInReplyToDetailsReady( - eventContent = StateContent("", OtherState.RoomJoinRules) + eventContent = StateContent("", OtherState.RoomJoinRules(null)) ).metadata(hideImage = false) }.test { awaitItem().let { @@ -548,7 +554,7 @@ class InReplyToMetadataKtTest { private fun anInReplyToDetailsReady( eventId: EventId = AN_EVENT_ID, senderId: UserId = A_USER_ID, - senderProfile: ProfileTimelineDetails = aProfileTimelineDetails(), + senderProfile: ProfileDetails = aProfileDetails(), eventContent: EventContent? = aMessageContent(), textContent: String? = "textContent", ) = InReplyToDetails.Ready( diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/model/MatrixUserExtensionsTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/model/MatrixUserExtensionsTest.kt index 611616cdd7..dd9d801210 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/model/MatrixUserExtensionsTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/model/MatrixUserExtensionsTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/model/RoomInfoExtensionTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/model/RoomInfoExtensionTest.kt new file mode 100644 index 0000000000..c6dcc27bba --- /dev/null +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/model/RoomInfoExtensionTest.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.ui.model + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels +import io.element.android.libraries.matrix.test.A_USER_ID +import io.element.android.libraries.matrix.test.A_USER_ID_2 +import io.element.android.libraries.matrix.test.A_USER_ID_3 +import io.element.android.libraries.matrix.test.room.aRoomInfo +import io.element.android.libraries.matrix.test.room.defaultRoomPowerLevelValues +import kotlinx.collections.immutable.toImmutableMap +import org.junit.Test + +class RoomInfoExtensionTest { + @Test + fun `roleOf returns Owner for creator with privilegedCreatorRole true`() { + val roomInfo = aRoomInfo( + privilegedCreatorRole = true, + roomCreators = listOf(A_USER_ID), + ) + assertThat(roomInfo.roleOf(A_USER_ID)).isEqualTo(RoomMember.Role.Owner(isCreator = true)) + } + + @Test + fun `roleOf returns User for not creator with privilegedCreatorRole true`() { + val roomInfo = aRoomInfo( + privilegedCreatorRole = true, + roomCreators = listOf(A_USER_ID), + ) + assertThat(roomInfo.roleOf(A_USER_ID_2)).isEqualTo(RoomMember.Role.User) + } + + @Test + fun `roleOf returns User for creator with privilegedCreatorRole false`() { + val roomInfo = aRoomInfo( + privilegedCreatorRole = false, + roomCreators = listOf(A_USER_ID), + ) + assertThat(roomInfo.roleOf(A_USER_ID)).isEqualTo(RoomMember.Role.User) + } + + @Test + fun `roleOf returns role from the power level`() { + val roomInfo = aRoomInfo( + privilegedCreatorRole = false, + roomPowerLevels = RoomPowerLevels( + values = defaultRoomPowerLevelValues(), + users = mapOf( + A_USER_ID to 100L, // Admin + A_USER_ID_2 to 50L, // Moderator + A_USER_ID_3 to 0L, // User + ).toImmutableMap(), + ), + roomCreators = listOf(A_USER_ID), + ) + assertThat(roomInfo.roleOf(A_USER_ID)).isEqualTo(RoomMember.Role.Admin) + assertThat(roomInfo.roleOf(A_USER_ID_2)).isEqualTo(RoomMember.Role.Moderator) + assertThat(roomInfo.roleOf(A_USER_ID_3)).isEqualTo(RoomMember.Role.User) + } + + @Test + fun `roleOf returns User when the power level is null`() { + val roomInfo = aRoomInfo( + privilegedCreatorRole = false, + roomPowerLevels = null, + roomCreators = listOf(A_USER_ID), + ) + assertThat(roomInfo.roleOf(A_USER_ID)).isEqualTo(RoomMember.Role.User) + assertThat(roomInfo.roleOf(A_USER_ID_2)).isEqualTo(RoomMember.Role.User) + assertThat(roomInfo.roleOf(A_USER_ID_3)).isEqualTo(RoomMember.Role.User) + } +} diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/ObserveRoomMemberIdentityStateChangeTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/ObserveRoomMemberIdentityStateChangeTest.kt index 8abf5092e6..d8ea1575ba 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/ObserveRoomMemberIdentityStateChangeTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/ObserveRoomMemberIdentityStateChangeTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/PowerLevelBaseRoomMemberComparatorTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/PowerLevelRoomMemberComparatorTest.kt similarity index 92% rename from features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/PowerLevelBaseRoomMemberComparatorTest.kt rename to libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/PowerLevelRoomMemberComparatorTest.kt index 80ada2e7b6..d8240715b7 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/PowerLevelBaseRoomMemberComparatorTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/PowerLevelRoomMemberComparatorTest.kt @@ -1,11 +1,12 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.roomdetails.impl.members +package io.element.android.libraries.matrix.ui.room import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.test.A_USER_ID @@ -13,9 +14,10 @@ import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.A_USER_ID_3 import io.element.android.libraries.matrix.test.A_USER_ID_4 import io.element.android.libraries.matrix.test.A_USER_ID_5 +import io.element.android.libraries.matrix.test.room.aRoomMember import org.junit.Test -class PowerLevelBaseRoomMemberComparatorTest { +class PowerLevelRoomMemberComparatorTest { @Test fun `order is Admin, then Moderator, then User`() { val memberList = listOf( diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/RoomMembersTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/RoomMembersTest.kt index 6bf081cf1f..816ac0967e 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/RoomMembersTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/RoomMembersTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediapickers/api/build.gradle.kts b/libraries/mediapickers/api/build.gradle.kts index c130cd7900..5e3d78dbc4 100644 --- a/libraries/mediapickers/api/build.gradle.kts +++ b/libraries/mediapickers/api/build.gradle.kts @@ -1,9 +1,10 @@ import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,12 +14,12 @@ plugins { android { namespace = "io.element.android.libraries.mediapickers.api" - - dependencies { - implementation(projects.libraries.uiStrings) - implementation(projects.libraries.core) - implementation(projects.libraries.di) - - testCommonDependencies(libs) - } +} + +dependencies { + implementation(projects.libraries.uiStrings) + implementation(projects.libraries.core) + implementation(projects.libraries.di) + + testCommonDependencies(libs) } diff --git a/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerLauncher.kt b/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerLauncher.kt index 2fdcd1ef40..7a9b393a2c 100644 --- a/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerLauncher.kt +++ b/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerLauncher.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerProvider.kt b/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerProvider.kt index 4a2f26a4b3..7ae35e8218 100644 --- a/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerProvider.kt +++ b/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerType.kt b/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerType.kt index 4059aa0840..9c69e6448b 100644 --- a/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerType.kt +++ b/libraries/mediapickers/api/src/main/kotlin/io/element/android/libraries/mediapickers/api/PickerType.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediapickers/api/src/test/kotlin/io/element/android/libraries/mediapickers/PickerTypeTest.kt b/libraries/mediapickers/api/src/test/kotlin/io/element/android/libraries/mediapickers/PickerTypeTest.kt index 9f15041f91..6c7dbb7db7 100644 --- a/libraries/mediapickers/api/src/test/kotlin/io/element/android/libraries/mediapickers/PickerTypeTest.kt +++ b/libraries/mediapickers/api/src/test/kotlin/io/element/android/libraries/mediapickers/PickerTypeTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediapickers/impl/build.gradle.kts b/libraries/mediapickers/impl/build.gradle.kts index 6a90b95306..6ed9cd97dc 100644 --- a/libraries/mediapickers/impl/build.gradle.kts +++ b/libraries/mediapickers/impl/build.gradle.kts @@ -1,9 +1,10 @@ import extension.setupDependencyInjection /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediapickers/impl/src/main/kotlin/io/element/android/libraries/mediapickers/impl/DefaultPickerProvider.kt b/libraries/mediapickers/impl/src/main/kotlin/io/element/android/libraries/mediapickers/impl/DefaultPickerProvider.kt index 878c9ea865..1b3df9c426 100644 --- a/libraries/mediapickers/impl/src/main/kotlin/io/element/android/libraries/mediapickers/impl/DefaultPickerProvider.kt +++ b/libraries/mediapickers/impl/src/main/kotlin/io/element/android/libraries/mediapickers/impl/DefaultPickerProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,7 +18,6 @@ import androidx.compose.ui.platform.LocalInspectionMode import androidx.core.content.FileProvider import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.mediapickers.api.ComposePickerLauncher import io.element.android.libraries.mediapickers.api.NoOpPickerLauncher @@ -27,7 +27,6 @@ import io.element.android.libraries.mediapickers.api.PickerType import java.io.File @ContributesBinding(AppScope::class) -@Inject class DefaultPickerProvider( @ApplicationContext private val context: Context, ) : PickerProvider { diff --git a/libraries/mediapickers/test/build.gradle.kts b/libraries/mediapickers/test/build.gradle.kts index ee743bb63d..d7c2989871 100644 --- a/libraries/mediapickers/test/build.gradle.kts +++ b/libraries/mediapickers/test/build.gradle.kts @@ -1,9 +1,8 @@ -import extension.setupDependencyInjection - /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,14 +10,12 @@ plugins { id("io.element.android-compose-library") } -setupDependencyInjection() - android { namespace = "io.element.android.libraries.mediapickers.test" - - dependencies { - implementation(projects.libraries.core) - implementation(projects.libraries.di) - api(projects.libraries.mediapickers.api) - } +} + +dependencies { + implementation(projects.libraries.core) + implementation(projects.libraries.di) + api(projects.libraries.mediapickers.api) } diff --git a/libraries/mediapickers/test/src/main/kotlin/io/element/android/libraries/mediapickers/test/FakePickerProvider.kt b/libraries/mediapickers/test/src/main/kotlin/io/element/android/libraries/mediapickers/test/FakePickerProvider.kt index 3a279868bf..98cbcec1ea 100644 --- a/libraries/mediapickers/test/src/main/kotlin/io/element/android/libraries/mediapickers/test/FakePickerProvider.kt +++ b/libraries/mediapickers/test/src/main/kotlin/io/element/android/libraries/mediapickers/test/FakePickerProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaplayer/api/build.gradle.kts b/libraries/mediaplayer/api/build.gradle.kts index f096738c6f..eb4a6cfaaf 100644 --- a/libraries/mediaplayer/api/build.gradle.kts +++ b/libraries/mediaplayer/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/mediaplayer/api/src/main/kotlin/io/element/android/libraries/mediaplayer/api/MediaPlayer.kt b/libraries/mediaplayer/api/src/main/kotlin/io/element/android/libraries/mediaplayer/api/MediaPlayer.kt index b10dd44b7f..7087235fec 100644 --- a/libraries/mediaplayer/api/src/main/kotlin/io/element/android/libraries/mediaplayer/api/MediaPlayer.kt +++ b/libraries/mediaplayer/api/src/main/kotlin/io/element/android/libraries/mediaplayer/api/MediaPlayer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaplayer/impl/build.gradle.kts b/libraries/mediaplayer/impl/build.gradle.kts index bd6382f86f..aa9140df35 100644 --- a/libraries/mediaplayer/impl/build.gradle.kts +++ b/libraries/mediaplayer/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayer.kt b/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayer.kt index 0767aea3ef..12b36207a6 100644 --- a/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayer.kt +++ b/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,6 @@ import androidx.media3.common.C import androidx.media3.common.MediaItem import androidx.media3.common.Player import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.audio.api.AudioFocus import io.element.android.libraries.audio.api.AudioFocusRequester @@ -36,7 +36,6 @@ import kotlin.time.Duration.Companion.seconds */ @ContributesBinding(RoomScope::class) @SingleIn(RoomScope::class) -@Inject class DefaultMediaPlayer( private val player: SimplePlayer, @SessionCoroutineScope diff --git a/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/SimplePlayer.kt b/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/SimplePlayer.kt index b6aff2570a..17bfe682e3 100644 --- a/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/SimplePlayer.kt +++ b/libraries/mediaplayer/impl/src/main/kotlin/io/element/android/libraries/mediaplayer/impl/SimplePlayer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayerTest.kt b/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayerTest.kt index a91a204378..f7a748b636 100644 --- a/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayerTest.kt +++ b/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayerTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/FakeSimplePlayer.kt b/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/FakeSimplePlayer.kt index 34078ab4e8..c1e2dc9763 100644 --- a/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/FakeSimplePlayer.kt +++ b/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/FakeSimplePlayer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaplayer/test/build.gradle.kts b/libraries/mediaplayer/test/build.gradle.kts index c58a8bf106..e2f1c8ac29 100644 --- a/libraries/mediaplayer/test/build.gradle.kts +++ b/libraries/mediaplayer/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaplayer/test/src/main/kotlin/io/element/android/libraries/mediaplayer/test/FakeMediaPlayer.kt b/libraries/mediaplayer/test/src/main/kotlin/io/element/android/libraries/mediaplayer/test/FakeMediaPlayer.kt index 0dcd655424..1d1fd07ebd 100644 --- a/libraries/mediaplayer/test/src/main/kotlin/io/element/android/libraries/mediaplayer/test/FakeMediaPlayer.kt +++ b/libraries/mediaplayer/test/src/main/kotlin/io/element/android/libraries/mediaplayer/test/FakeMediaPlayer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaupload/api/build.gradle.kts b/libraries/mediaupload/api/build.gradle.kts index bea5398372..a9f876c67f 100644 --- a/libraries/mediaupload/api/build.gradle.kts +++ b/libraries/mediaupload/api/build.gradle.kts @@ -1,10 +1,8 @@ -import extension.setupDependencyInjection -import extension.testCommonDependencies - /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,8 +10,6 @@ plugins { id("io.element.android-library") } -setupDependencyInjection() - android { namespace = "io.element.android.libraries.mediaupload.api" } @@ -26,9 +22,4 @@ dependencies { api(projects.libraries.matrix.api) api(projects.libraries.preferences.api) implementation(libs.coroutines.core) - - testCommonDependencies(libs) - testImplementation(projects.libraries.matrix.test) - testImplementation(projects.libraries.preferences.test) - testImplementation(projects.libraries.mediaupload.test) } diff --git a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MaxUploadSizeProvider.kt b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MaxUploadSizeProvider.kt index 743a684b11..a5462a6caf 100644 --- a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MaxUploadSizeProvider.kt +++ b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MaxUploadSizeProvider.kt @@ -1,23 +1,16 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.mediaupload.api -import dev.zacsweers.metro.Inject -import io.element.android.libraries.matrix.api.MatrixClient - /** * Provides the maximum upload size allowed by the Matrix server. */ -@Inject -class MaxUploadSizeProvider( - private val matrixClient: MatrixClient, -) { - suspend fun getMaxUploadSize(): Result { - return matrixClient.getMaxFileUploadSize() - } +fun interface MaxUploadSizeProvider { + suspend fun getMaxUploadSize(): Result } diff --git a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaOptimizationConfig.kt b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaOptimizationConfig.kt index bfa6e427c9..561a5af79d 100644 --- a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaOptimizationConfig.kt +++ b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaOptimizationConfig.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaOptimizationConfigProvider.kt b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaOptimizationConfigProvider.kt index eb13022ae3..976a7057f2 100644 --- a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaOptimizationConfigProvider.kt +++ b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaOptimizationConfigProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaPreProcessor.kt b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaPreProcessor.kt index 9f2f91f8c9..c2bf1e5f5d 100644 --- a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaPreProcessor.kt +++ b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaPreProcessor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 7592f46fa6..628e760c1e 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 @@ -1,80 +1,49 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.mediaupload.api import android.net.Uri -import dev.zacsweers.metro.Assisted -import dev.zacsweers.metro.AssistedFactory -import dev.zacsweers.metro.AssistedInject -import io.element.android.libraries.androidutils.hash.hash -import io.element.android.libraries.core.extensions.flatMap -import io.element.android.libraries.core.extensions.flatMapCatching import io.element.android.libraries.matrix.api.core.EventId -import io.element.android.libraries.matrix.api.media.MediaUploadHandler -import io.element.android.libraries.matrix.api.room.CreateTimelineParams import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.timeline.Timeline -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Job -import timber.log.Timber -import java.io.File -import java.util.concurrent.ConcurrentHashMap -@AssistedInject -class MediaSender( - private val preProcessor: MediaPreProcessor, - private val room: JoinedRoom, - @Assisted private val timelineMode: Timeline.Mode, - private val mediaOptimizationConfigProvider: MediaOptimizationConfigProvider, -) { - @AssistedFactory - interface Factory { - fun create( - timelineMode: Timeline.Mode, - ): MediaSender - } +fun interface MediaSenderFactory { + /** + * Create a [MediaSender] for the given [Timeline.Mode], in the Room Scope. + */ + fun create( + timelineMode: Timeline.Mode, + ): MediaSender +} - private val ongoingUploadJobs = ConcurrentHashMap() - val hasOngoingMediaUploads get() = ongoingUploadJobs.isNotEmpty() +fun interface MediaSenderRoomFactory { + /** + * Create a [MediaSender] for the given [JoinedRoom], with timeline mode Live. + */ + fun create( + room: JoinedRoom, + ): MediaSender +} +interface MediaSender { suspend fun preProcessMedia( uri: Uri, mimeType: String, mediaOptimizationConfig: MediaOptimizationConfig, - ): Result { - Timber.d("Pre-processing media | uri: ${mediaId(uri)} | mimeType: $mimeType") - return preProcessor - .process( - uri = uri, - mimeType = mimeType, - deleteOriginal = false, - mediaOptimizationConfig = mediaOptimizationConfig, - ) - } + ): Result suspend fun sendPreProcessedMedia( mediaUploadInfo: MediaUploadInfo, caption: String?, formattedCaption: String?, inReplyToEventId: EventId?, - ): Result { - val mediaLogId = mediaId(mediaUploadInfo.file) - return getTimeline().flatMap { - Timber.d("Started sending media $mediaLogId using timeline: ${it.mode}") - it.sendMedia( - uploadInfo = mediaUploadInfo, - caption = caption, - formattedCaption = formattedCaption, - inReplyToEventId = inReplyToEventId, - ) - } - .handleSendResult(mediaLogId) - } + ): Result suspend fun sendMedia( uri: Uri, @@ -83,147 +52,14 @@ class MediaSender( formattedCaption: String? = null, inReplyToEventId: EventId? = null, mediaOptimizationConfig: MediaOptimizationConfig, - ): Result { - return preProcessor - .process( - uri = uri, - mimeType = mimeType, - deleteOriginal = false, - mediaOptimizationConfig = mediaOptimizationConfig, - ) - .flatMapCatching { info -> - getTimeline().getOrThrow().sendMedia( - uploadInfo = info, - caption = caption, - formattedCaption = formattedCaption, - inReplyToEventId = inReplyToEventId, - ) - } - .handleSendResult(mediaId(uri)) - } + ): Result suspend fun sendVoiceMessage( uri: Uri, mimeType: String, waveForm: List, inReplyToEventId: EventId? = null, - ): Result { - return preProcessor - .process( - uri = uri, - mimeType = mimeType, - deleteOriginal = true, - mediaOptimizationConfig = mediaOptimizationConfigProvider.get(), - ) - .flatMapCatching { info -> - val audioInfo = (info as MediaUploadInfo.Audio).audioInfo - val newInfo = MediaUploadInfo.VoiceMessage( - file = info.file, - audioInfo = audioInfo, - waveform = waveForm, - ) - getTimeline().getOrThrow().sendMedia( - uploadInfo = newInfo, - caption = null, - formattedCaption = null, - inReplyToEventId = inReplyToEventId, - ) - } - .handleSendResult(mediaId(uri)) - } + ): Result - private fun Result.handleSendResult(mediaId: String) = this - .onFailure { error -> - val job = ongoingUploadJobs.remove(Job) - Timber.e(error, "Sending media $mediaId failed. Removing ongoing upload job. Total: ${ongoingUploadJobs.size}") - if (error !is CancellationException) { - job?.cancel() - } - } - .onSuccess { - Timber.d("Sent media $mediaId successfully. Removing ongoing upload job. Total: ${ongoingUploadJobs.size}") - ongoingUploadJobs.remove(Job) - } - - private suspend fun Timeline.sendMedia( - uploadInfo: MediaUploadInfo, - caption: String?, - formattedCaption: String?, - inReplyToEventId: EventId?, - ): Result { - val handler = when (uploadInfo) { - is MediaUploadInfo.Image -> { - sendImage( - file = uploadInfo.file, - thumbnailFile = uploadInfo.thumbnailFile, - imageInfo = uploadInfo.imageInfo, - caption = caption, - formattedCaption = formattedCaption, - inReplyToEventId = inReplyToEventId, - ) - } - is MediaUploadInfo.Video -> { - sendVideo( - file = uploadInfo.file, - thumbnailFile = uploadInfo.thumbnailFile, - videoInfo = uploadInfo.videoInfo, - caption = caption, - formattedCaption = formattedCaption, - inReplyToEventId = inReplyToEventId, - ) - } - is MediaUploadInfo.Audio -> { - sendAudio( - file = uploadInfo.file, - audioInfo = uploadInfo.audioInfo, - caption = caption, - formattedCaption = formattedCaption, - inReplyToEventId = inReplyToEventId, - ) - } - is MediaUploadInfo.VoiceMessage -> { - sendVoiceMessage( - file = uploadInfo.file, - audioInfo = uploadInfo.audioInfo, - waveform = uploadInfo.waveform, - inReplyToEventId = inReplyToEventId, - ) - } - is MediaUploadInfo.AnyFile -> { - sendFile( - file = uploadInfo.file, - fileInfo = uploadInfo.fileInfo, - caption = caption, - formattedCaption = formattedCaption, - inReplyToEventId = inReplyToEventId, - ) - } - } - - // We handle the cancellations here manually, so we suppress the warning - @Suppress("RunCatchingNotAllowed") - return handler - .mapCatching { uploadHandler -> - Timber.d("Added ongoing upload job, total: ${ongoingUploadJobs.size + 1}") - ongoingUploadJobs[Job] = uploadHandler - uploadHandler.await() - } - } - - private suspend fun getTimeline(): Result { - return when (timelineMode) { - is Timeline.Mode.Thread -> { - room.createTimeline(CreateTimelineParams.Threaded(threadRootEventId = timelineMode.threadRootId)) - } - else -> Result.success(room.liveTimeline) - } - } - - /** - * Clean up any temporary files or resources used during the media processing. - */ - fun cleanUp() = preProcessor.cleanUp() + fun cleanUp() } - -private fun mediaId(uri: Uri?): String = uri?.path.orEmpty().hash() -private fun mediaId(file: File): String = file.path.orEmpty().hash() diff --git a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaUploadInfo.kt b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaUploadInfo.kt index 4a656ce5d3..f0082a4d1b 100644 --- a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaUploadInfo.kt +++ b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaUploadInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaupload/impl/build.gradle.kts b/libraries/mediaupload/impl/build.gradle.kts index ffd01aac2f..827366f556 100644 --- a/libraries/mediaupload/impl/build.gradle.kts +++ b/libraries/mediaupload/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -30,6 +31,7 @@ dependencies { implementation(projects.libraries.androidutils) implementation(projects.libraries.core) implementation(projects.libraries.di) + implementation(projects.libraries.featureflag.api) implementation(projects.libraries.matrix.api) implementation(projects.services.toolbox.api) implementation(libs.androidx.exifinterface) @@ -41,4 +43,7 @@ dependencies { testCommonDependencies(libs) testImplementation(projects.services.toolbox.test) + testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.preferences.test) + testImplementation(projects.libraries.mediaupload.test) } diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessor.kt index 73c6e9c64a..2b1883619e 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessor.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,7 +15,6 @@ import android.net.Uri import androidx.exifinterface.media.ExifInterface import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.androidutils.file.TemporaryUriDeleter import io.element.android.libraries.androidutils.file.createTmpFile import io.element.android.libraries.androidutils.file.getFileName @@ -50,7 +50,6 @@ import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds @ContributesBinding(AppScope::class) -@Inject class AndroidMediaPreProcessor( @ApplicationContext private val context: Context, private val thumbnailFactory: ThumbnailFactory, diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/DefaultMaxUploadSizeProvider.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/DefaultMaxUploadSizeProvider.kt new file mode 100644 index 0000000000..0cd43bca01 --- /dev/null +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/DefaultMaxUploadSizeProvider.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.mediaupload.impl + +import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.mediaupload.api.MaxUploadSizeProvider + +/** + * Provides the maximum upload size allowed by the Matrix server. + */ +@ContributesBinding(SessionScope::class) +class DefaultMaxUploadSizeProvider( + private val matrixClient: MatrixClient, +) : MaxUploadSizeProvider { + override suspend fun getMaxUploadSize(): Result { + return matrixClient.getMaxFileUploadSize() + } +} diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/DefaultMediaOptimizationConfigProvider.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/DefaultMediaOptimizationConfigProvider.kt index f3e89730bd..363263e5bf 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/DefaultMediaOptimizationConfigProvider.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/DefaultMediaOptimizationConfigProvider.kt @@ -1,27 +1,37 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.mediaupload.impl import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider import io.element.android.libraries.preferences.api.store.SessionPreferencesStore +import io.element.android.libraries.preferences.api.store.VideoCompressionPreset import kotlinx.coroutines.flow.first @ContributesBinding(SessionScope::class) -@Inject class DefaultMediaOptimizationConfigProvider( private val sessionPreferencesStore: SessionPreferencesStore, + private val featureFlagsService: FeatureFlagService, ) : MediaOptimizationConfigProvider { - override suspend fun get(): MediaOptimizationConfig = MediaOptimizationConfig( - compressImages = sessionPreferencesStore.doesOptimizeImages().first(), - videoCompressionPreset = sessionPreferencesStore.getVideoCompressionPreset().first(), - ) + override suspend fun get(): MediaOptimizationConfig { + val compressImages = sessionPreferencesStore.doesOptimizeImages().first() + return MediaOptimizationConfig( + compressImages = compressImages, + videoCompressionPreset = if (featureFlagsService.isFeatureEnabled(FeatureFlags.SelectableMediaQuality)) { + sessionPreferencesStore.getVideoCompressionPreset().first() + } else { + if (compressImages) VideoCompressionPreset.STANDARD else VideoCompressionPreset.HIGH + }, + ) + } } diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/DefaultMediaSender.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/DefaultMediaSender.kt new file mode 100644 index 0000000000..ea2cac2951 --- /dev/null +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/DefaultMediaSender.kt @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.mediaupload.impl + +import android.net.Uri +import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.androidutils.hash.hash +import io.element.android.libraries.core.extensions.flatMap +import io.element.android.libraries.core.extensions.flatMapCatching +import io.element.android.libraries.di.RoomScope +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.media.MediaUploadHandler +import io.element.android.libraries.matrix.api.room.CreateTimelineParams +import io.element.android.libraries.matrix.api.room.JoinedRoom +import io.element.android.libraries.matrix.api.timeline.Timeline +import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig +import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider +import io.element.android.libraries.mediaupload.api.MediaPreProcessor +import io.element.android.libraries.mediaupload.api.MediaSender +import io.element.android.libraries.mediaupload.api.MediaSenderFactory +import io.element.android.libraries.mediaupload.api.MediaSenderRoomFactory +import io.element.android.libraries.mediaupload.api.MediaUploadInfo +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Job +import timber.log.Timber +import java.io.File +import java.util.concurrent.ConcurrentHashMap + +@ContributesBinding(RoomScope::class) +class DefaultMediaSenderFactory( + private val preProcessor: MediaPreProcessor, + private val room: JoinedRoom, + private val mediaOptimizationConfigProvider: MediaOptimizationConfigProvider, +) : MediaSenderFactory { + override fun create( + timelineMode: Timeline.Mode, + ): MediaSender { + return DefaultMediaSender( + preProcessor = preProcessor, + room = room, + timelineMode = timelineMode, + mediaOptimizationConfigProvider = mediaOptimizationConfigProvider, + ) + } +} + +@ContributesBinding(SessionScope::class) +class DefaultMediaSenderRoomFactory( + private val preProcessor: MediaPreProcessor, + private val mediaOptimizationConfigProvider: MediaOptimizationConfigProvider, +) : MediaSenderRoomFactory { + override fun create( + room: JoinedRoom, + ): MediaSender { + return DefaultMediaSender( + preProcessor = preProcessor, + room = room, + timelineMode = Timeline.Mode.Live, + mediaOptimizationConfigProvider = mediaOptimizationConfigProvider, + ) + } +} + +class DefaultMediaSender( + private val preProcessor: MediaPreProcessor, + private val room: JoinedRoom, + private val timelineMode: Timeline.Mode, + private val mediaOptimizationConfigProvider: MediaOptimizationConfigProvider, +) : MediaSender { + private val ongoingUploadJobs = ConcurrentHashMap() + val hasOngoingMediaUploads get() = ongoingUploadJobs.isNotEmpty() + + override suspend fun preProcessMedia( + uri: Uri, + mimeType: String, + mediaOptimizationConfig: MediaOptimizationConfig, + ): Result { + Timber.d("Pre-processing media | uri: ${mediaId(uri)} | mimeType: $mimeType") + return preProcessor + .process( + uri = uri, + mimeType = mimeType, + deleteOriginal = false, + mediaOptimizationConfig = mediaOptimizationConfig, + ) + } + + override suspend fun sendPreProcessedMedia( + mediaUploadInfo: MediaUploadInfo, + caption: String?, + formattedCaption: String?, + inReplyToEventId: EventId?, + ): Result { + val mediaLogId = mediaId(mediaUploadInfo.file) + return getTimeline().flatMap { + Timber.d("Started sending media $mediaLogId using timeline: ${it.mode}") + it.sendMedia( + uploadInfo = mediaUploadInfo, + caption = caption, + formattedCaption = formattedCaption, + inReplyToEventId = inReplyToEventId, + ) + } + .handleSendResult(mediaLogId) + } + + override suspend fun sendMedia( + uri: Uri, + mimeType: String, + caption: String?, + formattedCaption: String?, + inReplyToEventId: EventId?, + mediaOptimizationConfig: MediaOptimizationConfig, + ): Result { + return preProcessor + .process( + uri = uri, + mimeType = mimeType, + deleteOriginal = false, + mediaOptimizationConfig = mediaOptimizationConfig, + ) + .flatMapCatching { info -> + getTimeline().getOrThrow().sendMedia( + uploadInfo = info, + caption = caption, + formattedCaption = formattedCaption, + inReplyToEventId = inReplyToEventId, + ) + } + .handleSendResult(mediaId(uri)) + } + + override suspend fun sendVoiceMessage( + uri: Uri, + mimeType: String, + waveForm: List, + inReplyToEventId: EventId?, + ): Result { + return preProcessor + .process( + uri = uri, + mimeType = mimeType, + deleteOriginal = true, + mediaOptimizationConfig = mediaOptimizationConfigProvider.get(), + ) + .flatMapCatching { info -> + val audioInfo = (info as MediaUploadInfo.Audio).audioInfo + val newInfo = MediaUploadInfo.VoiceMessage( + file = info.file, + audioInfo = audioInfo, + waveform = waveForm, + ) + getTimeline().getOrThrow().sendMedia( + uploadInfo = newInfo, + caption = null, + formattedCaption = null, + inReplyToEventId = inReplyToEventId, + ) + } + .handleSendResult(mediaId(uri)) + } + + private fun Result.handleSendResult(mediaId: String) = this + .onFailure { error -> + val job = ongoingUploadJobs.remove(Job) + Timber.e(error, "Sending media $mediaId failed. Removing ongoing upload job. Total: ${ongoingUploadJobs.size}") + if (error !is CancellationException) { + job?.cancel() + } + } + .onSuccess { + Timber.d("Sent media $mediaId successfully. Removing ongoing upload job. Total: ${ongoingUploadJobs.size}") + ongoingUploadJobs.remove(Job) + } + + private suspend fun Timeline.sendMedia( + uploadInfo: MediaUploadInfo, + caption: String?, + formattedCaption: String?, + inReplyToEventId: EventId?, + ): Result { + val handler = when (uploadInfo) { + is MediaUploadInfo.Image -> { + sendImage( + file = uploadInfo.file, + thumbnailFile = uploadInfo.thumbnailFile, + imageInfo = uploadInfo.imageInfo, + caption = caption, + formattedCaption = formattedCaption, + inReplyToEventId = inReplyToEventId, + ) + } + is MediaUploadInfo.Video -> { + sendVideo( + file = uploadInfo.file, + thumbnailFile = uploadInfo.thumbnailFile, + videoInfo = uploadInfo.videoInfo, + caption = caption, + formattedCaption = formattedCaption, + inReplyToEventId = inReplyToEventId, + ) + } + is MediaUploadInfo.Audio -> { + sendAudio( + file = uploadInfo.file, + audioInfo = uploadInfo.audioInfo, + caption = caption, + formattedCaption = formattedCaption, + inReplyToEventId = inReplyToEventId, + ) + } + is MediaUploadInfo.VoiceMessage -> { + sendVoiceMessage( + file = uploadInfo.file, + audioInfo = uploadInfo.audioInfo, + waveform = uploadInfo.waveform, + inReplyToEventId = inReplyToEventId, + ) + } + is MediaUploadInfo.AnyFile -> { + sendFile( + file = uploadInfo.file, + fileInfo = uploadInfo.fileInfo, + caption = caption, + formattedCaption = formattedCaption, + inReplyToEventId = inReplyToEventId, + ) + } + } + + // We handle the cancellations here manually, so we suppress the warning + @Suppress("RunCatchingNotAllowed") + return handler + .mapCatching { uploadHandler -> + Timber.d("Added ongoing upload job, total: ${ongoingUploadJobs.size + 1}") + ongoingUploadJobs[Job] = uploadHandler + uploadHandler.await() + } + } + + private suspend fun getTimeline(): Result { + return when (timelineMode) { + is Timeline.Mode.Thread -> { + room.createTimeline(CreateTimelineParams.Threaded(threadRootEventId = timelineMode.threadRootId)) + } + else -> Result.success(room.liveTimeline) + } + } + + /** + * Clean up any temporary files or resources used during the media processing. + */ + override fun cleanUp() = preProcessor.cleanUp() +} + +private fun mediaId(uri: Uri?): String = uri?.path.orEmpty().hash() +private fun mediaId(file: File): String = file.path.orEmpty().hash() diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ImageCompressor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ImageCompressor.kt index 3bcb367575..ab32afcf4c 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ImageCompressor.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ImageCompressor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/MimeTypeUtil.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/MimeTypeUtil.kt index e6437e5668..9d4c11cbea 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/MimeTypeUtil.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/MimeTypeUtil.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ThumbnailFactory.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ThumbnailFactory.kt index c1c90302ba..6c6c711226 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ThumbnailFactory.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ThumbnailFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt index 3c289ad942..11cb193aeb 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressorConfig.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressorConfig.kt index 0337713f2a..152678261c 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressorConfig.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressorConfig.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessorTest.kt b/libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessorTest.kt index 3327a28e82..f4b4e7d4a5 100644 --- a/libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessorTest.kt +++ b/libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessorTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/Asset.kt b/libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/Asset.kt index 4c4aaae02e..4dad31a784 100644 --- a/libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/Asset.kt +++ b/libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/Asset.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt b/libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/DefaultMediaSenderTest.kt similarity index 89% rename from libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt rename to libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/DefaultMediaSenderTest.kt index e73bff4d7f..139042804f 100644 --- a/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt +++ b/libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/DefaultMediaSenderTest.kt @@ -1,11 +1,12 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.libraries.mediaupload.api +package io.element.android.libraries.mediaupload.impl import android.net.Uri import com.google.common.truth.Truth.assertThat @@ -18,6 +19,9 @@ import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.timeline.FakeTimeline +import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig +import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider +import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.preferences.api.store.VideoCompressionPreset import io.element.android.tests.testutils.lambda.lambdaRecorder @@ -32,7 +36,7 @@ import org.robolectric.RobolectricTestRunner import java.io.File @RunWith(RobolectricTestRunner::class) -class MediaSenderTest { +class DefaultMediaSenderTest { private val mediaOptimizationConfig = MediaOptimizationConfig( compressImages = true, videoCompressionPreset = VideoCompressionPreset.STANDARD, @@ -41,7 +45,7 @@ class MediaSenderTest { @Test fun `given an attachment when sending it the preprocessor always runs`() = runTest { val preProcessor = FakeMediaPreProcessor() - val sender = createMediaSender( + val sender = createDefaultMediaSender( preProcessor = preProcessor, room = FakeJoinedRoom( liveTimeline = FakeTimeline().apply { @@ -76,7 +80,7 @@ class MediaSenderTest { sendImageLambda = sendImageResult }, ) - val sender = createMediaSender(room = room) + val sender = createDefaultMediaSender(room = room) val uri = Uri.parse("content://image.jpg") sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg, mediaOptimizationConfig = mediaOptimizationConfig) @@ -87,7 +91,7 @@ class MediaSenderTest { val preProcessor = FakeMediaPreProcessor().apply { givenResult(Result.failure(Exception())) } - val sender = createMediaSender(preProcessor) + val sender = createDefaultMediaSender(preProcessor) val uri = Uri.parse("content://image.jpg") val result = sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg, mediaOptimizationConfig = mediaOptimizationConfig) @@ -109,7 +113,7 @@ class MediaSenderTest { sendImageLambda = sendImageResult }, ) - val sender = createMediaSender( + val sender = createDefaultMediaSender( preProcessor = preProcessor, room = room, ) @@ -132,7 +136,7 @@ class MediaSenderTest { sendFileLambda = sendFileResult }, ) - val sender = createMediaSender(room = room) + val sender = createDefaultMediaSender(room = room) val sendJob = launch { val uri = Uri.parse("content://image.jpg") sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg, mediaOptimizationConfig = mediaOptimizationConfig) @@ -154,11 +158,11 @@ class MediaSenderTest { sendFileResult.assertions().isCalledOnce() } - private fun createMediaSender( + private fun createDefaultMediaSender( preProcessor: MediaPreProcessor = FakeMediaPreProcessor(), room: JoinedRoom = FakeJoinedRoom(), mediaOptimizationConfigProvider: MediaOptimizationConfigProvider = MediaOptimizationConfigProvider { mediaOptimizationConfig }, - ) = MediaSender( + ) = DefaultMediaSender( preProcessor = preProcessor, room = room, timelineMode = Timeline.Mode.Live, diff --git a/libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressorConfigFactoryTest.kt b/libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressorConfigFactoryTest.kt index 943e89011f..041d05ae1e 100644 --- a/libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressorConfigFactoryTest.kt +++ b/libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressorConfigFactoryTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaupload/test/build.gradle.kts b/libraries/mediaupload/test/build.gradle.kts index 23a5f9a20f..7e729089c7 100644 --- a/libraries/mediaupload/test/build.gradle.kts +++ b/libraries/mediaupload/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaupload/test/src/main/kotlin/io/element/android/libraries/mediaupload/test/FakeMediaOptimizationConfigProvider.kt b/libraries/mediaupload/test/src/main/kotlin/io/element/android/libraries/mediaupload/test/FakeMediaOptimizationConfigProvider.kt index 6e5d73d305..f22129e5fa 100644 --- a/libraries/mediaupload/test/src/main/kotlin/io/element/android/libraries/mediaupload/test/FakeMediaOptimizationConfigProvider.kt +++ b/libraries/mediaupload/test/src/main/kotlin/io/element/android/libraries/mediaupload/test/FakeMediaOptimizationConfigProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaupload/test/src/main/kotlin/io/element/android/libraries/mediaupload/test/FakeMediaPreProcessor.kt b/libraries/mediaupload/test/src/main/kotlin/io/element/android/libraries/mediaupload/test/FakeMediaPreProcessor.kt index cda951ff50..c07ebb6ec9 100644 --- a/libraries/mediaupload/test/src/main/kotlin/io/element/android/libraries/mediaupload/test/FakeMediaPreProcessor.kt +++ b/libraries/mediaupload/test/src/main/kotlin/io/element/android/libraries/mediaupload/test/FakeMediaPreProcessor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaupload/test/src/main/kotlin/io/element/android/libraries/mediaupload/test/FakeMediaSender.kt b/libraries/mediaupload/test/src/main/kotlin/io/element/android/libraries/mediaupload/test/FakeMediaSender.kt new file mode 100644 index 0000000000..1713f7b5ea --- /dev/null +++ b/libraries/mediaupload/test/src/main/kotlin/io/element/android/libraries/mediaupload/test/FakeMediaSender.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.mediaupload.test + +import android.net.Uri +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.mediaupload.api.MediaOptimizationConfig +import io.element.android.libraries.mediaupload.api.MediaSender +import io.element.android.libraries.mediaupload.api.MediaUploadInfo +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeMediaSender( + private val preProcessMediaResult: () -> Result = { lambdaError() }, + private val sendPreProcessedMediaResult: () -> Result = { lambdaError() }, + private val sendMediaResult: () -> Result = { lambdaError() }, + private val sendVoiceMessageResult: () -> Result = { lambdaError() }, + private val cleanUpResult: () -> Unit = { lambdaError() }, +) : MediaSender { + override suspend fun preProcessMedia( + uri: Uri, + mimeType: String, + mediaOptimizationConfig: MediaOptimizationConfig, + ): Result { + return preProcessMediaResult() + } + + override suspend fun sendPreProcessedMedia( + mediaUploadInfo: MediaUploadInfo, + caption: String?, + formattedCaption: String?, + inReplyToEventId: EventId?, + ): Result { + return sendPreProcessedMediaResult() + } + + override suspend fun sendMedia( + uri: Uri, + mimeType: String, + caption: String?, + formattedCaption: String?, + inReplyToEventId: EventId?, + mediaOptimizationConfig: MediaOptimizationConfig, + ): Result { + return sendMediaResult() + } + + override suspend fun sendVoiceMessage( + uri: Uri, + mimeType: String, + waveForm: List, + inReplyToEventId: EventId?, + ): Result { + return sendVoiceMessageResult() + } + + override fun cleanUp() { + cleanUpResult() + } +} diff --git a/libraries/mediaviewer/api/build.gradle.kts b/libraries/mediaviewer/api/build.gradle.kts index 0f8aad9561..59760d2652 100644 --- a/libraries/mediaviewer/api/build.gradle.kts +++ b/libraries/mediaviewer/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaGalleryEntryPoint.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaGalleryEntryPoint.kt index 841e82f195..906604fd82 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaGalleryEntryPoint.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaGalleryEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,15 +15,15 @@ import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.matrix.api.core.EventId interface MediaGalleryEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - - interface NodeBuilder { - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: Callback, + ): Node interface Callback : Plugin { fun onBackClick() - fun onViewInTimeline(eventId: EventId) + fun viewInTimeline(eventId: EventId) + fun forward(eventId: EventId, fromPinnedEvents: Boolean) } } diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt index da7944e5d1..74b479dc8c 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaViewerEntryPoint.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaViewerEntryPoint.kt index 906283c457..536930b968 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaViewerEntryPoint.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/MediaViewerEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -19,18 +20,19 @@ import io.element.android.libraries.matrix.api.timeline.Timeline import kotlinx.parcelize.Parcelize interface MediaViewerEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder + fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: Params, + callback: Callback, + ): Node - interface NodeBuilder { - fun callback(callback: Callback): NodeBuilder - fun params(params: Params): NodeBuilder - fun avatar(filename: String, avatarUrl: String): NodeBuilder - fun build(): Node - } + fun createParamsForAvatar(filename: String, avatarUrl: String): Params interface Callback : Plugin { fun onDone() - fun onViewInTimeline(eventId: EventId) + fun viewInTimeline(eventId: EventId) + fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) } data class Params( diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/helper/FileExtensionAndSizeFormatter.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/helper/FileExtensionAndSizeFormatter.kt index a6ead62d27..fc40bca8dd 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/helper/FileExtensionAndSizeFormatter.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/helper/FileExtensionAndSizeFormatter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMedia.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMedia.kt index 930c7361e8..0041930d93 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMedia.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMedia.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaFactory.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaFactory.kt index ac10bfecd7..383f985c4f 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaFactory.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaRenderer.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaRenderer.kt index 0706fb5a0a..fb7f26101e 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaRenderer.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/local/LocalMediaRenderer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/util/FileExtensionExtractor.kt b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/util/FileExtensionExtractor.kt index 440381f1d3..094b4513d8 100644 --- a/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/util/FileExtensionExtractor.kt +++ b/libraries/mediaviewer/api/src/main/kotlin/io/element/android/libraries/mediaviewer/api/util/FileExtensionExtractor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/build.gradle.kts b/libraries/mediaviewer/impl/build.gradle.kts index 4af1c54f46..9c2342ecd9 100644 --- a/libraries/mediaviewer/impl/build.gradle.kts +++ b/libraries/mediaviewer/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { @@ -32,6 +33,7 @@ dependencies { implementation(libs.vanniktech.blurhash) implementation(libs.telephoto.flick) + implementation(projects.features.enterprise.api) implementation(projects.features.viewfolder.api) implementation(projects.libraries.androidutils) implementation(projects.libraries.architecture) @@ -42,7 +44,7 @@ dependencies { implementation(projects.libraries.designsystem) implementation(projects.libraries.featureflag.api) implementation(projects.libraries.matrix.api) - implementation(projects.libraries.matrixui) + implementation(projects.libraries.matrixmedia.api) implementation(projects.libraries.uiStrings) implementation(projects.libraries.voiceplayer.api) implementation(projects.services.toolbox.api) @@ -54,10 +56,12 @@ dependencies { implementation(projects.libraries.matrix.api) testCommonDependencies(libs, true) + testImplementation(projects.features.enterprise.test) testImplementation(projects.libraries.audio.test) testImplementation(projects.libraries.dateformatter.test) testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.matrixui) testImplementation(projects.libraries.mediaviewer.test) testImplementation(projects.services.toolbox.test) testImplementation(libs.coroutines.core) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaGalleryEntryPoint.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaGalleryEntryPoint.kt index 0b6c2cc6cb..433a53ae9f 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaGalleryEntryPoint.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaGalleryEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,29 +10,22 @@ package io.element.android.libraries.mediaviewer.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.architecture.createNode import io.element.android.libraries.mediaviewer.api.MediaGalleryEntryPoint import io.element.android.libraries.mediaviewer.impl.gallery.root.MediaGalleryFlowNode @ContributesBinding(AppScope::class) -@Inject class DefaultMediaGalleryEntryPoint : MediaGalleryEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): MediaGalleryEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : MediaGalleryEntryPoint.NodeBuilder { - override fun callback(callback: MediaGalleryEntryPoint.Callback): MediaGalleryEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: MediaGalleryEntryPoint.Callback, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf(callback), + ) } } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt index d2800ef9b8..e1e112ecfa 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,10 +10,8 @@ package io.element.android.libraries.mediaviewer.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.architecture.createNode import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.matrix.api.core.UserId @@ -22,54 +21,43 @@ import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint import io.element.android.libraries.mediaviewer.impl.viewer.MediaViewerNode @ContributesBinding(AppScope::class) -@Inject class DefaultMediaViewerEntryPoint : MediaViewerEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): MediaViewerEntryPoint.NodeBuilder { - val plugins = ArrayList() + override fun createParamsForAvatar(filename: String, avatarUrl: String): MediaViewerEntryPoint.Params { + // We need to fake the MimeType here for the viewer to work. + val mimeType = MimeTypes.Images + return MediaViewerEntryPoint.Params( + mode = MediaViewerEntryPoint.MediaViewerMode.SingleMedia, + eventId = null, + mediaInfo = MediaInfo( + filename = filename, + fileSize = null, + caption = null, + mimeType = mimeType, + formattedFileSize = "", + fileExtension = "", + senderId = UserId("@dummy:server.org"), + senderName = null, + senderAvatar = null, + dateSent = null, + dateSentFull = null, + waveform = null, + duration = null, + ), + mediaSource = MediaSource(url = avatarUrl), + thumbnailSource = null, + canShowInfo = false, + ) + } - return object : MediaViewerEntryPoint.NodeBuilder { - override fun callback(callback: MediaViewerEntryPoint.Callback): MediaViewerEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun params(params: MediaViewerEntryPoint.Params): MediaViewerEntryPoint.NodeBuilder { - plugins += params - return this - } - - override fun avatar(filename: String, avatarUrl: String): MediaViewerEntryPoint.NodeBuilder { - // We need to fake the MimeType here for the viewer to work. - val mimeType = MimeTypes.Images - return params( - MediaViewerEntryPoint.Params( - mode = MediaViewerEntryPoint.MediaViewerMode.SingleMedia, - eventId = null, - mediaInfo = MediaInfo( - filename = filename, - fileSize = null, - caption = null, - mimeType = mimeType, - formattedFileSize = "", - fileExtension = "", - senderId = UserId("@dummy:server.org"), - senderName = null, - senderAvatar = null, - dateSent = null, - dateSentFull = null, - waveform = null, - duration = null, - ), - mediaSource = MediaSource(url = avatarUrl), - thumbnailSource = null, - canShowInfo = false, - ) - ) - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: MediaViewerEntryPoint.Params, + callback: MediaViewerEntryPoint.Callback, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf(params, callback), + ) } } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactory.kt index d6d9128869..edbf9dc51e 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/EventItemFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/FocusedTimelineMediaGalleryDataSourceFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/FocusedTimelineMediaGalleryDataSourceFactory.kt index 5d998d25fd..5e127b6738 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/FocusedTimelineMediaGalleryDataSourceFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/FocusedTimelineMediaGalleryDataSourceFactory.kt @@ -1,14 +1,14 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.mediaviewer.impl.datasource import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.room.JoinedRoom @@ -23,7 +23,6 @@ fun interface FocusedTimelineMediaGalleryDataSourceFactory { } @ContributesBinding(RoomScope::class) -@Inject class DefaultFocusedTimelineMediaGalleryDataSourceFactory( private val room: JoinedRoom, private val timelineMediaItemsFactory: TimelineMediaItemsFactory, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt index f40bb08a86..722e14a790 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaGalleryDataSource.kt @@ -1,14 +1,14 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.mediaviewer.impl.datasource import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.di.RoomScope @@ -39,7 +39,6 @@ interface MediaGalleryDataSource { @SingleIn(RoomScope::class) @ContributesBinding(RoomScope::class) -@Inject class TimelineMediaGalleryDataSource( private val room: BaseRoom, private val mediaTimeline: MediaTimeline, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaItemsPostProcessor.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaItemsPostProcessor.kt index 102fc611ef..d95c3b4490 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaItemsPostProcessor.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaItemsPostProcessor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaTimeline.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaTimeline.kt index c3543ba129..d20c620f36 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaTimeline.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaTimeline.kt @@ -1,14 +1,14 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.mediaviewer.impl.datasource import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.EventId @@ -36,7 +36,6 @@ interface MediaTimeline { */ @SingleIn(RoomScope::class) @ContributesBinding(RoomScope::class) -@Inject class LiveMediaTimeline( private val room: JoinedRoom, ) : MediaTimeline { diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/TimelineMediaItemsFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/TimelineMediaItemsFactory.kt index a9ee0a89e2..d2f78a7249 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/TimelineMediaItemsFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/TimelineMediaItemsFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/VirtualItemFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/VirtualItemFactory.kt index 125981d4f8..1ffc12bb18 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/VirtualItemFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/VirtualItemFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaBottomSheetState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaBottomSheetState.kt index ac2b7b761d..7cd4dee318 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaBottomSheetState.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaBottomSheetState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheet.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheet.kt index 121f23a79b..3850f3a0f8 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheet.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheet.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt index 22c29c590b..a6c30796af 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheet.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -49,6 +50,7 @@ fun MediaDetailsBottomSheet( state: MediaBottomSheetState.MediaDetailsBottomSheetState, onViewInTimeline: (EventId) -> Unit, onShare: (EventId) -> Unit, + onForward: (EventId) -> Unit, onDownload: (EventId) -> Unit, onDelete: (EventId) -> Unit, onDismiss: () -> Unit, @@ -102,6 +104,14 @@ fun MediaDetailsBottomSheet( onShare(state.eventId) } ) + ListItem( + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Forward())), + headlineContent = { Text(stringResource(CommonStrings.action_forward)) }, + style = ListItemStyle.Primary, + onClick = { + onForward(state.eventId) + } + ) ListItem( leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Download())), headlineContent = { Text(stringResource(CommonStrings.action_save)) }, @@ -216,6 +226,7 @@ internal fun MediaDetailsBottomSheetPreview() = ElementPreview { state = aMediaDetailsBottomSheetState(), onViewInTimeline = {}, onShare = {}, + onForward = {}, onDownload = {}, onDelete = {}, onDismiss = {}, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/Preview.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/Preview.kt index 2d83ff40f3..a152a32091 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/Preview.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/details/Preview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryEvents.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryEvents.kt index df7d82c7b2..2bf4f6b37d 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryEvents.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,8 +17,9 @@ import io.element.android.libraries.mediaviewer.impl.model.MediaItem sealed interface MediaGalleryEvents { data class ChangeMode(val mode: MediaGalleryMode) : MediaGalleryEvents data class LoadMore(val direction: Timeline.PaginationDirection) : MediaGalleryEvents - data class Share(val eventId: EventId?) : MediaGalleryEvents - data class SaveOnDisk(val eventId: EventId?) : MediaGalleryEvents + data class Share(val eventId: EventId) : MediaGalleryEvents + data class Forward(val eventId: EventId) : MediaGalleryEvents + data class SaveOnDisk(val eventId: EventId) : MediaGalleryEvents data class OpenInfo(val mediaItem: MediaItem.Event) : MediaGalleryEvents data class ViewInTimeline(val eventId: EventId) : MediaGalleryEvents diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNavigator.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNavigator.kt index 0195e7f39c..3e60b4e804 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNavigator.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNavigator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,4 +12,5 @@ import io.element.android.libraries.matrix.api.core.EventId interface MediaGalleryNavigator { fun onViewInTimelineClick(eventId: EventId) + fun onForwardClick(eventId: EventId) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNode.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNode.kt index 06a3c6a58f..ca1fd62b6b 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNode.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,10 +14,10 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.mediaviewer.impl.gallery.di.LocalMediaItemPresenterFactories @@ -38,26 +39,19 @@ class MediaGalleryNode( interface Callback : Plugin { fun onBackClick() - fun onItemClick(item: MediaItem.Event) - fun onViewInTimeline(eventId: EventId) + fun showItem(item: MediaItem.Event) + fun viewInTimeline(eventId: EventId) + fun forward(eventId: EventId) } - private fun onBackClick() { - plugins().forEach { - it.onBackClick() - } - } + private val callback: Callback = callback() override fun onViewInTimelineClick(eventId: EventId) { - plugins().forEach { - it.onViewInTimeline(eventId) - } + callback.viewInTimeline(eventId) } - private fun onItemClick(item: MediaItem.Event) { - plugins().forEach { - it.onItemClick(item) - } + override fun onForwardClick(eventId: EventId) { + callback.forward(eventId) } @Composable @@ -68,8 +62,8 @@ class MediaGalleryNode( val state = presenter.present() MediaGalleryView( state = state, - onBackClick = ::onBackClick, - onItemClick = ::onItemClick, + onBackClick = callback::onBackClick, + onItemClick = callback::showItem, modifier = modifier, ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt index ba3d4d5e1f..cc26e69c33 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -29,8 +30,7 @@ import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarM import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.room.BaseRoom -import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther -import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn +import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory import io.element.android.libraries.mediaviewer.impl.datasource.MediaGalleryDataSource @@ -38,8 +38,10 @@ import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetSta import io.element.android.libraries.mediaviewer.impl.local.LocalMediaActions import io.element.android.libraries.mediaviewer.impl.model.GroupedMediaItems import io.element.android.libraries.mediaviewer.impl.model.MediaItem +import io.element.android.libraries.mediaviewer.impl.model.MediaPermissions import io.element.android.libraries.mediaviewer.impl.model.eventId import io.element.android.libraries.mediaviewer.impl.model.mediaInfo +import io.element.android.libraries.mediaviewer.impl.model.mediaPermissions import io.element.android.libraries.mediaviewer.impl.model.mediaSource import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.coroutines.launch @@ -79,10 +81,14 @@ class MediaGalleryPresenter( mediaGalleryDataSource.start() } + val permissions by room.permissionsAsState(MediaPermissions.DEFAULT) { perms -> + perms.mediaPermissions() + } + val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() localMediaActions.Configure() - fun handleEvents(event: MediaGalleryEvents) { + fun handleEvent(event: MediaGalleryEvents) { when (event) { is MediaGalleryEvents.ChangeMode -> { mode = event.mode @@ -105,6 +111,10 @@ class MediaGalleryPresenter( share(it) } } + is MediaGalleryEvents.Forward -> { + mediaBottomSheetState = MediaBottomSheetState.Hidden + navigator.onForwardClick(event.eventId) + } is MediaGalleryEvents.ViewInTimeline -> { mediaBottomSheetState = MediaBottomSheetState.Hidden navigator.onViewInTimelineClick(event.eventId) @@ -114,8 +124,8 @@ class MediaGalleryPresenter( eventId = event.mediaItem.eventId(), canDelete = when (event.mediaItem.mediaInfo().senderId) { null -> false - room.sessionId -> room.canRedactOwn().getOrElse { false } && event.mediaItem.eventId() != null - else -> room.canRedactOther().getOrElse { false } && event.mediaItem.eventId() != null + room.sessionId -> permissions.canRedactOwn && event.mediaItem.eventId() != null + else -> permissions.canRedactOther && event.mediaItem.eventId() != null }, mediaInfo = event.mediaItem.mediaInfo(), thumbnailSource = when (event.mediaItem) { @@ -146,7 +156,7 @@ class MediaGalleryPresenter( groupedMediaItems = groupedMediaItems, mediaBottomSheetState = mediaBottomSheetState, snackbarMessage = snackbarMessage, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryState.kt index 62877368d7..897e5d1e97 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryState.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt index aedea1eb75..a19f810e45 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,7 @@ package io.element.android.libraries.mediaviewer.impl.gallery import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.designsystem.components.media.aWaveForm +import io.element.android.libraries.designsystem.components.media.WaveFormSamples import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState import io.element.android.libraries.mediaviewer.impl.details.aMediaDetailsBottomSheetState @@ -71,7 +72,7 @@ open class MediaGalleryStateProvider : PreviewParameterProvider state.eventSink(MediaGalleryEvents.Share(eventId)) }, + onForward = { eventId -> + state.eventSink(MediaGalleryEvents.Forward(eventId)) + }, onDownload = { eventId -> state.eventSink(MediaGalleryEvents.SaveOnDisk(eventId)) }, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/FakeTimelineItemPresenterFactories.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/FakeTimelineItemPresenterFactories.kt index fe3a65ff4a..f38262ec4b 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/FakeTimelineItemPresenterFactories.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/FakeTimelineItemPresenterFactories.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/LocalMediaItemPresenterFactories.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/LocalMediaItemPresenterFactories.kt index 1b67559f30..04003d8f9b 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/LocalMediaItemPresenterFactories.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/LocalMediaItemPresenterFactories.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemEventContentKey.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemEventContentKey.kt index eb04d88787..38ea708471 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemEventContentKey.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemEventContentKey.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemPresenterFactories.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemPresenterFactories.kt index eaf0f5a49e..b74debc177 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemPresenterFactories.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemPresenterFactories.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemPresenterFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemPresenterFactory.kt index 0b3c77eda1..6553a0a361 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemPresenterFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/di/MediaItemPresenterFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/root/MediaGalleryFlowNode.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/root/MediaGalleryFlowNode.kt index e617829ad0..5d6a48c25b 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/root/MediaGalleryFlowNode.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/root/MediaGalleryFlowNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,13 +14,13 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.libraries.architecture.BackstackWithOverlayBox import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.overlay.Overlay import io.element.android.libraries.architecture.overlay.operation.hide @@ -44,7 +45,7 @@ import kotlinx.parcelize.Parcelize class MediaGalleryFlowNode( @Assisted buildContext: BuildContext, @Assisted plugins: List, - private val mediaViewerEntryPoint: MediaViewerEntryPoint + private val mediaViewerEntryPoint: MediaViewerEntryPoint, ) : BaseFlowNode( backstack = BackStack( initialElement = NavTarget.Root, @@ -70,31 +71,25 @@ class MediaGalleryFlowNode( ) : NavTarget } - private fun onBackClick() { - plugins().forEach { - it.onBackClick() - } - } - - private fun onViewInTimeline(eventId: EventId) { - plugins().forEach { - it.onViewInTimeline(eventId) - } - } + private val callback: MediaGalleryEntryPoint.Callback = callback() override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Root -> { val callback = object : MediaGalleryNode.Callback { override fun onBackClick() { - this@MediaGalleryFlowNode.onBackClick() + callback.onBackClick() } - override fun onViewInTimeline(eventId: EventId) { - this@MediaGalleryFlowNode.onViewInTimeline(eventId) + override fun viewInTimeline(eventId: EventId) { + callback.viewInTimeline(eventId) } - override fun onItemClick(item: MediaItem.Event) { + override fun forward(eventId: EventId) { + callback.forward(eventId, fromPinnedEvents = false) + } + + override fun showItem(item: MediaItem.Event) { val mode = when (item) { is MediaItem.Audio, is MediaItem.Voice, @@ -121,23 +116,28 @@ class MediaGalleryFlowNode( overlay.hide() } - override fun onViewInTimeline(eventId: EventId) { - this@MediaGalleryFlowNode.onViewInTimeline(eventId) + override fun viewInTimeline(eventId: EventId) { + callback.viewInTimeline(eventId) + } + + override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) { + // Need to go to the parent because of the overlay + callback.forward(eventId, fromPinnedEvents) } } - mediaViewerEntryPoint.nodeBuilder(this, buildContext) - .params( - MediaViewerEntryPoint.Params( - mode = navTarget.mode, - eventId = navTarget.eventId, - mediaInfo = navTarget.mediaInfo, - mediaSource = navTarget.mediaSource, - thumbnailSource = navTarget.thumbnailSource, - canShowInfo = true, - ) - ) - .callback(callback) - .build() + mediaViewerEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + params = MediaViewerEntryPoint.Params( + mode = navTarget.mode, + eventId = navTarget.eventId, + mediaInfo = navTarget.mediaInfo, + mediaSource = navTarget.mediaSource, + thumbnailSource = navTarget.thumbnailSource, + canShowInfo = true, + ), + callback = callback, + ) } } } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt index 7229bdb9c1..ed4e59d631 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/AudioItemView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/CaptionView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/CaptionView.kt index 2c6cf286a9..cb137cf2ed 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/CaptionView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/CaptionView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/DateItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/DateItemView.kt index ff59a67b49..b04b25a53e 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/DateItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/DateItemView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt index 2b01b734e8..7738e4c1e4 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/FileItemView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/ImageItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/ImageItemView.kt index 05e6ccf86a..65256d6110 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/ImageItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/ImageItemView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemAudioProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemAudioProvider.kt index ccbc9079cd..25e8a88dfe 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemAudioProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemAudioProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemDateSeparatorProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemDateSeparatorProvider.kt index 13d49a9919..05705a9251 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemDateSeparatorProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemDateSeparatorProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemFileProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemFileProvider.kt index 74042472f9..7419ae3d44 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemFileProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemFileProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVideoProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVideoProvider.kt index 34eef52479..c9c4666904 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVideoProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVideoProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVoiceProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVoiceProvider.kt index 8353e5bfca..4b23d28795 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVoiceProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/MediaItemVoiceProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt index 4bfe58badd..7d9e634687 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VideoItemView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt index bd8daa69e1..878c8ffd3a 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/ui/VoiceItemView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/voice/VoiceMessagePresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/voice/VoiceMessagePresenter.kt index 830a405110..4eac5a5ce3 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/voice/VoiceMessagePresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/voice/VoiceMessagePresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt index 6e7508a947..c7a01ea494 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -31,7 +32,6 @@ import androidx.core.content.PermissionChecker import androidx.core.net.toFile import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.androidutils.system.startInstallFromSourceIntent import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.extensions.runCatchingExceptions @@ -47,7 +47,6 @@ import java.io.FileOutputStream import java.io.InputStream @ContributesBinding(AppScope::class) -@Inject class AndroidLocalMediaActions( @ApplicationContext private val context: Context, private val coroutineDispatchers: CoroutineDispatchers, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt index 4854c1e2f9..05cbe40f36 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,7 +13,6 @@ import android.net.Uri import androidx.core.net.toUri import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.androidutils.file.getFileName import io.element.android.libraries.androidutils.file.getFileSize import io.element.android.libraries.androidutils.file.getMimeType @@ -28,7 +28,6 @@ import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractor @ContributesBinding(AppScope::class) -@Inject class AndroidLocalMediaFactory( @ApplicationContext private val context: Context, private val fileSizeFormatter: FileSizeFormatter, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/DefaultLocalMediaRenderer.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/DefaultLocalMediaRenderer.kt index 1e69abecc1..96450d99bf 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/DefaultLocalMediaRenderer.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/DefaultLocalMediaRenderer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,7 +13,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.viewfolder.api.TextFileViewer import io.element.android.libraries.audio.api.AudioFocus import io.element.android.libraries.mediaviewer.api.local.LocalMedia @@ -22,7 +22,6 @@ import me.saket.telephoto.zoomable.ZoomSpec import me.saket.telephoto.zoomable.rememberZoomableState @ContributesBinding(AppScope::class) -@Inject class DefaultLocalMediaRenderer( private val textFileViewer: TextFileViewer, private val audioFocus: AudioFocus, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaActions.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaActions.kt index afef2505ff..8aee16c19a 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaActions.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaActions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt index 1ce9bd33eb..cc276b52c8 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaViewState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaViewState.kt index da03000fd7..08b3ec5eca 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaViewState.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/LocalMediaViewState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt index 54a6127d9d..1bf952d4a0 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaAudioView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaInfoAudioProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaInfoAudioProvider.kt index fd6e338b5e..02607e3e0e 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaInfoAudioProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaInfoAudioProvider.kt @@ -1,14 +1,15 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.mediaviewer.impl.local.audio import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.libraries.designsystem.components.media.aWaveForm +import io.element.android.libraries.designsystem.components.media.WaveFormSamples import io.element.android.libraries.mediaviewer.api.MediaInfo import io.element.android.libraries.mediaviewer.api.anAudioMediaInfo @@ -17,7 +18,7 @@ open class MediaInfoAudioProvider : PreviewParameterProvider { get() = sequenceOf( anAudioMediaInfo(), anAudioMediaInfo( - waveForm = aWaveForm(), + waveForm = WaveFormSamples.realisticWaveForm, ), ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaMetadata.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaMetadata.kt index 7905ae8b15..4c267a54dc 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaMetadata.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/audio/MediaMetadata.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/file/MediaFileView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/file/MediaFileView.kt index 29e743c7b0..912ea88e99 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/file/MediaFileView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/file/MediaFileView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/file/MediaInfoFileProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/file/MediaInfoFileProvider.kt index 42a693fc77..8e41a4809a 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/file/MediaInfoFileProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/file/MediaInfoFileProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/image/MediaImageView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/image/MediaImageView.kt index 4deae2dddc..306a18b4d6 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/image/MediaImageView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/image/MediaImageView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/MediaPdfView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/MediaPdfView.kt index 0d090e33f2..82024bde90 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/MediaPdfView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/MediaPdfView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/ParcelFileDescriptorFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/ParcelFileDescriptorFactory.kt index 9f6d10b6b0..67949c9931 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/ParcelFileDescriptorFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/ParcelFileDescriptorFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfPage.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfPage.kt index ffa787d0e9..7163ccb290 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfPage.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfPage.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfRendererManager.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfRendererManager.kt index c7562e98e7..3b2e16960c 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfRendererManager.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfRendererManager.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfViewer.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfViewer.kt index 3493c277cb..395a4429ab 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfViewer.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfViewer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfViewerState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfViewerState.kt index 024184747c..83d111ef67 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfViewerState.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfViewerState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerExtensions.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerExtensions.kt index 115e3ca72b..4e17b77336 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerExtensions.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerExtensions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerFactory.kt index 83ef2d4ac2..d0043c657a 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerForPreview.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerForPreview.kt index 19d90bf109..51a44d49fb 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerForPreview.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/ExoPlayerForPreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -37,6 +38,8 @@ import androidx.media3.common.VideoSize import androidx.media3.common.text.CueGroup import androidx.media3.common.util.Clock import androidx.media3.common.util.Size +import androidx.media3.exoplayer.CodecParameters +import androidx.media3.exoplayer.CodecParametersChangeListener import androidx.media3.exoplayer.DecoderCounters import androidx.media3.exoplayer.ExoPlaybackException import androidx.media3.exoplayer.ExoPlayer @@ -159,6 +162,8 @@ class ExoPlayerForPreview( override fun getAudioAttributes(): AudioAttributes = throw NotImplementedError() override fun setVolume(volume: Float) = throw NotImplementedError() override fun getVolume(): Float = throw NotImplementedError() + override fun mute() {} + override fun unmute() {} override fun clearVideoSurface() {} override fun clearVideoSurface(surface: Surface?) {} override fun setVideoSurface(surface: Surface?) {} @@ -191,6 +196,7 @@ class ExoPlayerForPreview( override fun getRendererCount(): Int = throw NotImplementedError() override fun getRendererType(index: Int): Int = throw NotImplementedError() override fun getRenderer(index: Int): Renderer = throw NotImplementedError() + override fun getSecondaryRenderer(index: Int): Renderer? = throw NotImplementedError() override fun getTrackSelector(): TrackSelector? = throw NotImplementedError() override fun getCurrentTrackGroups(): TrackGroupArray = throw NotImplementedError() override fun getCurrentTrackSelections(): TrackSelectionArray = throw NotImplementedError() @@ -215,6 +221,7 @@ class ExoPlayerForPreview( override fun setAuxEffectInfo(auxEffectInfo: AuxEffectInfo) {} override fun clearAuxEffectInfo() {} override fun setPreferredAudioDevice(audioDeviceInfo: AudioDeviceInfo?) {} + override fun setVirtualDeviceId(virtualDeviceId: Int) {} override fun setSkipSilenceEnabled(skipSilenceEnabled: Boolean) {} override fun getSkipSilenceEnabled(): Boolean = throw NotImplementedError() override fun setScrubbingModeEnabled(scrubbingModeEnabled: Boolean) {} @@ -233,6 +240,9 @@ class ExoPlayerForPreview( override fun createMessage(target: PlayerMessage.Target): PlayerMessage = throw NotImplementedError() override fun setSeekParameters(seekParameters: SeekParameters?) {} override fun getSeekParameters(): SeekParameters = throw NotImplementedError() + override fun setSeekBackIncrementMs(seekBackIncrementMs: Long) {} + override fun setSeekForwardIncrementMs(seekForwardIncrementMs: Long) {} + override fun setMaxSeekToPreviousPositionMs(maxSeekToPreviousPositionMs: Long) {} override fun setForegroundMode(foregroundMode: Boolean) {} override fun setPauseAtEndOfMediaItems(pauseAtEndOfMediaItems: Boolean) {} override fun getPauseAtEndOfMediaItems(): Boolean = throw NotImplementedError() @@ -248,4 +258,10 @@ class ExoPlayerForPreview( override fun isTunnelingEnabled(): Boolean = throw NotImplementedError() override fun isReleased(): Boolean = throw NotImplementedError() override fun setImageOutput(imageOutput: ImageOutput?) {} + override fun setAudioCodecParameters(codecParameters: CodecParameters) {} + override fun addAudioCodecParametersChangeListener(listener: CodecParametersChangeListener, keys: List) {} + override fun removeAudioCodecParametersChangeListener(listener: CodecParametersChangeListener) {} + override fun setVideoCodecParameters(codecParameters: CodecParameters) {} + override fun addVideoCodecParametersChangeListener(listener: CodecParametersChangeListener, keys: List) {} + override fun removeVideoCodecParametersChangeListener(listener: CodecParametersChangeListener) {} } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerState.kt index 6160b6758c..b7fe22a27a 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerState.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerStateProvider.kt index 2ce66a79e3..bcdef4713a 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerView.kt index e4add62aab..7c09c02867 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/player/MediaPlayerControllerView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TextFileContentProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TextFileContentProvider.kt index 00480f2d42..d92e400495 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TextFileContentProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TextFileContentProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TextFileView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TextFileView.kt index 4c4429b775..f4984470dd 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TextFileView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TextFileView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt index 0ce19aaa84..65148f37ff 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/model/GroupedMediaItems.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/model/GroupedMediaItems.kt index 448426f374..1662afc624 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/model/GroupedMediaItems.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/model/GroupedMediaItems.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/model/MediaItem.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/model/MediaItem.kt index bf89486964..90708db802 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/model/MediaItem.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/model/MediaItem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/model/MediaItemFactories.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/model/MediaItemFactories.kt index b04973322a..be73787828 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/model/MediaItemFactories.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/model/MediaItemFactories.kt @@ -1,13 +1,14 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.mediaviewer.impl.model -import io.element.android.libraries.designsystem.components.media.aWaveForm +import io.element.android.libraries.designsystem.components.media.WaveFormSamples import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.api.core.UserId @@ -91,7 +92,7 @@ fun aMediaItemVoice( filename: String = "filename.ogg", caption: String? = null, duration: String? = "1:23", - waveform: List = aWaveForm(), + waveform: List = WaveFormSamples.realisticWaveForm, ): MediaItem.Voice { return MediaItem.Voice( id = id, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/model/MediaPermissions.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/model/MediaPermissions.kt new file mode 100644 index 0000000000..4a111965e9 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/model/MediaPermissions.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.model + +import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions + +data class MediaPermissions( + val canRedactOwn: Boolean, + val canRedactOther: Boolean, +) { + companion object { + val DEFAULT = MediaPermissions( + canRedactOwn = false, + canRedactOther = false, + ) + } +} + +fun RoomPermissions.mediaPermissions(): MediaPermissions { + return MediaPermissions( + canRedactOwn = canOwnUserRedactOwn(), + canRedactOther = canOwnUserRedactOther(), + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/util/Colors.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/util/Colors.kt index 5105f1ba9b..4d110b5d5c 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/util/Colors.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/util/Colors.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/util/FileExtensionExtractorWithValidation.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/util/FileExtensionExtractorWithValidation.kt index 009218d755..cb390ad717 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/util/FileExtensionExtractorWithValidation.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/util/FileExtensionExtractorWithValidation.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,11 +11,9 @@ package io.element.android.libraries.mediaviewer.impl.util import android.webkit.MimeTypeMap import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractor @ContributesBinding(AppScope::class) -@Inject class FileExtensionExtractorWithValidation : FileExtensionExtractor { override fun extractFromName(name: String): String { val fileExtension = name.substringAfterLast('.', "") diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerDataSource.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerDataSource.kt index 1046a80fe3..ae7b54ccdd 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerDataSource.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerDataSource.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerEvents.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerEvents.kt index 708c423d36..3f1436b9b6 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerEvents.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,6 +18,7 @@ sealed interface MediaViewerEvents { data class OpenWith(val data: MediaViewerPageData.MediaViewerData) : MediaViewerEvents data class ClearLoadingError(val data: MediaViewerPageData.MediaViewerData) : MediaViewerEvents data class ViewInTimeline(val eventId: EventId) : MediaViewerEvents + data class Forward(val eventId: EventId) : MediaViewerEvents data class OpenInfo(val data: MediaViewerPageData.MediaViewerData) : MediaViewerEvents data class ConfirmDelete( val eventId: EventId, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerFlickToDismiss.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerFlickToDismiss.kt index 43880809d9..b35b638495 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerFlickToDismiss.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerFlickToDismiss.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,7 +17,9 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.androidutils.system.areAnimationsEnabled import kotlinx.coroutines.delay import me.saket.telephoto.ExperimentalTelephotoApi import me.saket.telephoto.flick.FlickToDismiss @@ -34,10 +37,14 @@ fun MediaViewerFlickToDismiss( content: @Composable BoxScope.() -> Unit, ) { val flickState = rememberFlickToDismissState(dismissThresholdRatio = 0.1f, rotateOnDrag = false) + val context = LocalContext.current DismissFlickEffects( flickState = flickState, onDismissing = { animationDuration -> - delay(animationDuration / 3) + // Only add the delay if an animation should be played, otherwise `onDismiss` will never be called + if (context.areAnimationsEnabled()) { + delay(animationDuration / 3) + } onDismiss() }, onDragging = onDragging, diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNavigator.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNavigator.kt index c75db00afe..327505d7ac 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNavigator.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNavigator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,5 +12,6 @@ import io.element.android.libraries.matrix.api.core.EventId interface MediaViewerNavigator { fun onViewInTimelineClick(eventId: EventId) + fun onForwardClick(eventId: EventId, fromPinnedEvents: Boolean) fun onItemDeleted() } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNode.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNode.kt index cb9743bf97..d834534e3e 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNode.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerNode.kt @@ -1,28 +1,35 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.mediaviewer.impl.viewer import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode +import io.element.android.compound.colors.SemanticColorsLightDark import io.element.android.compound.theme.ForcedDarkElementTheme +import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.viewfolder.api.TextFileViewer +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs import io.element.android.libraries.audio.api.AudioFocus import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint @@ -47,24 +54,23 @@ class MediaViewerNode( pagerKeysHandler: PagerKeysHandler, private val textFileViewer: TextFileViewer, private val audioFocus: AudioFocus, + private val sessionId: SessionId, + private val enterpriseService: EnterpriseService, ) : Node(buildContext, plugins = plugins), MediaViewerNavigator { + private val callback: MediaViewerEntryPoint.Callback = callback() private val inputs = inputs() - private fun onDone() { - plugins().forEach { - it.onDone() - } + override fun onViewInTimelineClick(eventId: EventId) { + callback.viewInTimeline(eventId) } - override fun onViewInTimelineClick(eventId: EventId) { - plugins().forEach { - it.onViewInTimeline(eventId) - } + override fun onForwardClick(eventId: EventId, fromPinnedEvents: Boolean) { + callback.forwardEvent(eventId, fromPinnedEvents) } override fun onItemDeleted() { - onDone() + callback.onDone() } private val mediaGallerySource = if (inputs.mode == MediaViewerEntryPoint.MediaViewerMode.SingleMedia) { @@ -76,11 +82,7 @@ class MediaViewerNode( timelineMediaGalleryDataSource } else { // Can we use a specific timeline? - val timelineMode = when (val mode = inputs.mode) { - is MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos -> mode.timelineMode - is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios -> mode.timelineMode - else -> null - } + val timelineMode = inputs.mode.getTimelineMode() when (timelineMode) { null -> timelineMediaGalleryDataSource Timeline.Mode.Live, @@ -127,15 +129,28 @@ class MediaViewerNode( @Composable override fun View(modifier: Modifier) { - ForcedDarkElementTheme { + val colors by remember { + enterpriseService.semanticColorsFlow(sessionId = sessionId) + }.collectAsState(SemanticColorsLightDark.default) + ForcedDarkElementTheme( + colors = colors, + ) { val state = presenter.present() MediaViewerView( state = state, textFileViewer = textFileViewer, modifier = modifier, audioFocus = audioFocus, - onBackClick = ::onDone, + onBackClick = callback::onDone, ) } } } + +internal fun MediaViewerEntryPoint.MediaViewerMode.getTimelineMode(): Timeline.Mode? { + return when (this) { + is MediaViewerEntryPoint.MediaViewerMode.TimelineImagesAndVideos -> timelineMode + is MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios -> timelineMode + else -> null + } +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt index c7b93a227f..dc0feb70cf 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -31,14 +32,16 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.room.JoinedRoom -import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther -import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn +import io.element.android.libraries.matrix.api.room.powerlevels.permissionsAsState +import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.impl.R import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetState import io.element.android.libraries.mediaviewer.impl.local.LocalMediaActions +import io.element.android.libraries.mediaviewer.impl.model.MediaPermissions +import io.element.android.libraries.mediaviewer.impl.model.mediaPermissions import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.CoroutineScope @@ -79,6 +82,9 @@ class MediaViewerPresenter( NoMoreItemsBackwardSnackBarDisplayer(currentIndex, data) NoMoreItemsForwardSnackBarDisplayer(currentIndex, data) + val permissions by room.permissionsAsState(MediaPermissions.DEFAULT) { perms -> + perms.mediaPermissions() + } var mediaBottomSheetState by remember { mutableStateOf(MediaBottomSheetState.Hidden) } DisposableEffect(Unit) { @@ -89,7 +95,7 @@ class MediaViewerPresenter( } localMediaActions.Configure() - fun handleEvents(event: MediaViewerEvents) { + fun handleEvent(event: MediaViewerEvents) { when (event) { is MediaViewerEvents.LoadMedia -> { coroutineScope.downloadMedia(data = event.data) @@ -117,13 +123,20 @@ class MediaViewerPresenter( mediaBottomSheetState = MediaBottomSheetState.Hidden navigator.onViewInTimelineClick(event.eventId) } + is MediaViewerEvents.Forward -> { + mediaBottomSheetState = MediaBottomSheetState.Hidden + navigator.onForwardClick( + eventId = event.eventId, + fromPinnedEvents = inputs.mode.getTimelineMode() == Timeline.Mode.PinnedEvents, + ) + } is MediaViewerEvents.OpenInfo -> coroutineScope.launch { mediaBottomSheetState = MediaBottomSheetState.MediaDetailsBottomSheetState( eventId = event.data.eventId, canDelete = when (event.data.mediaInfo.senderId) { null -> false - room.sessionId -> room.canRedactOwn().getOrElse { false } && event.data.eventId != null - else -> room.canRedactOther().getOrElse { false } && event.data.eventId != null + room.sessionId -> permissions.canRedactOwn && event.data.eventId != null + else -> permissions.canRedactOther && event.data.eventId != null }, mediaInfo = event.data.mediaInfo, thumbnailSource = event.data.thumbnailSource, @@ -155,7 +168,7 @@ class MediaViewerPresenter( snackbarMessage = snackbarMessage, canShowInfo = inputs.canShowInfo, mediaBottomSheetState = mediaBottomSheetState, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt index 5a0c4c2307..ae1a422a6f 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt index 05a55b4e5f..85aecc41a8 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,7 @@ import android.net.Uri import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.designsystem.components.media.aWaveForm +import io.element.android.libraries.designsystem.components.media.WaveFormSamples import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.timeline.Timeline @@ -138,7 +139,7 @@ open class MediaViewerStateProvider : PreviewParameterProvider mediaBottomSheetState = aMediaDeleteConfirmationState(), ), anAudioMediaInfo( - waveForm = aWaveForm(), + waveForm = WaveFormSamples.realisticWaveForm, ).let { aMediaViewerState( listOf( diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt index 110054eb20..7439909330 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -247,6 +248,9 @@ fun MediaViewerView( state.eventSink(MediaViewerEvents.Share(currentData)) } }, + onForward = { + state.eventSink(MediaViewerEvents.Forward(it)) + }, onDownload = { (currentData as? MediaViewerPageData.MediaViewerData)?.let { state.eventSink(MediaViewerEvents.SaveOnDisk(currentData)) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/PagerKeysHandler.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/PagerKeysHandler.kt index 03d37690ed..a2234143af 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/PagerKeysHandler.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/PagerKeysHandler.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/SingleMediaGalleryDataSource.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/SingleMediaGalleryDataSource.kt index 94ac0fea21..f243ac4fd7 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/SingleMediaGalleryDataSource.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/SingleMediaGalleryDataSource.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -27,6 +28,7 @@ class SingleMediaGalleryDataSource( override fun start() = Unit override fun groupedMediaItemsFlow() = flowOf(AsyncData.Success(data)) override fun getLastData(): AsyncData = AsyncData.Success(data) + override suspend fun loadMore(direction: Timeline.PaginationDirection) = Unit override suspend fun deleteItem(eventId: EventId) = Unit diff --git a/libraries/mediaviewer/impl/src/main/res/values-fa/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-fa/translations.xml index c43bed885c..81f8f4db2d 100644 --- a/libraries/mediaviewer/impl/src/main/res/values-fa/translations.xml +++ b/libraries/mediaviewer/impl/src/main/res/values-fa/translations.xml @@ -1,7 +1,7 @@ "حذف پرونده؟" - "بررسی اتّصال اینترنتیتان و تلاش دوباره" + "بررسی اتّصال اینترنتیتان و تلاش دوباره." "سندها، پرونده‌ها و پیام‌های صوتی بار گذاشته در این اتاق این‌جا نشان داده خواهند شد." "هنوز هیچ پرونده‌ای بارگذاشته نشده" "بار کردن پرونده‌ها…" diff --git a/libraries/mediaviewer/impl/src/main/res/values-hr/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..7637fbf350 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,21 @@ + + + "Ova će se datoteka ukloniti iz sobe i članovi joj neće moći pristupiti." + "Želite li izbrisati datoteku?" + "Provjerite internetsku vezu i pokušajte ponovno." + "Ovdje će se prikazati dokumenti, audiodatoteke i glasovne poruke prenesene u ovu sobu." + "Još nema prenesenih datoteka" + "Učitavanje datoteka…" + "Učitavanje medija…" + "Datoteke" + "Mediji" + "Ovdje će se prikazati slike i videozapisi preneseni u ovu sobu." + "Još nijedan medij nije prenesen" + "Mediji i datoteke" + "Oblik datoteke" + "Naziv datoteke" + "Nema više datoteka za prikaz" + "Nema više medijskih sadržaja za prikaz" + "Prenio/la" + "Preneseno na" + diff --git a/libraries/mediaviewer/impl/src/main/res/values-uz/translations.xml b/libraries/mediaviewer/impl/src/main/res/values-uz/translations.xml new file mode 100644 index 0000000000..8f838c0d26 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/res/values-uz/translations.xml @@ -0,0 +1,21 @@ + + + "Bu fayl xonadan olib tashlanadi va a’zolar unga kira olmaydilar." + "Fayl oʻchirilsinmi?" + "Internet aloqangizni tekshiring va qayta urining." + "Ushbu xonaga yuklangan hujjatlar, audio fayllar va ovozli xabarlar shu yerda ko‘rsatiladi." + "Hali hech qanday fayl yuklanmagan" + "Fayllar yuklanmoqda…" + "Media yuklanmoqda…" + "Fayllar" + "Media" + "Bu xonaga yuklangan rasm va videolar shu yerda chiqadi." + "Hali hech qanday media yuklanmagan" + "Media va fayllar" + "Fayl formati" + "Fayl nomi" + "Ko‘rsatish uchun boshqa fayllar yo‘q" + "Ko‘rsatish uchun boshqa media yo‘q" + "Tomonidan yuklangan" + "Yuklangan" + diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaGalleryEntryPointTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaGalleryEntryPointTest.kt index 0f8b0fedfb..8b2f3b561d 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaGalleryEntryPointTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaGalleryEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,13 +10,12 @@ package io.element.android.libraries.mediaviewer.impl import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.bumble.appyx.core.modality.BuildContext -import com.bumble.appyx.core.node.Node import com.bumble.appyx.testing.junit4.util.MainDispatcherRule import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.mediaviewer.api.MediaGalleryEntryPoint -import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint import io.element.android.libraries.mediaviewer.impl.gallery.root.MediaGalleryFlowNode +import io.element.android.libraries.mediaviewer.test.FakeMediaViewerEntryPoint import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.node.TestParentNode import org.junit.Rule @@ -35,18 +35,19 @@ class DefaultMediaGalleryEntryPointTest { MediaGalleryFlowNode( buildContext = buildContext, plugins = plugins, - mediaViewerEntryPoint = object : MediaViewerEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext) = lambdaError() - } + mediaViewerEntryPoint = FakeMediaViewerEntryPoint(), ) } val callback = object : MediaGalleryEntryPoint.Callback { override fun onBackClick() = lambdaError() - override fun onViewInTimeline(eventId: EventId) = lambdaError() + override fun viewInTimeline(eventId: EventId) = lambdaError() + override fun forward(eventId: EventId, fromPinnedEvents: Boolean) = lambdaError() } - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + callback = callback, + ) assertThat(result).isInstanceOf(MediaGalleryFlowNode::class.java) assertThat(result.plugins).contains(callback) } diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPointTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPointTest.kt index 3af2e8cf69..b848ea2a7b 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPointTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/DefaultMediaViewerEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,10 +12,12 @@ import android.net.Uri import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.bumble.appyx.core.modality.BuildContext import com.google.common.truth.Truth.assertThat +import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.media.MediaSource +import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.media.FakeMatrixMediaLoader import io.element.android.libraries.mediaplayer.test.FakeAudioFocus import io.element.android.libraries.mediaviewer.api.MediaInfo @@ -63,17 +66,22 @@ class DefaultMediaViewerEntryPointTest { pagerKeysHandler = PagerKeysHandler(), textFileViewer = { _, _ -> lambdaError() }, audioFocus = FakeAudioFocus(), + sessionId = A_SESSION_ID, + enterpriseService = FakeEnterpriseService(), ) } val callback = object : MediaViewerEntryPoint.Callback { override fun onDone() = lambdaError() - override fun onViewInTimeline(eventId: EventId) = lambdaError() + override fun viewInTimeline(eventId: EventId) = lambdaError() + override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) = lambdaError() } val params = createMediaViewerEntryPointParams() - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .params(params) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + params = params, + callback = callback, + ) assertThat(result).isInstanceOf(MediaViewerNode::class.java) assertThat(result.plugins).contains(params) assertThat(result.plugins).contains(callback) @@ -104,19 +112,25 @@ class DefaultMediaViewerEntryPointTest { pagerKeysHandler = PagerKeysHandler(), textFileViewer = { _, _ -> lambdaError() }, audioFocus = FakeAudioFocus(), + sessionId = A_SESSION_ID, + enterpriseService = FakeEnterpriseService(), ) } val callback = object : MediaViewerEntryPoint.Callback { override fun onDone() = lambdaError() - override fun onViewInTimeline(eventId: EventId) = lambdaError() + override fun viewInTimeline(eventId: EventId) = lambdaError() + override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) = lambdaError() } - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .avatar( - filename = "fn", - avatarUrl = "avatarUrl", - ) - .callback(callback) - .build() + val params = entryPoint.createParamsForAvatar( + filename = "fn", + avatarUrl = "avatarUrl", + ) + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + params = params, + callback = callback, + ) assertThat(result).isInstanceOf(MediaViewerNode::class.java) assertThat(result.plugins).contains( MediaViewerEntryPoint.Params( diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/DefaultEventItemFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/DefaultEventItemFactoryTest.kt index 457907793f..c70d658418 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/DefaultEventItemFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/DefaultEventItemFactoryTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -83,7 +84,7 @@ class DefaultEventItemFactoryTest { ), mediaSource = MediaSource("") ), - UnableToDecryptContent(UnableToDecryptContent.Data.Unknown), + UnableToDecryptContent(data = UnableToDecryptContent.Data.Unknown, threadInfo = null), UnknownContent, ) contents.forEach { @@ -396,8 +397,8 @@ class DefaultEventItemFactoryTest { height = 1L, width = 2L, blurhash = null, - ) - ) + ), + ), ) ) ) diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/DefaultFocusedTimelineMediaGalleryDataSourceFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/DefaultFocusedTimelineMediaGalleryDataSourceFactoryTest.kt index 9edc2d0a75..a70c865a86 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/DefaultFocusedTimelineMediaGalleryDataSourceFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/DefaultFocusedTimelineMediaGalleryDataSourceFactoryTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/FakeMediaGalleryDataSource.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/FakeMediaGalleryDataSource.kt index ea4bdfbad8..c612bba1bc 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/FakeMediaGalleryDataSource.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/FakeMediaGalleryDataSource.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/FocusedMediaTimelineTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/FocusedMediaTimelineTest.kt index 1f2ab92dfc..4e1a57e037 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/FocusedMediaTimelineTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/FocusedMediaTimelineTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/LiveMediaTimelineTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/LiveMediaTimelineTest.kt index 4d59b88c09..fae858d8a6 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/LiveMediaTimelineTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/LiveMediaTimelineTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaItemsPostProcessorTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaItemsPostProcessorTest.kt index a8903394b3..53cc25c2c1 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaItemsPostProcessorTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/MediaItemsPostProcessorTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/TimelineMediaGalleryDataSourceTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/TimelineMediaGalleryDataSourceTest.kt index 771c82e4a2..bb8419dde5 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/TimelineMediaGalleryDataSourceTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/datasource/TimelineMediaGalleryDataSourceTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheetTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheetTest.kt index 1596104c46..4d8b81a2dd 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheetTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDeleteConfirmationBottomSheetTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheetTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheetTest.kt index bd4373a420..580cd89c72 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheetTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/details/MediaDetailsBottomSheetTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -56,6 +57,19 @@ class MediaDetailsBottomSheetTest { } } + @Test + @Config(qualifiers = "h1024dp") + fun `clicking on Forward invokes expected callback`() { + val state = aMediaDetailsBottomSheetState() + ensureCalledOnceWithParam(state.eventId) { callback -> + rule.setMediaDetailsBottomSheet( + state = state, + onForward = callback, + ) + rule.clickOn(CommonStrings.action_forward) + } + } + @Test @Config(qualifiers = "h1024dp") fun `clicking on Save invokes expected callback`() { @@ -100,6 +114,7 @@ private fun AndroidComposeTestRule.setMedia state: MediaBottomSheetState.MediaDetailsBottomSheetState, onViewInTimeline: (EventId) -> Unit = EnsureNeverCalledWithParam(), onShare: (EventId) -> Unit = EnsureNeverCalledWithParam(), + onForward: (EventId) -> Unit = EnsureNeverCalledWithParam(), onDownload: (EventId) -> Unit = EnsureNeverCalledWithParam(), onDelete: (EventId) -> Unit = EnsureNeverCalledWithParam(), onDismiss: () -> Unit = EnsureNeverCalled(), @@ -109,6 +124,7 @@ private fun AndroidComposeTestRule.setMedia state = state, onViewInTimeline = onViewInTimeline, onShare = onShare, + onForward = onForward, onDownload = onDownload, onDelete = onDelete, onDismiss = onDismiss, diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaGalleryNavigator.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaGalleryNavigator.kt index 2566ef91a4..0d8ff6b411 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaGalleryNavigator.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/FakeMediaGalleryNavigator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,9 +12,14 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.tests.testutils.lambda.lambdaError class FakeMediaGalleryNavigator( - private val onViewInTimelineClickLambda: (EventId) -> Unit = { lambdaError() } + private val onViewInTimelineClickLambda: (EventId) -> Unit = { lambdaError() }, + private val onForwardClickLambda: (EventId) -> Unit = { lambdaError() }, ) : MediaGalleryNavigator { override fun onViewInTimelineClick(eventId: EventId) { onViewInTimelineClickLambda(eventId) } + + override fun onForwardClick(eventId: EventId) { + onForwardClickLambda(eventId) + } } diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt index f126dab2b5..3069a4fd9d 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -24,6 +25,7 @@ import io.element.android.libraries.matrix.test.media.FakeMatrixMediaLoader import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.aRoomInfo +import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.mediaviewer.impl.datasource.FakeMediaGalleryDataSource import io.element.android.libraries.mediaviewer.impl.datasource.MediaGalleryDataSource @@ -108,7 +110,9 @@ class MediaGalleryPresenterTest { baseRoom = FakeBaseRoom( sessionId = A_USER_ID, initialRoomInfo = aRoomInfo(name = A_ROOM_NAME), - canRedactOwnResult = { Result.success(canDeleteOwn) } + roomPermissions = FakeRoomPermissions( + canRedactOwn = canDeleteOwn + ), ), ) ) @@ -152,7 +156,9 @@ class MediaGalleryPresenterTest { baseRoom = FakeBaseRoom( sessionId = A_USER_ID, initialRoomInfo = aRoomInfo(name = A_ROOM_NAME), - canRedactOtherResult = { Result.success(canDeleteOther) }, + roomPermissions = FakeRoomPermissions( + canRedactOther = canDeleteOther + ), ), createTimelineResult = { Result.success(FakeTimeline()) } ) @@ -345,7 +351,7 @@ class MediaGalleryPresenterTest { } @Test - fun `present - view in timeline invokes the navigator`() = runTest { + fun `present - view in timeline closes the bottom sheet and invokes the navigator`() = runTest { val onViewInTimelineClickLambda = lambdaRecorder { } val navigator = FakeMediaGalleryNavigator( onViewInTimelineClickLambda = onViewInTimelineClickLambda, @@ -353,16 +359,63 @@ class MediaGalleryPresenterTest { val presenter = createMediaGalleryPresenter( room = FakeJoinedRoom( createTimelineResult = { Result.success(FakeTimeline()) }, + baseRoom = FakeBaseRoom( + roomPermissions = FakeRoomPermissions( + canRedactOwn = true + ), + ), ), navigator = navigator, ) presenter.test { val initialState = awaitFirstItem() - initialState.eventSink(MediaGalleryEvents.ViewInTimeline(AN_EVENT_ID)) + val item = aMediaItemImage( + eventId = AN_EVENT_ID, + senderId = A_USER_ID, + ) + initialState.eventSink(MediaGalleryEvents.OpenInfo(item)) + val withBottomSheetState = awaitItem() + assertThat(withBottomSheetState.mediaBottomSheetState).isInstanceOf(MediaBottomSheetState.MediaDetailsBottomSheetState::class.java) + withBottomSheetState.eventSink(MediaGalleryEvents.ViewInTimeline(AN_EVENT_ID)) + val finalState = awaitItem() + assertThat(finalState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) onViewInTimelineClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID)) } } + @Test + fun `present - forward closes the bottom sheet and invokes the navigator`() = runTest { + val onForwardClickLambda = lambdaRecorder { } + val navigator = FakeMediaGalleryNavigator( + onForwardClickLambda = onForwardClickLambda, + ) + val presenter = createMediaGalleryPresenter( + room = FakeJoinedRoom( + createTimelineResult = { Result.success(FakeTimeline()) }, + baseRoom = FakeBaseRoom( + roomPermissions = FakeRoomPermissions( + canRedactOwn = true + ), + ), + ), + navigator = navigator, + ) + presenter.test { + val initialState = awaitFirstItem() + val item = aMediaItemImage( + eventId = AN_EVENT_ID, + senderId = A_USER_ID, + ) + initialState.eventSink(MediaGalleryEvents.OpenInfo(item)) + val withBottomSheetState = awaitItem() + assertThat(withBottomSheetState.mediaBottomSheetState).isInstanceOf(MediaBottomSheetState.MediaDetailsBottomSheetState::class.java) + withBottomSheetState.eventSink(MediaGalleryEvents.Forward(AN_EVENT_ID)) + val finalState = awaitItem() + assertThat(finalState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) + onForwardClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID)) + } + } + @Test fun `present - load more`() = runTest { val loadMoreLambda = lambdaRecorder { } diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActionsTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActionsTest.kt index 6c4a8a29b4..cd7b7003af 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActionsTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActionsTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt index e092f5c624..f01ac1d749 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaFactoryTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/NoOpActivityResultRegistryOwner.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/NoOpActivityResultRegistryOwner.kt index 7dd7966c74..e3e2399171 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/NoOpActivityResultRegistryOwner.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/local/NoOpActivityResultRegistryOwner.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/model/GroupedMediaItemsTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/model/GroupedMediaItemsTest.kt index 16f0777994..dd36b40659 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/model/GroupedMediaItemsTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/model/GroupedMediaItemsTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/util/FileExtensionExtractorWithValidationTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/util/FileExtensionExtractorWithValidationTest.kt index e2399eae34..3c5c91589c 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/util/FileExtensionExtractorWithValidationTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/util/FileExtensionExtractorWithValidationTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/FakeMediaViewerNavigator.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/FakeMediaViewerNavigator.kt index 6c0148b124..a116faa250 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/FakeMediaViewerNavigator.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/FakeMediaViewerNavigator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,12 +13,17 @@ import io.element.android.tests.testutils.lambda.lambdaError class FakeMediaViewerNavigator( private val onViewInTimelineClickLambda: (EventId) -> Unit = { lambdaError() }, + private val onForwardClickLambda: (EventId, Boolean) -> Unit = { _, _ -> lambdaError() }, private val onItemDeletedLambda: () -> Unit = { lambdaError() }, ) : MediaViewerNavigator { override fun onViewInTimelineClick(eventId: EventId) { onViewInTimelineClickLambda(eventId) } + override fun onForwardClick(eventId: EventId, fromPinnedEvents: Boolean) { + onForwardClickLambda(eventId, fromPinnedEvents) + } + override fun onItemDeleted() { onItemDeletedLambda() } diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerDataSourceTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerDataSourceTest.kt index 43a37e8c4c..44eed2733f 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerDataSourceTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerDataSourceTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenterTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenterTest.kt index b9964d8d3d..a9d1704bdc 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenterTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -26,6 +27,7 @@ import io.element.android.libraries.matrix.test.media.FakeMatrixMediaLoader import io.element.android.libraries.matrix.test.media.aMediaSource import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom +import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint import io.element.android.libraries.mediaviewer.api.anApkMediaInfo @@ -82,7 +84,9 @@ class MediaViewerPresenterTest { localMediaFactory = localMediaFactory, room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canRedactOwnResult = { Result.success(true) }, + roomPermissions = FakeRoomPermissions( + canRedactOwn = true + ), ) ) ) @@ -103,7 +107,9 @@ class MediaViewerPresenterTest { canShowInfo = false, room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canRedactOwnResult = { Result.success(true) }, + roomPermissions = FakeRoomPermissions( + canRedactOwn = true + ), ) ) ) @@ -124,7 +130,9 @@ class MediaViewerPresenterTest { eventId = AN_EVENT_ID, room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canRedactOwnResult = { Result.success(true) }, + roomPermissions = FakeRoomPermissions( + canRedactOwn = true + ), ) ) ) @@ -146,7 +154,9 @@ class MediaViewerPresenterTest { room = FakeJoinedRoom( baseRoom = FakeBaseRoom( sessionId = A_SESSION_ID_2, - canRedactOtherResult = { Result.success(false) }, + roomPermissions = FakeRoomPermissions( + canRedactOther = false + ), ) ) ) @@ -235,7 +245,9 @@ class MediaViewerPresenterTest { mediaGalleryDataSource = mediaGalleryDataSource, room = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canRedactOwnResult = { Result.success(true) }, + roomPermissions = FakeRoomPermissions( + canRedactOwn = true + ), ) ) ) @@ -459,7 +471,11 @@ class MediaViewerPresenterTest { localMediaFactory = localMediaFactory, room = FakeJoinedRoom( liveTimeline = timeline, - baseRoom = FakeBaseRoom(canRedactOwnResult = { Result.success(true) }), + baseRoom = FakeBaseRoom( + roomPermissions = FakeRoomPermissions( + canRedactOwn = true + ), + ), ), mediaGalleryDataSource = mediaGalleryDataSource, mediaViewerNavigator = FakeMediaViewerNavigator( @@ -759,7 +775,7 @@ class MediaViewerPresenterTest { } @Test - fun `present - view in timeline hide the bottom sheet and invokes the navigator`() = runTest { + fun `present - view in timeline hides the bottom sheet and invokes the navigator`() = runTest { val onViewInTimelineClickLambda = lambdaRecorder { } val navigator = FakeMediaViewerNavigator( onViewInTimelineClickLambda = onViewInTimelineClickLambda, @@ -768,7 +784,11 @@ class MediaViewerPresenterTest { localMediaFactory = localMediaFactory, mediaViewerNavigator = navigator, room = FakeJoinedRoom( - baseRoom = FakeBaseRoom(canRedactOwnResult = { Result.success(true) }), + baseRoom = FakeBaseRoom( + roomPermissions = FakeRoomPermissions( + canRedactOwn = true + ), + ), ) ) presenter.test { @@ -783,6 +803,67 @@ class MediaViewerPresenterTest { } } + @Test + fun `present - forward hides the bottom sheet and invokes the navigator`() = runTest { + val onForwardClickLambda = lambdaRecorder { _, _ -> } + val navigator = FakeMediaViewerNavigator( + onForwardClickLambda = onForwardClickLambda, + ) + val presenter = createMediaViewerPresenter( + localMediaFactory = localMediaFactory, + mediaViewerNavigator = navigator, + room = FakeJoinedRoom( + baseRoom = FakeBaseRoom( + roomPermissions = FakeRoomPermissions( + canRedactOwn = true + ), + ), + ), + ) + presenter.test { + val initialState = awaitItem() + initialState.eventSink(MediaViewerEvents.OpenInfo(aMediaViewerPageData())) + val withBottomSheetState = awaitItem() + assertThat(withBottomSheetState.mediaBottomSheetState).isInstanceOf(MediaBottomSheetState.MediaDetailsBottomSheetState::class.java) + initialState.eventSink(MediaViewerEvents.Forward(AN_EVENT_ID)) + val finalState = awaitItem() + assertThat(finalState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) + onForwardClickLambda.assertions().isCalledOnce() + .with(value(AN_EVENT_ID), value(false)) + } + } + + @Test + fun `present - forward from pinned events hides the bottom sheet and invokes the navigator`() = runTest { + val onForwardClickLambda = lambdaRecorder { _, _ -> } + val navigator = FakeMediaViewerNavigator( + onForwardClickLambda = onForwardClickLambda, + ) + val presenter = createMediaViewerPresenter( + mode = MediaViewerEntryPoint.MediaViewerMode.TimelineFilesAndAudios(timelineMode = Timeline.Mode.PinnedEvents), + localMediaFactory = localMediaFactory, + mediaViewerNavigator = navigator, + room = FakeJoinedRoom( + baseRoom = FakeBaseRoom( + roomPermissions = FakeRoomPermissions( + canRedactOwn = true + ), + ), + ), + ) + presenter.test { + val initialState = awaitItem() + initialState.eventSink(MediaViewerEvents.OpenInfo(aMediaViewerPageData())) + val withBottomSheetState = awaitItem() + assertThat(withBottomSheetState.mediaBottomSheetState).isInstanceOf(MediaBottomSheetState.MediaDetailsBottomSheetState::class.java) + initialState.eventSink(MediaViewerEvents.Forward(AN_EVENT_ID)) + val finalState = awaitItem() + assertThat(finalState.mediaBottomSheetState).isEqualTo(MediaBottomSheetState.Hidden) + onForwardClickLambda.assertions().isCalledOnce() + .with(value(AN_EVENT_ID), value(true)) + } + } + private suspend fun ReceiveTurbine.awaitFirstItem(): T { return awaitItem() } diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerViewTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerViewTest.kt index 6423c3f0b7..b1114945c2 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerViewTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/PagerKeysHandlerTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/PagerKeysHandlerTest.kt index 807c7b51cd..8a334b3c6f 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/PagerKeysHandlerTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/PagerKeysHandlerTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/SingleMediaGalleryDataSourceTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/SingleMediaGalleryDataSourceTest.kt index c80aa15428..c6460cb70a 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/SingleMediaGalleryDataSourceTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/SingleMediaGalleryDataSourceTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,7 @@ package io.element.android.libraries.mediaviewer.impl.viewer import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.architecture.AsyncData -import io.element.android.libraries.designsystem.components.media.createFakeWaveform +import io.element.android.libraries.designsystem.components.media.WaveFormSamples import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.test.AN_EVENT_ID @@ -128,7 +129,7 @@ class SingleMediaGalleryDataSourceTest { fun `createFrom should create a SingleMediaGalleryDataSource with a voice item`() { testFactory( mediaInfo = aVoiceMediaInfo( - waveForm = createFakeWaveform(), + waveForm = WaveFormSamples.longRealisticWaveForm, duration = "12:34", ), expectedResult = { params -> diff --git a/libraries/mediaviewer/test/build.gradle.kts b/libraries/mediaviewer/test/build.gradle.kts index 45096be999..1918714d7b 100644 --- a/libraries/mediaviewer/test/build.gradle.kts +++ b/libraries/mediaviewer/test/build.gradle.kts @@ -1,9 +1,10 @@ import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaActions.kt b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaActions.kt index af6e00b05d..875be941db 100644 --- a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaActions.kt +++ b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaActions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt index a9e962bac4..faa27fd0e3 100644 --- a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt +++ b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeLocalMediaFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeMediaGalleryEntryPoint.kt b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeMediaGalleryEntryPoint.kt new file mode 100644 index 0000000000..be5d1b972e --- /dev/null +++ b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeMediaGalleryEntryPoint.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.libraries.mediaviewer.api.MediaGalleryEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeMediaGalleryEntryPoint : MediaGalleryEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: MediaGalleryEntryPoint.Callback, + ): Node = lambdaError() +} diff --git a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeMediaViewerEntryPoint.kt b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeMediaViewerEntryPoint.kt new file mode 100644 index 0000000000..8867f9c170 --- /dev/null +++ b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/FakeMediaViewerEntryPoint.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.libraries.mediaviewer.api.MediaViewerEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeMediaViewerEntryPoint : MediaViewerEntryPoint { + override fun createParamsForAvatar(filename: String, avatarUrl: String) = lambdaError() + + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: MediaViewerEntryPoint.Params, + callback: MediaViewerEntryPoint.Callback, + ): Node = lambdaError() +} diff --git a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/util/FileExtensionExtractorWithoutValidation.kt b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/util/FileExtensionExtractorWithoutValidation.kt index c29274fea4..09a74f2bfb 100644 --- a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/util/FileExtensionExtractorWithoutValidation.kt +++ b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/util/FileExtensionExtractorWithoutValidation.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/viewer/LocalMedia.kt b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/viewer/LocalMedia.kt index 3927d8a981..a7bb30d732 100644 --- a/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/viewer/LocalMedia.kt +++ b/libraries/mediaviewer/test/src/main/kotlin/io/element/android/libraries/mediaviewer/test/viewer/LocalMedia.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/mediaviewer/test/src/test/kotlin/io/element/android/libraries/mediaviewer/test/util/FileExtensionExtractorWithoutValidationTest.kt b/libraries/mediaviewer/test/src/test/kotlin/io/element/android/libraries/mediaviewer/test/util/FileExtensionExtractorWithoutValidationTest.kt index d3f1b647d2..fad7a1d14f 100644 --- a/libraries/mediaviewer/test/src/test/kotlin/io/element/android/libraries/mediaviewer/test/util/FileExtensionExtractorWithoutValidationTest.kt +++ b/libraries/mediaviewer/test/src/test/kotlin/io/element/android/libraries/mediaviewer/test/util/FileExtensionExtractorWithoutValidationTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/network/build.gradle.kts b/libraries/network/build.gradle.kts index b702ff7881..72e7eb05f2 100644 --- a/libraries/network/build.gradle.kts +++ b/libraries/network/build.gradle.kts @@ -1,9 +1,10 @@ import extension.setupDependencyInjection /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { @@ -23,9 +24,11 @@ android { setupDependencyInjection() dependencies { + implementation(projects.libraries.androidutils) implementation(projects.libraries.core) implementation(projects.libraries.di) implementation(projects.libraries.matrix.api) + implementation(projects.libraries.preferences.api) implementation(platform(libs.network.okhttp.bom)) implementation(libs.network.okhttp) implementation(libs.network.okhttp.logging) diff --git a/libraries/network/src/main/kotlin/io/element/android/libraries/network/NetworkModule.kt b/libraries/network/src/main/kotlin/io/element/android/libraries/network/NetworkModule.kt index 6ede5a0c48..d8809e042c 100644 --- a/libraries/network/src/main/kotlin/io/element/android/libraries/network/NetworkModule.kt +++ b/libraries/network/src/main/kotlin/io/element/android/libraries/network/NetworkModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,10 +13,9 @@ import dev.zacsweers.metro.BindingContainer import dev.zacsweers.metro.ContributesTo import dev.zacsweers.metro.Provides import dev.zacsweers.metro.SingleIn -import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.network.interceptors.DynamicHttpLoggingInterceptor import io.element.android.libraries.network.interceptors.FormattedJsonHttpLogger import io.element.android.libraries.network.interceptors.UserAgentInterceptor -import kotlinx.serialization.json.Json import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import java.util.concurrent.TimeUnit @@ -26,27 +26,20 @@ object NetworkModule { @Provides @SingleIn(AppScope::class) fun providesOkHttpClient( - buildMeta: BuildMeta, userAgentInterceptor: UserAgentInterceptor, + dynamicHttpLoggingInterceptor: DynamicHttpLoggingInterceptor, ): OkHttpClient = OkHttpClient.Builder().apply { connectTimeout(30, TimeUnit.SECONDS) readTimeout(60, TimeUnit.SECONDS) writeTimeout(60, TimeUnit.SECONDS) addInterceptor(userAgentInterceptor) - if (buildMeta.isDebuggable) addInterceptor(providesHttpLoggingInterceptor()) + addInterceptor(dynamicHttpLoggingInterceptor) }.build() @Provides @SingleIn(AppScope::class) - fun providesJson(): Json = Json { - ignoreUnknownKeys = true + fun providesHttpLoggingInterceptor(): HttpLoggingInterceptor { + val logger = FormattedJsonHttpLogger(HttpLoggingInterceptor.Level.BODY) + return HttpLoggingInterceptor(logger) } } - -private fun providesHttpLoggingInterceptor(): HttpLoggingInterceptor { - val loggingLevel = HttpLoggingInterceptor.Level.BODY - val logger = FormattedJsonHttpLogger(loggingLevel) - val interceptor = HttpLoggingInterceptor(logger) - interceptor.level = loggingLevel - return interceptor -} diff --git a/libraries/network/src/main/kotlin/io/element/android/libraries/network/RetrofitFactory.kt b/libraries/network/src/main/kotlin/io/element/android/libraries/network/RetrofitFactory.kt index 261d7d02ba..88240c3d65 100644 --- a/libraries/network/src/main/kotlin/io/element/android/libraries/network/RetrofitFactory.kt +++ b/libraries/network/src/main/kotlin/io/element/android/libraries/network/RetrofitFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,8 +10,8 @@ package io.element.android.libraries.network import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Provider +import io.element.android.libraries.androidutils.json.JsonProvider import io.element.android.libraries.core.uri.ensureTrailingSlash -import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import retrofit2.Retrofit @@ -19,11 +20,11 @@ import retrofit2.converter.kotlinx.serialization.asConverterFactory @Inject class RetrofitFactory( private val okHttpClient: Provider, - private val json: Provider, + private val json: Provider, ) { fun create(baseUrl: String): Retrofit = Retrofit.Builder() .baseUrl(baseUrl.ensureTrailingSlash()) - .addConverterFactory(json().asConverterFactory("application/json".toMediaType())) + .addConverterFactory(json()().asConverterFactory("application/json".toMediaType())) .callFactory { request -> okHttpClient().newCall(request) } .build() } diff --git a/libraries/network/src/main/kotlin/io/element/android/libraries/network/headers/HttpHeaders.kt b/libraries/network/src/main/kotlin/io/element/android/libraries/network/headers/HttpHeaders.kt index 85abddee17..259e6b8c44 100644 --- a/libraries/network/src/main/kotlin/io/element/android/libraries/network/headers/HttpHeaders.kt +++ b/libraries/network/src/main/kotlin/io/element/android/libraries/network/headers/HttpHeaders.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/DynamicHttpLoggingInterceptor.kt b/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/DynamicHttpLoggingInterceptor.kt new file mode 100644 index 0000000000..daaf922ce7 --- /dev/null +++ b/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/DynamicHttpLoggingInterceptor.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.network.interceptors + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.SingleIn +import io.element.android.libraries.matrix.api.tracing.LogLevel +import io.element.android.libraries.preferences.api.store.AppPreferencesStore +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import okhttp3.Interceptor +import okhttp3.Response +import okhttp3.logging.HttpLoggingInterceptor +import okhttp3.logging.HttpLoggingInterceptor.Level + +/** + * HTTP logging interceptor that decides whether to display the HTTP logs or not based on the current log level. + */ +@Inject +@SingleIn(AppScope::class) +class DynamicHttpLoggingInterceptor( + private val appPreferencesStore: AppPreferencesStore, + private val loggingInterceptor: HttpLoggingInterceptor, +) : Interceptor by loggingInterceptor { + override fun intercept(chain: Interceptor.Chain): Response { + // This is called in a separate thread, so calling `runBlocking` here should be fine, it should be also instant after the value is cached + val logLevel = runBlocking { appPreferencesStore.getTracingLogLevelFlow().first() } + loggingInterceptor.level = if (logLevel >= LogLevel.DEBUG) Level.BODY else Level.NONE + return loggingInterceptor.intercept(chain) + } +} diff --git a/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/FormattedJsonHttpLogger.kt b/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/FormattedJsonHttpLogger.kt index e2fad575f4..11aa0a5cce 100644 --- a/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/FormattedJsonHttpLogger.kt +++ b/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/FormattedJsonHttpLogger.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -29,7 +30,7 @@ internal class FormattedJsonHttpLogger( */ @Synchronized override fun log(message: String) { - Timber.v(message.ellipsize(200_000)) + Timber.d(message.ellipsize(200_000)) // Try to log formatted Json only if there is a chance that [message] contains Json. // It can be only the case if we log the bodies of Http requests. diff --git a/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/UserAgentInterceptor.kt b/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/UserAgentInterceptor.kt index 0242774bdd..eb1d621330 100644 --- a/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/UserAgentInterceptor.kt +++ b/libraries/network/src/main/kotlin/io/element/android/libraries/network/interceptors/UserAgentInterceptor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/DefaultUserAgentProvider.kt b/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/DefaultUserAgentProvider.kt index bf1d2a93c9..3ecd091e0d 100644 --- a/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/DefaultUserAgentProvider.kt +++ b/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/DefaultUserAgentProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,14 +11,12 @@ package io.element.android.libraries.network.useragent import android.os.Build import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.api.SdkMetadata @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DefaultUserAgentProvider( private val buildMeta: BuildMeta, private val sdkMeta: SdkMetadata, diff --git a/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/SimpleUserAgentProvider.kt b/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/SimpleUserAgentProvider.kt index f3b1be5674..9d8e71a2e6 100644 --- a/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/SimpleUserAgentProvider.kt +++ b/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/SimpleUserAgentProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/UserAgentProvider.kt b/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/UserAgentProvider.kt index 2025264f10..dc552f47a7 100644 --- a/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/UserAgentProvider.kt +++ b/libraries/network/src/main/kotlin/io/element/android/libraries/network/useragent/UserAgentProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/oidc/api/build.gradle.kts b/libraries/oidc/api/build.gradle.kts index 28b26d4e9e..8cc0125142 100644 --- a/libraries/oidc/api/build.gradle.kts +++ b/libraries/oidc/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/oidc/api/src/main/kotlin/io/element/android/libraries/oidc/api/OidcAction.kt b/libraries/oidc/api/src/main/kotlin/io/element/android/libraries/oidc/api/OidcAction.kt index fc464e9ee2..d7c061ab25 100644 --- a/libraries/oidc/api/src/main/kotlin/io/element/android/libraries/oidc/api/OidcAction.kt +++ b/libraries/oidc/api/src/main/kotlin/io/element/android/libraries/oidc/api/OidcAction.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/oidc/api/src/main/kotlin/io/element/android/libraries/oidc/api/OidcActionFlow.kt b/libraries/oidc/api/src/main/kotlin/io/element/android/libraries/oidc/api/OidcActionFlow.kt index 139317e37b..17340eb5ec 100644 --- a/libraries/oidc/api/src/main/kotlin/io/element/android/libraries/oidc/api/OidcActionFlow.kt +++ b/libraries/oidc/api/src/main/kotlin/io/element/android/libraries/oidc/api/OidcActionFlow.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/oidc/api/src/main/kotlin/io/element/android/libraries/oidc/api/OidcIntentResolver.kt b/libraries/oidc/api/src/main/kotlin/io/element/android/libraries/oidc/api/OidcIntentResolver.kt index 75b67ef03a..97fa1baa27 100644 --- a/libraries/oidc/api/src/main/kotlin/io/element/android/libraries/oidc/api/OidcIntentResolver.kt +++ b/libraries/oidc/api/src/main/kotlin/io/element/android/libraries/oidc/api/OidcIntentResolver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/oidc/impl/build.gradle.kts b/libraries/oidc/impl/build.gradle.kts index f3c50ed9ab..e11ce11c70 100644 --- a/libraries/oidc/impl/build.gradle.kts +++ b/libraries/oidc/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcActionFlow.kt b/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcActionFlow.kt index c49128543a..6096ef7eef 100644 --- a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcActionFlow.kt +++ b/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcActionFlow.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.oidc.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.oidc.api.OidcAction import io.element.android.libraries.oidc.api.OidcActionFlow @@ -18,7 +18,6 @@ import kotlinx.coroutines.flow.MutableStateFlow @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) -@Inject class DefaultOidcActionFlow : OidcActionFlow { private val mutableStateFlow = MutableStateFlow(null) diff --git a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcIntentResolver.kt b/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcIntentResolver.kt index 42cdb14851..2a16030b3b 100644 --- a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcIntentResolver.kt +++ b/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcIntentResolver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,12 +11,10 @@ package io.element.android.libraries.oidc.impl import android.content.Intent import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.oidc.api.OidcAction import io.element.android.libraries.oidc.api.OidcIntentResolver @ContributesBinding(AppScope::class) -@Inject class DefaultOidcIntentResolver( private val oidcUrlParser: OidcUrlParser, ) : OidcIntentResolver { diff --git a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/OidcUrlParser.kt b/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/OidcUrlParser.kt index 1e9b6953a8..8933873dc2 100644 --- a/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/OidcUrlParser.kt +++ b/libraries/oidc/impl/src/main/kotlin/io/element/android/libraries/oidc/impl/OidcUrlParser.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.oidc.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.matrix.api.auth.OidcRedirectUrlProvider import io.element.android.libraries.oidc.api.OidcAction @@ -22,7 +22,6 @@ fun interface OidcUrlParser { * TODO Find documentation about the format. */ @ContributesBinding(AppScope::class) -@Inject class DefaultOidcUrlParser( private val oidcRedirectUrlProvider: OidcRedirectUrlProvider, ) : OidcUrlParser { diff --git a/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcActionFlowTest.kt b/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcActionFlowTest.kt index 51017f0af0..387b9aceb0 100644 --- a/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcActionFlowTest.kt +++ b/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcActionFlowTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcIntentResolverTest.kt b/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcIntentResolverTest.kt index 48595452d2..64068030d7 100644 --- a/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcIntentResolverTest.kt +++ b/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcIntentResolverTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcUrlParserTest.kt b/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcUrlParserTest.kt index 7ec03a258e..7f145c053d 100644 --- a/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcUrlParserTest.kt +++ b/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/DefaultOidcUrlParserTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/oidc/test/build.gradle.kts b/libraries/oidc/test/build.gradle.kts index ac9d4cfda8..efe32d404a 100644 --- a/libraries/oidc/test/build.gradle.kts +++ b/libraries/oidc/test/build.gradle.kts @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/oidc/test/src/main/kotlin/io/element/android/libraries/oidc/test/FakeOidcIntentResolver.kt b/libraries/oidc/test/src/main/kotlin/io/element/android/libraries/oidc/test/FakeOidcIntentResolver.kt index 1fa5947c3f..45b400868b 100644 --- a/libraries/oidc/test/src/main/kotlin/io/element/android/libraries/oidc/test/FakeOidcIntentResolver.kt +++ b/libraries/oidc/test/src/main/kotlin/io/element/android/libraries/oidc/test/FakeOidcIntentResolver.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/oidc/test/src/main/kotlin/io/element/android/libraries/oidc/test/customtab/FakeOidcActionFlow.kt b/libraries/oidc/test/src/main/kotlin/io/element/android/libraries/oidc/test/customtab/FakeOidcActionFlow.kt index d1673d8bc8..5362aefa7c 100644 --- a/libraries/oidc/test/src/main/kotlin/io/element/android/libraries/oidc/test/customtab/FakeOidcActionFlow.kt +++ b/libraries/oidc/test/src/main/kotlin/io/element/android/libraries/oidc/test/customtab/FakeOidcActionFlow.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/permissions/api/build.gradle.kts b/libraries/permissions/api/build.gradle.kts index ad11bd214c..0b7edd9fdb 100644 --- a/libraries/permissions/api/build.gradle.kts +++ b/libraries/permissions/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionStateProvider.kt b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionStateProvider.kt index be9e38c4fb..beeef0b234 100644 --- a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionStateProvider.kt +++ b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsEvents.kt b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsEvent.kt similarity index 52% rename from libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsEvents.kt rename to libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsEvent.kt index bc58e3bcb3..fd01e12134 100644 --- a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsEvents.kt +++ b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsEvent.kt @@ -1,14 +1,15 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.permissions.api -sealed interface PermissionsEvents { - data object RequestPermissions : PermissionsEvents - data object CloseDialog : PermissionsEvents - data object OpenSystemSettingAndCloseDialog : PermissionsEvents +sealed interface PermissionsEvent { + data object RequestPermissions : PermissionsEvent + data object CloseDialog : PermissionsEvent + data object OpenSystemSettingAndCloseDialog : PermissionsEvent } diff --git a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsPresenter.kt b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsPresenter.kt index e775714f6b..f2ccb2bfc3 100644 --- a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsPresenter.kt +++ b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsState.kt b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsState.kt index 367d48c499..3cbdfea50d 100644 --- a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsState.kt +++ b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,5 +17,5 @@ data class PermissionsState( val permissionAlreadyAsked: Boolean, // If true, there is no need to ask again, the system dialog will not be displayed val permissionAlreadyDenied: Boolean, - val eventSink: (PermissionsEvents) -> Unit + val eventSink: (PermissionsEvent) -> Unit ) diff --git a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsStateProvider.kt b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsStateProvider.kt index 7a48bd64b4..a330333d17 100644 --- a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsStateProvider.kt +++ b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsStore.kt b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsStore.kt index 37f986e57a..3bb7d2c679 100644 --- a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsStore.kt +++ b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsView.kt b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsView.kt index 1748a02db4..af0f3dd5e8 100644 --- a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsView.kt +++ b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/PermissionsView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -34,9 +35,9 @@ fun PermissionsView( content = content ?: state.permission.toDialogContent(), submitText = stringResource(id = CommonStrings.action_open_settings), onSubmitClick = { - state.eventSink.invoke(PermissionsEvents.OpenSystemSettingAndCloseDialog) + state.eventSink.invoke(PermissionsEvent.OpenSystemSettingAndCloseDialog) }, - onDismiss = { state.eventSink.invoke(PermissionsEvents.CloseDialog) }, + onDismiss = { state.eventSink.invoke(PermissionsEvent.CloseDialog) }, icon = icon, ) } diff --git a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/Util.kt b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/Util.kt index 89f9e558ae..5a0e9b2e21 100644 --- a/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/Util.kt +++ b/libraries/permissions/api/src/main/kotlin/io/element/android/libraries/permissions/api/Util.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/permissions/api/src/main/res/values-hr/translations.xml b/libraries/permissions/api/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..e300660d59 --- /dev/null +++ b/libraries/permissions/api/src/main/res/values-hr/translations.xml @@ -0,0 +1,7 @@ + + + "Kako biste aplikaciji omogućili korištenje kamere, dajte dopuštenje u postavkama sustava." + "Dajte dopuštenje u postavkama sustava." + "Kako biste aplikaciji omogućili korištenje mikrofona, dajte dopuštenje u postavkama sustava." + "Kako biste aplikaciji omogućili prikaz obavijesti, dajte dopuštenje u postavkama sustava." + diff --git a/libraries/permissions/impl/build.gradle.kts b/libraries/permissions/impl/build.gradle.kts index a4fafda599..70e27db7f0 100644 --- a/libraries/permissions/impl/build.gradle.kts +++ b/libraries/permissions/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/AccompanistPermissionStateProvider.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/AccompanistPermissionStateProvider.kt index f2f83e86e5..be5ce1a998 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/AccompanistPermissionStateProvider.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/AccompanistPermissionStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,7 +16,6 @@ import com.google.accompanist.permissions.PermissionState import com.google.accompanist.permissions.rememberPermissionState import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject interface ComposablePermissionStateProvider { @Composable @@ -23,7 +23,6 @@ interface ComposablePermissionStateProvider { } @ContributesBinding(AppScope::class) -@Inject class AccompanistPermissionStateProvider : ComposablePermissionStateProvider { @Composable override fun provide(permission: String, onPermissionResult: (Boolean) -> Unit): PermissionState { diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionStateProvider.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionStateProvider.kt index 6902247970..fadde406b7 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionStateProvider.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,7 +13,6 @@ import android.content.pm.PackageManager import androidx.core.content.ContextCompat import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.permissions.api.PermissionStateProvider @@ -21,7 +21,6 @@ import kotlinx.coroutines.flow.Flow @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DefaultPermissionStateProvider( @ApplicationContext private val context: Context, private val permissionsStore: PermissionsStore, diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt index f12d92a50d..42331e001a 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -27,7 +28,7 @@ import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedInject import dev.zacsweers.metro.ContributesBinding import io.element.android.libraries.core.log.logger.LoggerTag -import io.element.android.libraries.permissions.api.PermissionsEvents +import io.element.android.libraries.permissions.api.PermissionsEvent import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.api.PermissionsState import io.element.android.libraries.permissions.api.PermissionsStore @@ -99,20 +100,20 @@ class DefaultPermissionsPresenter( val showDialog = rememberSaveable { mutableStateOf(false) } - fun handleEvents(event: PermissionsEvents) { + fun handleEvent(event: PermissionsEvent) { when (event) { - PermissionsEvents.CloseDialog -> { + PermissionsEvent.CloseDialog -> { showDialog.value = false } - PermissionsEvents.RequestPermissions -> { + PermissionsEvent.RequestPermissions -> { if (permissionState.status !is PermissionStatus.Granted && isAlreadyDenied) { showDialog.value = true } else { permissionState.launchPermissionRequest() } } - PermissionsEvents.OpenSystemSettingAndCloseDialog -> { - permissionActions.openSettings() + PermissionsEvent.OpenSystemSettingAndCloseDialog -> { + permissionActions.openSettings(permission) showDialog.value = false } } @@ -125,7 +126,7 @@ class DefaultPermissionsPresenter( showDialog = showDialog.value, permissionAlreadyAsked = isAlreadyAsked, permissionAlreadyDenied = isAlreadyDenied, - eventSink = { handleEvents(it) } + eventSink = ::handleEvent, ) } diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsStore.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsStore.kt index bd495b1d4b..facc633b57 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsStore.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,6 @@ import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.permissions.api.PermissionsStore import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory @@ -19,7 +19,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @ContributesBinding(AppScope::class) -@Inject class DefaultPermissionsStore( preferenceDataStoreFactory: PreferenceDataStoreFactory, ) : PermissionsStore { diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/AndroidPermissionActions.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/AndroidPermissionActions.kt index e2a3701774..1be8428890 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/AndroidPermissionActions.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/AndroidPermissionActions.kt @@ -1,25 +1,29 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.permissions.impl.action +import android.Manifest import android.content.Context import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject +import io.element.android.libraries.androidutils.system.openAppSettingsPage import io.element.android.libraries.androidutils.system.startNotificationSettingsIntent import io.element.android.libraries.di.annotations.ApplicationContext @ContributesBinding(AppScope::class) -@Inject class AndroidPermissionActions( @ApplicationContext private val context: Context ) : PermissionActions { - override fun openSettings() { - context.startNotificationSettingsIntent() + override fun openSettings(permission: String) { + when (permission) { + Manifest.permission.POST_NOTIFICATIONS -> context.startNotificationSettingsIntent() + else -> context.openAppSettingsPage() + } } } diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/PermissionActions.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/PermissionActions.kt index 05fc404284..2b1249b823 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/PermissionActions.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/PermissionActions.kt @@ -1,12 +1,13 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.permissions.impl.action interface PermissionActions { - fun openSettings() + fun openSettings(permission: String) } diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTest.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTest.kt index eeadb5598c..eff20fabec 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTest.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -60,6 +61,6 @@ class NotificationTroubleshootCheckPermissionTest( navigator: NotificationTroubleshootNavigator, ) { // Do not bother about asking the permission inline, just lead the user to the settings - permissionActions.openSettings() + permissionActions.openSettings(Manifest.permission.POST_NOTIFICATIONS) } } diff --git a/libraries/permissions/impl/src/main/res/values-hr/translations.xml b/libraries/permissions/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..e0efeca8b2 --- /dev/null +++ b/libraries/permissions/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,5 @@ + + + "Provjerite može li aplikacija prikazivati ​​obavijesti." + "Provjeri dopuštenja" + 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 601ef5cbbb..c7a10ca8d1 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,21 +10,23 @@ package io.element.android.libraries.permissions.impl -import app.cash.molecule.RecompositionMode -import app.cash.molecule.moleculeFlow -import app.cash.turbine.test import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.PermissionStatus import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.permissions.api.PermissionsEvents +import io.element.android.libraries.permissions.api.PermissionsEvent +import io.element.android.libraries.permissions.api.PermissionsStore import io.element.android.libraries.permissions.impl.action.FakePermissionActions +import io.element.android.libraries.permissions.impl.action.PermissionActions import io.element.android.libraries.permissions.test.InMemoryPermissionsStore import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value +import io.element.android.tests.testutils.test import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test -const val A_PERMISSION = "A_PERMISSION" +private const val A_PERMISSION = "A_PERMISSION" class DefaultPermissionsPresenterTest { @get:Rule @@ -31,24 +34,8 @@ class DefaultPermissionsPresenterTest { @Test fun `present - initial state`() = runTest { - val permissionsStore = InMemoryPermissionsStore() - val permissionState = FakePermissionState( - A_PERMISSION, - PermissionStatus.Granted - ) - val permissionStateProvider = - FakeComposablePermissionStateProvider( - permissionState - ) - val presenter = DefaultPermissionsPresenter( - A_PERMISSION, - permissionsStore, - permissionStateProvider, - FakePermissionActions(), - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val presenter = createPresenter() + presenter.test { val initialState = awaitItem() assertThat(initialState.permission).isEqualTo(A_PERMISSION) assertThat(initialState.permissionGranted).isTrue() @@ -65,29 +52,22 @@ class DefaultPermissionsPresenterTest { permissionDenied = true, permissionAsked = true ) - val permissionState = FakePermissionState( - A_PERMISSION, - PermissionStatus.Denied(shouldShowRationale = false) + val permissionStateProvider = FakeComposablePermissionStateProvider( + permissionState = aFakePermissionState( + initialStatus = PermissionStatus.Denied(shouldShowRationale = false) + ), ) - val permissionStateProvider = - FakeComposablePermissionStateProvider( - permissionState - ) - val presenter = DefaultPermissionsPresenter( - A_PERMISSION, - permissionsStore, - permissionStateProvider, - FakePermissionActions(), + val presenter = createPresenter( + permissionsStore = permissionsStore, + permissionStateProvider = permissionStateProvider, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + presenter.test { skipItems(1) val initialState = awaitItem() - initialState.eventSink.invoke(PermissionsEvents.RequestPermissions) + initialState.eventSink.invoke(PermissionsEvent.RequestPermissions) val withDialogState = awaitItem() assertThat(withDialogState.showDialog).isTrue() - withDialogState.eventSink.invoke(PermissionsEvents.CloseDialog) + withDialogState.eventSink.invoke(PermissionsEvent.CloseDialog) assertThat(awaitItem().showDialog).isFalse() } } @@ -98,59 +78,48 @@ class DefaultPermissionsPresenterTest { permissionDenied = true, permissionAsked = true ) - val permissionState = FakePermissionState( - A_PERMISSION, - PermissionStatus.Denied(shouldShowRationale = false) + val permissionStateProvider = FakeComposablePermissionStateProvider( + permissionState = aFakePermissionState( + initialStatus = PermissionStatus.Denied(shouldShowRationale = false), + ), ) - val permissionStateProvider = - FakeComposablePermissionStateProvider( - permissionState - ) - val permissionActions = FakePermissionActions() - val presenter = DefaultPermissionsPresenter( - A_PERMISSION, - permissionsStore, - permissionStateProvider, - permissionActions, + val openSettingsAction = lambdaRecorder { } + val permissionActions = FakePermissionActions( + openSettingsAction = openSettingsAction, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val presenter = createPresenter( + permissionsStore = permissionsStore, + permissionStateProvider = permissionStateProvider, + permissionActions = permissionActions, + ) + presenter.test { skipItems(1) val initialState = awaitItem() - initialState.eventSink.invoke(PermissionsEvents.RequestPermissions) + initialState.eventSink.invoke(PermissionsEvent.RequestPermissions) val withDialogState = awaitItem() assertThat(withDialogState.showDialog).isTrue() - assertThat(permissionActions.openSettingsCalled).isFalse() - withDialogState.eventSink.invoke(PermissionsEvents.OpenSystemSettingAndCloseDialog) + openSettingsAction.assertions().isNeverCalled() + withDialogState.eventSink.invoke(PermissionsEvent.OpenSystemSettingAndCloseDialog) assertThat(awaitItem().showDialog).isFalse() - assertThat(permissionActions.openSettingsCalled).isTrue() + openSettingsAction.assertions().isCalledOnce().with(value(A_PERMISSION)) } } @Test fun `present - user does not grant permission`() = runTest { - val permissionsStore = InMemoryPermissionsStore() - val permissionState = FakePermissionState( - A_PERMISSION, - PermissionStatus.Denied(shouldShowRationale = false) + val permissionState = aFakePermissionState( + initialStatus = PermissionStatus.Denied(shouldShowRationale = false) ) - val permissionStateProvider = - FakeComposablePermissionStateProvider( - permissionState - ) - val presenter = DefaultPermissionsPresenter( - A_PERMISSION, - permissionsStore, - permissionStateProvider, - FakePermissionActions(), + val permissionStateProvider = FakeComposablePermissionStateProvider( + permissionState = permissionState, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val presenter = createPresenter( + permissionStateProvider = permissionStateProvider, + ) + presenter.test { val initialState = awaitItem() assertThat(initialState.showDialog).isFalse() - initialState.eventSink.invoke(PermissionsEvents.RequestPermissions) + initialState.eventSink.invoke(PermissionsEvent.RequestPermissions) assertThat(permissionState.launchPermissionRequestCalled).isTrue() // User does not grant permission permissionStateProvider.userGiveAnswer(answer = false, firstTime = true) @@ -165,27 +134,19 @@ class DefaultPermissionsPresenterTest { @Test fun `present - user does not grant permission second time`() = runTest { - val permissionsStore = InMemoryPermissionsStore() - val permissionState = FakePermissionState( - A_PERMISSION, - PermissionStatus.Denied(shouldShowRationale = true) + val permissionState = aFakePermissionState( + initialStatus = PermissionStatus.Denied(shouldShowRationale = true) ) - val permissionStateProvider = - FakeComposablePermissionStateProvider( - permissionState - ) - val presenter = DefaultPermissionsPresenter( - A_PERMISSION, - permissionsStore, - permissionStateProvider, - FakePermissionActions(), + val permissionStateProvider = FakeComposablePermissionStateProvider( + permissionState = permissionState, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val presenter = createPresenter( + permissionStateProvider = permissionStateProvider, + ) + presenter.test { val initialState = awaitItem() assertThat(initialState.showDialog).isFalse() - initialState.eventSink.invoke(PermissionsEvents.RequestPermissions) + initialState.eventSink.invoke(PermissionsEvent.RequestPermissions) assertThat(permissionState.launchPermissionRequestCalled).isTrue() // User does not grant permission permissionStateProvider.userGiveAnswer(answer = false, firstTime = false) @@ -200,31 +161,24 @@ class DefaultPermissionsPresenterTest { @Test fun `present - user does not grant permission third time`() = runTest { - val permissionsStore = - InMemoryPermissionsStore( - permissionDenied = true, - permissionAsked = true - ) - val permissionState = FakePermissionState( - A_PERMISSION, - PermissionStatus.Denied(shouldShowRationale = false) + val permissionsStore = InMemoryPermissionsStore( + permissionDenied = true, + permissionAsked = true, ) - val permissionStateProvider = - FakeComposablePermissionStateProvider( - permissionState - ) - val presenter = DefaultPermissionsPresenter( - A_PERMISSION, - permissionsStore, - permissionStateProvider, - FakePermissionActions(), + val permissionState = aFakePermissionState( + initialStatus = PermissionStatus.Denied(shouldShowRationale = false), ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val permissionStateProvider = FakeComposablePermissionStateProvider( + permissionState = permissionState, + ) + val presenter = createPresenter( + permissionsStore = permissionsStore, + permissionStateProvider = permissionStateProvider, + ) + presenter.test { skipItems(1) val initialState = awaitItem() - initialState.eventSink.invoke(PermissionsEvents.RequestPermissions) + initialState.eventSink.invoke(PermissionsEvent.RequestPermissions) val withDialogState = awaitItem() assertThat(withDialogState.showDialog).isTrue() assertThat(withDialogState.permissionGranted).isFalse() @@ -235,27 +189,19 @@ class DefaultPermissionsPresenterTest { @Test fun `present - user grants permission`() = runTest { - val permissionsStore = InMemoryPermissionsStore() - val permissionState = FakePermissionState( - A_PERMISSION, - PermissionStatus.Denied(shouldShowRationale = false) + val permissionState = aFakePermissionState( + initialStatus = PermissionStatus.Denied(shouldShowRationale = false) ) - val permissionStateProvider = - FakeComposablePermissionStateProvider( - permissionState - ) - val presenter = DefaultPermissionsPresenter( - A_PERMISSION, - permissionsStore, - permissionStateProvider, - FakePermissionActions(), + val permissionStateProvider = FakeComposablePermissionStateProvider( + permissionState = permissionState, ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { + val presenter = createPresenter( + permissionStateProvider = permissionStateProvider, + ) + presenter.test { val initialState = awaitItem() assertThat(initialState.showDialog).isFalse() - initialState.eventSink.invoke(PermissionsEvents.RequestPermissions) + initialState.eventSink.invoke(PermissionsEvent.RequestPermissions) assertThat(permissionState.launchPermissionRequestCalled).isTrue() // User grants permission permissionStateProvider.userGiveAnswer(answer = true, firstTime = true) @@ -268,3 +214,25 @@ class DefaultPermissionsPresenterTest { } } } + +private fun createPresenter( + permission: String = A_PERMISSION, + permissionsStore: PermissionsStore = InMemoryPermissionsStore(), + permissionStateProvider: ComposablePermissionStateProvider = FakeComposablePermissionStateProvider( + permissionState = aFakePermissionState(), + ), + permissionActions: PermissionActions = FakePermissionActions(), +) = DefaultPermissionsPresenter( + permission = permission, + permissionsStore = permissionsStore, + composablePermissionStateProvider = permissionStateProvider, + permissionActions = permissionActions, +) + +private fun aFakePermissionState( + permission: String = A_PERMISSION, + initialStatus: PermissionStatus = PermissionStatus.Granted, +) = FakePermissionState( + permission = permission, + initialStatus = initialStatus, +) diff --git a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/FakeComposablePermissionStateProvider.kt b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/FakeComposablePermissionStateProvider.kt index 95f45078a7..eca61c67ba 100644 --- a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/FakeComposablePermissionStateProvider.kt +++ b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/FakeComposablePermissionStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/action/FakePermissionActions.kt b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/action/FakePermissionActions.kt index 33504c24a8..df66dee377 100644 --- a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/action/FakePermissionActions.kt +++ b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/action/FakePermissionActions.kt @@ -1,20 +1,19 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.permissions.impl.action -class FakePermissionActions( - val openSettingsAction: () -> Unit = {} -) : PermissionActions { - var openSettingsCalled = false - private set +import io.element.android.tests.testutils.lambda.lambdaError - override fun openSettings() { - openSettingsAction() - openSettingsCalled = true +class FakePermissionActions( + val openSettingsAction: (String) -> Unit = { lambdaError() } +) : PermissionActions { + override fun openSettings(permission: String) { + openSettingsAction(permission) } } diff --git a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt index 41a7260ca2..03a9027f74 100644 --- a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt +++ b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/permissions/noop/build.gradle.kts b/libraries/permissions/noop/build.gradle.kts index 343e913b60..d9aad63530 100644 --- a/libraries/permissions/noop/build.gradle.kts +++ b/libraries/permissions/noop/build.gradle.kts @@ -1,9 +1,10 @@ import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/permissions/noop/src/main/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenter.kt b/libraries/permissions/noop/src/main/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenter.kt index ed3d5b35e1..fb18fa6fed 100644 --- a/libraries/permissions/noop/src/main/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenter.kt +++ b/libraries/permissions/noop/src/main/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 fc9c6b0f98..aff3c87452 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/permissions/test/build.gradle.kts b/libraries/permissions/test/build.gradle.kts index 3615ff38a6..1601143f46 100644 --- a/libraries/permissions/test/build.gradle.kts +++ b/libraries/permissions/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/FakePermissionStateProvider.kt b/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/FakePermissionStateProvider.kt index 0365ebfbbe..b7fa096a86 100644 --- a/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/FakePermissionStateProvider.kt +++ b/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/FakePermissionStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/FakePermissionsPresenter.kt b/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/FakePermissionsPresenter.kt index 2aa1d103db..efd584585f 100644 --- a/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/FakePermissionsPresenter.kt +++ b/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/FakePermissionsPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,7 @@ package io.element.android.libraries.permissions.test import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf -import io.element.android.libraries.permissions.api.PermissionsEvents +import io.element.android.libraries.permissions.api.PermissionsEvent import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.api.PermissionsState import io.element.android.libraries.permissions.api.aPermissionsState @@ -17,15 +18,15 @@ import io.element.android.libraries.permissions.api.aPermissionsState class FakePermissionsPresenter( private val initialState: PermissionsState = aPermissionsState(showDialog = false), ) : PermissionsPresenter { - private fun eventSink(events: PermissionsEvents) { - when (events) { - PermissionsEvents.RequestPermissions -> state.value = state.value.copy(showDialog = true, permissionAlreadyAsked = true) - PermissionsEvents.CloseDialog -> state.value = state.value.copy(showDialog = false) - PermissionsEvents.OpenSystemSettingAndCloseDialog -> state.value = state.value.copy(showDialog = false) + private fun handleEvent(event: PermissionsEvent) { + when (event) { + PermissionsEvent.RequestPermissions -> state.value = state.value.copy(showDialog = true, permissionAlreadyAsked = true) + PermissionsEvent.CloseDialog -> state.value = state.value.copy(showDialog = false) + PermissionsEvent.OpenSystemSettingAndCloseDialog -> state.value = state.value.copy(showDialog = false) } } - private val state = mutableStateOf(initialState.copy(eventSink = ::eventSink)) + private val state = mutableStateOf(initialState.copy(eventSink = ::handleEvent)) fun setPermissionGranted() { state.value = state.value.copy(permissionGranted = true) diff --git a/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/FakePermissionsPresenterFactory.kt b/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/FakePermissionsPresenterFactory.kt index 31d030bae4..49c258af02 100644 --- a/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/FakePermissionsPresenterFactory.kt +++ b/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/FakePermissionsPresenterFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/InMemoryPermissionsStore.kt b/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/InMemoryPermissionsStore.kt index d34db961c6..38aff2ff39 100644 --- a/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/InMemoryPermissionsStore.kt +++ b/libraries/permissions/test/src/main/kotlin/io/element/android/libraries/permissions/test/InMemoryPermissionsStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/preferences/api/build.gradle.kts b/libraries/preferences/api/build.gradle.kts index 0e86cfc083..a441616657 100644 --- a/libraries/preferences/api/build.gradle.kts +++ b/libraries/preferences/api/build.gradle.kts @@ -1,9 +1,10 @@ import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt index 7e47785088..476658946a 100644 --- a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt +++ b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/AppPreferencesStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/PreferenceDataStoreFactory.kt b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/PreferenceDataStoreFactory.kt index fefe658752..50f6eb2431 100644 --- a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/PreferenceDataStoreFactory.kt +++ b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/PreferenceDataStoreFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/SessionPreferencesStore.kt b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/SessionPreferencesStore.kt index 4cc16b3056..d3f3fb4e28 100644 --- a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/SessionPreferencesStore.kt +++ b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/SessionPreferencesStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/SessionPreferencesStoreFactory.kt b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/SessionPreferencesStoreFactory.kt index e7d0f6d7bd..2c0038d06e 100644 --- a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/SessionPreferencesStoreFactory.kt +++ b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/SessionPreferencesStoreFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/VideoCompressionPreset.kt b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/VideoCompressionPreset.kt index d68a53a1a5..22e0da1480 100644 --- a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/VideoCompressionPreset.kt +++ b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/VideoCompressionPreset.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/preferences/impl/build.gradle.kts b/libraries/preferences/impl/build.gradle.kts index f63c057183..c567471da4 100644 --- a/libraries/preferences/impl/build.gradle.kts +++ b/libraries/preferences/impl/build.gradle.kts @@ -1,9 +1,10 @@ import extension.setupDependencyInjection /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt index b3890a8a7d..6856f8bdb6 100644 --- a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt +++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultAppPreferencesStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,7 +13,6 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.matrix.api.media.MediaPreviewValue @@ -32,7 +32,6 @@ private val logLevelKey = stringPreferencesKey("logLevel") private val traceLogPacksKey = stringPreferencesKey("traceLogPacks") @ContributesBinding(AppScope::class) -@Inject class DefaultAppPreferencesStore( private val buildMeta: BuildMeta, preferenceDataStoreFactory: PreferenceDataStoreFactory, diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultPreferencesDataStoreFactory.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultPreferencesDataStoreFactory.kt index 78fccf0e84..267961c11b 100644 --- a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultPreferencesDataStoreFactory.kt +++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultPreferencesDataStoreFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,6 @@ import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.preferencesDataStore import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.androidutils.preferences.DefaultPreferencesCorruptionHandlerFactory import io.element.android.libraries.di.annotations.ApplicationContext @@ -22,7 +22,6 @@ import java.util.concurrent.ConcurrentHashMap @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DefaultPreferencesDataStoreFactory( @ApplicationContext private val context: Context, ) : PreferenceDataStoreFactory { diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt index 3d6cd266b1..907d454ada 100644 --- a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt +++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStoreFactory.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStoreFactory.kt index 4f24bec89e..ce76bd9b5c 100644 --- a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStoreFactory.kt +++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStoreFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.libraries.preferences.impl.store import android.content.Context import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.matrix.api.core.SessionId @@ -23,7 +23,6 @@ import java.util.concurrent.ConcurrentHashMap @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DefaultSessionPreferencesStoreFactory( @ApplicationContext private val context: Context, sessionObserver: SessionObserver, @@ -32,8 +31,7 @@ class DefaultSessionPreferencesStoreFactory( init { sessionObserver.addListener(object : SessionListener { - override suspend fun onSessionCreated(userId: String) = Unit - override suspend fun onSessionDeleted(userId: String) { + override suspend fun onSessionDeleted(userId: String, wasLastSession: Boolean) { val sessionPreferences = cache.remove(SessionId(userId)) sessionPreferences?.clear() } diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/SessionPreferencesModule.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/SessionPreferencesModule.kt index 225ea4e3d2..43de0b5a62 100644 --- a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/SessionPreferencesModule.kt +++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/SessionPreferencesModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/SessionPreferencesStoreMigration.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/SessionPreferencesStoreMigration.kt index b43223b988..a19107dba2 100644 --- a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/SessionPreferencesStoreMigration.kt +++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/SessionPreferencesStoreMigration.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/preferences/test/build.gradle.kts b/libraries/preferences/test/build.gradle.kts index 9db4ab1866..d433490e73 100644 --- a/libraries/preferences/test/build.gradle.kts +++ b/libraries/preferences/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,12 +12,12 @@ plugins { android { namespace = "io.element.android.libraries.preferences.test" - - dependencies { - api(projects.libraries.preferences.api) - implementation(projects.libraries.matrix.api) - implementation(projects.tests.testutils) - implementation(libs.coroutines.core) - implementation(libs.androidx.datastore.preferences) - } +} + +dependencies { + api(projects.libraries.preferences.api) + implementation(projects.libraries.matrix.api) + implementation(projects.tests.testutils) + implementation(libs.coroutines.core) + implementation(libs.androidx.datastore.preferences) } diff --git a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/FakePreferenceDataStoreFactory.kt b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/FakePreferenceDataStoreFactory.kt index 7dff109202..dc9bbc26d1 100644 --- a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/FakePreferenceDataStoreFactory.kt +++ b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/FakePreferenceDataStoreFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/FakeSessionPreferencesStoreFactory.kt b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/FakeSessionPreferencesStoreFactory.kt index fbef1a05b6..2fc3d66eed 100644 --- a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/FakeSessionPreferencesStoreFactory.kt +++ b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/FakeSessionPreferencesStoreFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt index d0ee298a66..6e7d22a568 100644 --- a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt +++ b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemoryAppPreferencesStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemorySessionPreferencesStore.kt b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemorySessionPreferencesStore.kt index e5e1923f7b..7e2027d58f 100644 --- a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemorySessionPreferencesStore.kt +++ b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemorySessionPreferencesStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/previewutils/build.gradle.kts b/libraries/previewutils/build.gradle.kts index 92218e9286..3fca91acf6 100644 --- a/libraries/previewutils/build.gradle.kts +++ b/libraries/previewutils/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,11 +12,11 @@ plugins { android { namespace = "io.element.android.libraries.previewutils" - - dependencies { - implementation(projects.libraries.designsystem) - implementation(projects.libraries.matrix.api) - - implementation(libs.kotlinx.collections.immutable) - } +} + +dependencies { + implementation(projects.libraries.designsystem) + implementation(projects.libraries.matrix.api) + + implementation(libs.kotlinx.collections.immutable) } diff --git a/libraries/previewutils/src/main/kotlin/io/element/android/libraries/previewutils/room/RoomMemberFixture.kt b/libraries/previewutils/src/main/kotlin/io/element/android/libraries/previewutils/room/RoomMemberFixture.kt index e699f3c4b2..1b73ccea31 100644 --- a/libraries/previewutils/src/main/kotlin/io/element/android/libraries/previewutils/room/RoomMemberFixture.kt +++ b/libraries/previewutils/src/main/kotlin/io/element/android/libraries/previewutils/room/RoomMemberFixture.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/previewutils/src/main/kotlin/io/element/android/libraries/previewutils/room/SpaceRoomFixture.kt b/libraries/previewutils/src/main/kotlin/io/element/android/libraries/previewutils/room/SpaceRoomFixture.kt index aae6bbfb76..4bf1d2501b 100644 --- a/libraries/previewutils/src/main/kotlin/io/element/android/libraries/previewutils/room/SpaceRoomFixture.kt +++ b/libraries/previewutils/src/main/kotlin/io/element/android/libraries/previewutils/room/SpaceRoomFixture.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/api/build.gradle.kts b/libraries/push/api/build.gradle.kts index 48df34053a..df6ac6120a 100644 --- a/libraries/push/api/build.gradle.kts +++ b/libraries/push/api/build.gradle.kts @@ -1,11 +1,12 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { - id("io.element.android-library") + id("io.element.android-compose-library") } android { @@ -16,7 +17,8 @@ dependencies { implementation(libs.androidx.corektx) implementation(libs.coroutines.core) implementation(libs.coil.compose) + implementation(projects.libraries.designsystem) implementation(projects.libraries.matrix.api) - implementation(projects.libraries.matrixui) + implementation(projects.libraries.matrixmedia.api) implementation(projects.libraries.pushproviders.api) } diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/GetCurrentPushProvider.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/GetCurrentPushProvider.kt index f7a3dc1638..a6fbb47173 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/GetCurrentPushProvider.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/GetCurrentPushProvider.kt @@ -1,12 +1,15 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.api +import io.element.android.libraries.matrix.api.core.SessionId + interface GetCurrentPushProvider { - suspend fun getCurrentPushProvider(): String? + suspend fun getCurrentPushProvider(sessionId: SessionId): String? } diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt index fb1bd14404..a43ee36403 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,6 +10,7 @@ package io.element.android.libraries.push.api import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.push.api.history.PushHistoryItem import io.element.android.libraries.pushproviders.api.Distributor import io.element.android.libraries.pushproviders.api.PushProvider @@ -18,7 +20,7 @@ interface PushService { /** * Return the current push provider, or null if none. */ - suspend fun getCurrentPushProvider(): PushProvider? + suspend fun getCurrentPushProvider(sessionId: SessionId): PushProvider? /** * Return the list of push providers, available at compile time, sorted by index. @@ -36,6 +38,15 @@ interface PushService { distributor: Distributor, ): Result + /** + * Ensure that the pusher with the current push provider and distributor is registered. + * If there is no current config, the default push provider with the default distributor will be used. + * Error can be [PusherRegistrationFailure]. + */ + suspend fun ensurePusherIsRegistered( + matrixClient: MatrixClient, + ): Result + /** * Store the given push provider as the current one, but do not register. * To be used when there is no distributor available. @@ -51,7 +62,7 @@ interface PushService { /** * Return false in case of early error. */ - suspend fun testPush(): Boolean + suspend fun testPush(sessionId: SessionId): Boolean /** * Get a flow of total number of received Push. @@ -72,4 +83,9 @@ interface PushService { * Reset the battery optimization state. */ suspend fun resetBatteryOptimizationState() + + /** + * Notify the user that the service is un-registered. + */ + suspend fun onServiceUnregistered(userId: UserId) } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/PusherRegistrationFailure.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PusherRegistrationFailure.kt similarity index 84% rename from appnav/src/main/kotlin/io/element/android/appnav/loggedin/PusherRegistrationFailure.kt rename to libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PusherRegistrationFailure.kt index 477d8357ca..b8ae677aab 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/PusherRegistrationFailure.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PusherRegistrationFailure.kt @@ -1,11 +1,12 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.appnav.loggedin +package io.element.android.libraries.push.api import io.element.android.libraries.matrix.api.exception.ClientException diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationEvents.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationEvents.kt index ab8d9e92e5..b7a01cc61f 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationEvents.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationState.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationState.kt index 7e7c2b2bd6..6732f7c17d 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationState.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationStateProvider.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationStateProvider.kt index d6d4e5774b..684185f40a 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationStateProvider.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/battery/BatteryOptimizationStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/gateway/PushGatewayFailure.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/gateway/PushGatewayFailure.kt index ea32694932..975e10e134 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/gateway/PushGatewayFailure.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/gateway/PushGatewayFailure.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/history/PushHistoryItem.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/history/PushHistoryItem.kt index 9606e7e6dc..e3a8a494ef 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/history/PushHistoryItem.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/history/PushHistoryItem.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/NotificationBitmapLoader.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/NotificationBitmapLoader.kt index d0fa4c418a..bded69991b 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/NotificationBitmapLoader.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/NotificationBitmapLoader.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,17 +11,18 @@ package io.element.android.libraries.push.api.notifications import android.graphics.Bitmap import androidx.core.graphics.drawable.IconCompat import coil3.ImageLoader +import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.matrix.ui.media.AVATAR_THUMBNAIL_SIZE_IN_PIXEL interface NotificationBitmapLoader { /** * Get icon of a room. - * @param path mxc url + * @param avatarData the data related to the Avatar * @param imageLoader Coil image loader * @param targetSize The size we want the bitmap to be resized to */ suspend fun getRoomBitmap( - path: String?, + avatarData: AvatarData, imageLoader: ImageLoader, targetSize: Long = AVATAR_THUMBNAIL_SIZE_IN_PIXEL, ): Bitmap? @@ -28,8 +30,11 @@ interface NotificationBitmapLoader { /** * Get icon of a user. * Before Android P, this does nothing because the icon won't be used - * @param path mxc url + * @param avatarData the data related to the Avatar * @param imageLoader Coil image loader */ - suspend fun getUserIcon(path: String?, imageLoader: ImageLoader): IconCompat? + suspend fun getUserIcon( + avatarData: AvatarData, + imageLoader: ImageLoader, + ): IconCompat? } diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/NotificationCleaner.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/NotificationCleaner.kt index 4d6850a18f..0a4e35c697 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/NotificationCleaner.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/NotificationCleaner.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,10 +11,12 @@ package io.element.android.libraries.push.api.notifications 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 interface NotificationCleaner { fun clearAllMessagesEvents(sessionId: SessionId) fun clearMessagesForRoom(sessionId: SessionId, roomId: RoomId) + fun clearMessagesForThread(sessionId: SessionId, roomId: RoomId, threadId: ThreadId) fun clearEvent(sessionId: SessionId, eventId: EventId) fun clearMembershipNotificationForSession(sessionId: SessionId) diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/NotificationIdProvider.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/NotificationIdProvider.kt index 83ab37682b..ff7119b647 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/NotificationIdProvider.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/NotificationIdProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -31,12 +32,8 @@ object NotificationIdProvider { return getOffset(sessionId) + FALLBACK_NOTIFICATION_ID } - fun getCallNotificationId(sessionId: SessionId): Int { - return getOffset(sessionId) + ROOM_CALL_NOTIFICATION_ID - } - fun getForegroundServiceNotificationId(type: ForegroundServiceType): Int { - return type.id * 10 + FOREGROUND_SERVICE_NOTIFICATION_ID + return type.ordinal * 10 + FOREGROUND_SERVICE_NOTIFICATION_ID } private fun getOffset(sessionId: SessionId): Int { @@ -49,12 +46,11 @@ object NotificationIdProvider { private const val ROOM_MESSAGES_NOTIFICATION_ID = 1 private const val ROOM_EVENT_NOTIFICATION_ID = 2 private const val ROOM_INVITATION_NOTIFICATION_ID = 3 - private const val ROOM_CALL_NOTIFICATION_ID = 3 private const val FOREGROUND_SERVICE_NOTIFICATION_ID = 4 } -enum class ForegroundServiceType(val id: Int) { - INCOMING_CALL(1), - ONGOING_CALL(2), +enum class ForegroundServiceType { + INCOMING_CALL, + ONGOING_CALL, } diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/OnMissedCallNotificationHandler.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/OnMissedCallNotificationHandler.kt index 5de4dc1838..2ba61925c5 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/OnMissedCallNotificationHandler.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/OnMissedCallNotificationHandler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/conversations/NotificationConversationService.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/conversations/NotificationConversationService.kt index 586aea0fae..504adacdb6 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/conversations/NotificationConversationService.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/notifications/conversations/NotificationConversationService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/push/NotificationEventRequest.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/push/NotificationEventRequest.kt new file mode 100644 index 0000000000..ff38c7a726 --- /dev/null +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/push/NotificationEventRequest.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.api.push + +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 + +data class NotificationEventRequest( + val sessionId: SessionId, + val roomId: RoomId, + val eventId: EventId, + val providerInfo: String, +) diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/push/SyncOnNotifiableEvent.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/push/SyncOnNotifiableEvent.kt new file mode 100644 index 0000000000..bc7bf44ae2 --- /dev/null +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/push/SyncOnNotifiableEvent.kt @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.api.push + +fun interface SyncOnNotifiableEvent { + suspend operator fun invoke(requests: List) +} diff --git a/libraries/push/impl/build.gradle.kts b/libraries/push/impl/build.gradle.kts index fa0f65cb8f..68d32dbd23 100644 --- a/libraries/push/impl/build.gradle.kts +++ b/libraries/push/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { @@ -49,11 +50,15 @@ dependencies { implementation(projects.libraries.network) implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrixui) + implementation(projects.libraries.matrixmedia.api) + implementation(projects.features.networkmonitor.api) implementation(projects.libraries.preferences.api) implementation(projects.libraries.sessionStorage.api) implementation(projects.libraries.uiStrings) implementation(projects.libraries.troubleshoot.api) + implementation(projects.libraries.workmanager.api) implementation(projects.features.call.api) + implementation(projects.features.enterprise.api) implementation(projects.features.lockscreen.api) implementation(projects.libraries.featureflag.api) api(projects.libraries.pushproviders.api) @@ -67,14 +72,19 @@ dependencies { testCommonDependencies(libs) testImplementation(libs.coil.test) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.matrixmedia.test) testImplementation(projects.libraries.preferences.test) testImplementation(projects.libraries.sessionStorage.test) testImplementation(projects.libraries.push.test) testImplementation(projects.libraries.pushproviders.test) testImplementation(projects.libraries.pushstore.test) testImplementation(projects.libraries.troubleshoot.test) + testImplementation(projects.libraries.workmanager.test) testImplementation(projects.features.call.test) + testImplementation(projects.features.enterprise.test) testImplementation(projects.features.lockscreen.test) + testImplementation(projects.features.networkmonitor.test) + testImplementation(projects.services.appnavstate.impl) testImplementation(projects.services.appnavstate.test) testImplementation(projects.services.toolbox.impl) testImplementation(projects.services.toolbox.test) diff --git a/libraries/push/impl/src/debug/res/raw/message.mp3 b/libraries/push/impl/src/debug/res/raw/message.mp3 new file mode 100644 index 0000000000..abc056786c Binary files /dev/null and b/libraries/push/impl/src/debug/res/raw/message.mp3 differ diff --git a/libraries/push/impl/src/main/AndroidManifest.xml b/libraries/push/impl/src/main/AndroidManifest.xml index c08c16ed5f..a15bb34715 100644 --- a/libraries/push/impl/src/main/AndroidManifest.xml +++ b/libraries/push/impl/src/main/AndroidManifest.xml @@ -1,13 +1,13 @@ - diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultGetCurrentPushProvider.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultGetCurrentPushProvider.kt index 9e7ec0d651..a3e6cf2111 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultGetCurrentPushProvider.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultGetCurrentPushProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,25 +10,15 @@ package io.element.android.libraries.push.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.push.api.GetCurrentPushProvider import io.element.android.libraries.pushstore.api.UserPushStoreFactory -import io.element.android.services.appnavstate.api.AppNavigationStateService -import io.element.android.services.appnavstate.api.currentSessionId @ContributesBinding(AppScope::class) -@Inject class DefaultGetCurrentPushProvider( private val pushStoreFactory: UserPushStoreFactory, - private val appNavigationStateService: AppNavigationStateService, ) : GetCurrentPushProvider { - override suspend fun getCurrentPushProvider(): String? { - return appNavigationStateService - .appNavigationState - .value - .navigationState - .currentSessionId() - ?.let { pushStoreFactory.getOrCreate(it) } - ?.getPushProviderName() + override suspend fun getCurrentPushProvider(sessionId: SessionId): String? { + return pushStoreFactory.getOrCreate(sessionId).getPushProviderName() } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt index 45a0e5b4cd..3f3a8d05b4 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,29 +10,33 @@ package io.element.android.libraries.push.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import dev.zacsweers.metro.binding import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.push.api.GetCurrentPushProvider import io.element.android.libraries.push.api.PushService +import io.element.android.libraries.push.api.PusherRegistrationFailure import io.element.android.libraries.push.api.history.PushHistoryItem import io.element.android.libraries.push.impl.push.MutableBatteryOptimizationStore import io.element.android.libraries.push.impl.store.PushDataStore import io.element.android.libraries.push.impl.test.TestPush +import io.element.android.libraries.push.impl.unregistration.ServiceUnregisteredHandler import io.element.android.libraries.pushproviders.api.Distributor import io.element.android.libraries.pushproviders.api.PushProvider +import io.element.android.libraries.pushproviders.api.RegistrationFailure import io.element.android.libraries.pushstore.api.UserPushStoreFactory import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecretStore import io.element.android.libraries.sessionstorage.api.observer.SessionListener import io.element.android.libraries.sessionstorage.api.observer.SessionObserver import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first import timber.log.Timber @ContributesBinding(AppScope::class, binding = binding()) @SingleIn(AppScope::class) -@Inject class DefaultPushService( private val testPush: TestPush, private val userPushStoreFactory: UserPushStoreFactory, @@ -41,13 +46,14 @@ class DefaultPushService( private val pushClientSecretStore: PushClientSecretStore, private val pushDataStore: PushDataStore, private val mutableBatteryOptimizationStore: MutableBatteryOptimizationStore, + private val serviceUnregisteredHandler: ServiceUnregisteredHandler, ) : PushService, SessionListener { init { observeSessions() } - override suspend fun getCurrentPushProvider(): PushProvider? { - val currentPushProvider = getCurrentPushProvider.getCurrentPushProvider() + override suspend fun getCurrentPushProvider(sessionId: SessionId): PushProvider? { + val currentPushProvider = getCurrentPushProvider.getCurrentPushProvider(sessionId) return pushProviders.find { it.name == currentPushProvider } } @@ -82,6 +88,59 @@ class DefaultPushService( return pushProvider.registerWith(matrixClient, distributor) } + override suspend fun ensurePusherIsRegistered(matrixClient: MatrixClient): Result { + val verificationStatus = matrixClient.sessionVerificationService.sessionVerifiedStatus.first() + if (verificationStatus != SessionVerifiedStatus.Verified) { + return Result.failure(PusherRegistrationFailure.AccountNotVerified()) + .also { Timber.w("Account is not verified") } + } + Timber.d("Ensure pusher is registered") + val currentPushProvider = getCurrentPushProvider(matrixClient.sessionId) + val result = if (currentPushProvider == null) { + Timber.d("Register with the first available push provider with at least one distributor") + val pushProvider = getAvailablePushProviders() + .firstOrNull { it.getDistributors().isNotEmpty() } + // Else fallback to the first available push provider (the list should never be empty) + ?: getAvailablePushProviders().firstOrNull() + ?: return Result.failure(PusherRegistrationFailure.NoProvidersAvailable()) + .also { Timber.w("No push providers available") } + val distributor = pushProvider.getDistributors().firstOrNull() + ?: return Result.failure(PusherRegistrationFailure.NoDistributorsAvailable()) + .also { Timber.w("No distributors available") } + .also { + // In this case, consider the push provider is chosen. + selectPushProvider(matrixClient.sessionId, pushProvider) + } + registerWith(matrixClient, pushProvider, distributor) + } else { + val currentPushDistributor = currentPushProvider.getCurrentDistributor(matrixClient.sessionId) + if (currentPushDistributor == null) { + Timber.d("Register with the first available distributor") + val distributor = currentPushProvider.getDistributors().firstOrNull() + ?: return Result.failure(PusherRegistrationFailure.NoDistributorsAvailable()) + .also { Timber.w("No distributors available") } + registerWith(matrixClient, currentPushProvider, distributor) + } else { + Timber.d("Re-register with the current distributor") + registerWith(matrixClient, currentPushProvider, currentPushDistributor) + } + } + return result.fold( + onSuccess = { + Timber.d("Pusher registered") + Result.success(Unit) + }, + onFailure = { + Timber.e(it, "Failed to register pusher") + if (it is RegistrationFailure) { + Result.failure(PusherRegistrationFailure.RegistrationFailure(it.clientException, it.isRegisteringAgain)) + } else { + Result.failure(it) + } + } + ) + } + override suspend fun selectPushProvider( sessionId: SessionId, pushProvider: PushProvider, @@ -99,9 +158,9 @@ class DefaultPushService( userPushStoreFactory.getOrCreate(sessionId).setIgnoreRegistrationError(ignore) } - override suspend fun testPush(): Boolean { - val pushProvider = getCurrentPushProvider() ?: return false - val config = pushProvider.getCurrentUserPushConfig() ?: return false + override suspend fun testPush(sessionId: SessionId): Boolean { + val pushProvider = getCurrentPushProvider(sessionId) ?: return false + val config = pushProvider.getPushConfig(sessionId) ?: return false testPush.execute(config) return true } @@ -110,10 +169,6 @@ class DefaultPushService( sessionObserver.addListener(this) } - override suspend fun onSessionCreated(userId: String) { - // Nothing to do - } - /** * The session has been deleted. * In this case, this is not necessary to unregister the pusher from the homeserver, @@ -121,7 +176,7 @@ class DefaultPushService( * The current push provider may want to take action, and we need to * cleanup the stores. */ - override suspend fun onSessionDeleted(userId: String) { + override suspend fun onSessionDeleted(userId: String, wasLastSession: Boolean) { val sessionId = SessionId(userId) val userPushStore = userPushStoreFactory.getOrCreate(sessionId) val currentPushProviderName = userPushStore.getPushProviderName() @@ -146,4 +201,8 @@ class DefaultPushService( override suspend fun resetBatteryOptimizationState() { mutableBatteryOptimizationStore.reset() } + + override suspend fun onServiceUnregistered(userId: UserId) { + serviceUnregisteredHandler.handle(userId) + } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPusherSubscriber.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPusherSubscriber.kt index 7dbe4e795e..39f9d3acd6 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPusherSubscriber.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPusherSubscriber.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.push.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.appconfig.PushConfig import io.element.android.libraries.core.extensions.mapFailure import io.element.android.libraries.core.log.logger.LoggerTag @@ -30,7 +30,6 @@ internal const val DEFAULT_PUSHER_FILE_TAG = "mobile" private val loggerTag = LoggerTag("DefaultPusherSubscriber", LoggerTag.PushLoggerTag) @ContributesBinding(AppScope::class) -@Inject class DefaultPusherSubscriber( private val buildMeta: BuildMeta, private val pushClientSecret: PushClientSecret, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimization.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimization.kt index 8379289f88..0912987477 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimization.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimization.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -17,7 +18,6 @@ import androidx.core.content.getSystemService import androidx.core.net.toUri import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.services.toolbox.api.intent.ExternalIntentLauncher import timber.log.Timber @@ -45,7 +45,6 @@ interface BatteryOptimization { } @ContributesBinding(AppScope::class) -@Inject class AndroidBatteryOptimization( @ApplicationContext private val context: Context, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenter.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenter.kt index 3c876ece1d..e9af494686 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenter.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -47,7 +48,7 @@ class BatteryOptimizationPresenter( onPauseOrDispose {} } - fun handleEvents(event: BatteryOptimizationEvents) { + fun handleEvent(event: BatteryOptimizationEvents) { when (event) { BatteryOptimizationEvents.Dismiss -> coroutineScope.launch { mutableBatteryOptimizationStore.onOptimizationBannerDismissed() @@ -66,7 +67,7 @@ class BatteryOptimizationPresenter( return BatteryOptimizationState( shouldDisplayBanner = localShouldDisplayBanner && storeShouldDisplayBanner && !isSystemIgnoringBatteryOptimizations, - eventSink = ::handleEvents, + eventSink = ::handleEvent, ) } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/di/PushModule.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/di/PushModule.kt index bdd1dc6d7c..b7e807523c 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/di/PushModule.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/di/PushModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/DefaultPushHistoryService.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/DefaultPushHistoryService.kt index c69a09b31f..2c8dc5480a 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/DefaultPushHistoryService.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/DefaultPushHistoryService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,6 @@ import android.os.PowerManager import androidx.core.content.getSystemService import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId @@ -23,7 +23,6 @@ import io.element.android.libraries.push.impl.db.PushHistory import io.element.android.services.toolbox.api.systemclock.SystemClock @ContributesBinding(AppScope::class) -@Inject class DefaultPushHistoryService( private val pushDatabase: PushDatabase, private val systemClock: SystemClock, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/PushHistoryService.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/PushHistoryService.kt index 31d431bb2c..8096ad222e 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/PushHistoryService.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/PushHistoryService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/di/PushHistoryModule.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/di/PushHistoryModule.kt index b1eb45e433..6472883d4d 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/di/PushHistoryModule.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/history/di/PushHistoryModule.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/intent/IntentProvider.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/intent/IntentProvider.kt index f98b10674c..82ee730d17 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/intent/IntentProvider.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/intent/IntentProvider.kt @@ -1,13 +1,16 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.intent import android.content.Intent +import android.os.Bundle +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 @@ -20,5 +23,7 @@ interface IntentProvider { sessionId: SessionId, roomId: RoomId?, threadId: ThreadId?, + eventId: EventId?, + extras: Bundle? = null, ): Intent } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/ActiveNotificationsProvider.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/ActiveNotificationsProvider.kt index 11717c081a..4cc279b8bb 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/ActiveNotificationsProvider.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/ActiveNotificationsProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,15 +12,24 @@ import android.service.notification.StatusBarNotification import androidx.core.app.NotificationManagerCompat import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.extensions.runCatchingExceptions 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.api.notifications.NotificationIdProvider +import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator import timber.log.Timber interface ActiveNotificationsProvider { - fun getMessageNotificationsForRoom(sessionId: SessionId, roomId: RoomId): List + /** + * Gets the displayed notifications for the combination of [sessionId], [roomId] and [threadId]. + */ + fun getMessageNotificationsForRoom(sessionId: SessionId, roomId: RoomId, threadId: ThreadId?): List + + /** + * Gets all displayed notifications associated to [sessionId] and [roomId]. These will include all thread notifications as well. + */ + fun getAllMessageNotificationsForRoom(sessionId: SessionId, roomId: RoomId): List fun getNotificationsForSession(sessionId: SessionId): List fun getMembershipNotificationForSession(sessionId: SessionId): List fun getMembershipNotificationForRoom(sessionId: SessionId, roomId: RoomId): List @@ -28,7 +38,6 @@ interface ActiveNotificationsProvider { } @ContributesBinding(AppScope::class) -@Inject class DefaultActiveNotificationsProvider( private val notificationManager: NotificationManagerCompat, ) : ActiveNotificationsProvider { @@ -46,9 +55,15 @@ class DefaultActiveNotificationsProvider( return getNotificationsForSession(sessionId).filter { it.id == notificationId } } - override fun getMessageNotificationsForRoom(sessionId: SessionId, roomId: RoomId): List { + override fun getMessageNotificationsForRoom(sessionId: SessionId, roomId: RoomId, threadId: ThreadId?): List { val notificationId = NotificationIdProvider.getRoomMessagesNotificationId(sessionId) - return getNotificationsForSession(sessionId).filter { it.id == notificationId && it.tag == roomId.value } + val expectedTag = NotificationCreator.messageTag(roomId, threadId) + return getNotificationsForSession(sessionId).filter { it.id == notificationId && it.tag == expectedTag } + } + + override fun getAllMessageNotificationsForRoom(sessionId: SessionId, roomId: RoomId): List { + val notificationId = NotificationIdProvider.getRoomMessagesNotificationId(sessionId) + return getNotificationsForSession(sessionId).filter { it.id == notificationId && it.tag.startsWith(roomId.value) } } override fun getMembershipNotificationForRoom(sessionId: SessionId, roomId: RoomId): List { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/CallNotificationEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/CallNotificationEventResolver.kt index 689cc8a2ea..444d0c725a 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/CallNotificationEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/CallNotificationEventResolver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.push.impl.notifications import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.matrix.api.core.SessionId @@ -47,7 +47,6 @@ interface CallNotificationEventResolver { } @ContributesBinding(AppScope::class) -@Inject class DefaultCallNotificationEventResolver( private val stringProvider: StringProvider, private val appForegroundStateService: AppForegroundStateService, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt index 868c7e767a..e3d0f21d03 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt @@ -1,23 +1,28 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.notifications import android.content.Context +import android.graphics.ImageDecoder import android.net.Uri +import android.os.Build +import androidx.core.app.NotificationCompat import androidx.core.content.FileProvider import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.core.extensions.flatMap import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.di.annotations.ApplicationContext +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.matrix.api.core.EventId @@ -44,6 +49,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageTy import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType import io.element.android.libraries.matrix.ui.messages.toPlainText +import io.element.android.libraries.push.api.push.NotificationEventRequest import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent @@ -77,7 +83,6 @@ interface NotifiableEventResolver { @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) -@Inject class DefaultNotifiableEventResolver( private val stringProvider: StringProvider, private val matrixClientProvider: MatrixClientProvider, @@ -86,6 +91,7 @@ class DefaultNotifiableEventResolver( private val permalinkParser: PermalinkParser, private val callNotificationEventResolver: CallNotificationEventResolver, private val fallbackNotificationFactory: FallbackNotificationFactory, + private val featureFlagService: FeatureFlagService, ) : NotifiableEventResolver { override suspend fun resolveEvents( sessionId: SessionId, @@ -93,9 +99,12 @@ class DefaultNotifiableEventResolver( ): ResolvePushEventsResult { Timber.d("Queueing notifications: $notificationEventRequests") val client = matrixClientProvider.getOrRestore(sessionId).getOrElse { - return Result.failure(IllegalStateException("Couldn't get or restore client for session $sessionId")) + return Result.failure(it) } - val ids = notificationEventRequests.groupBy { it.roomId }.mapValues { (_, value) -> value.map { it.eventId } } + val ids = notificationEventRequests.groupBy { it.roomId } + .mapValues { (_, requests) -> + requests.map { it.eventId } + } // TODO this notificationData is not always valid at the moment, sometimes the Rust SDK can't fetch the matching event val notificationsResult = client.notificationService.getNotifications(ids) @@ -133,19 +142,25 @@ class DefaultNotifiableEventResolver( is NotificationContent.MessageLike.RoomMessage -> { val showMediaPreview = client.mediaPreviewService.getMediaPreviewValue() == MediaPreviewValue.On val senderDisambiguatedDisplayName = getDisambiguatedDisplayName(content.senderId) - val messageBody = descriptionFromMessageContent(content, senderDisambiguatedDisplayName) + val imageMimeType = if (showMediaPreview) content.getImageMimetype() else null + val imageUriString = imageMimeType?.let { content.fetchImageIfPresent(client, imageMimeType)?.toString() } + val messageBody = descriptionFromMessageContent( + content = content, + senderDisambiguatedDisplayName = senderDisambiguatedDisplayName, + hasImageUri = imageUriString != null, + ) val notifiableMessageEvent = buildNotifiableMessageEvent( sessionId = userId, senderId = content.senderId, roomId = roomId, eventId = eventId, - threadId = threadId, + threadId = threadId.takeIf { featureFlagService.isFeatureEnabled(FeatureFlags.Threads) }, noisy = isNoisy, timestamp = this.timestamp, senderDisambiguatedDisplayName = senderDisambiguatedDisplayName, body = messageBody, - imageUriString = if (showMediaPreview) content.fetchImageIfPresent(client)?.toString() else null, - imageMimeType = if (showMediaPreview) content.getImageMimetype() else null, + imageUriString = imageUriString, + imageMimeType = imageMimeType.takeIf { imageUriString != null }, roomName = roomDisplayName, roomIsDm = isDm, roomAvatarPath = roomAvatarUrl, @@ -168,7 +183,11 @@ class DefaultNotifiableEventResolver( soundName = null, isRedacted = false, isUpdated = false, - description = descriptionFromRoomMembershipInvite(senderDisambiguatedDisplayName, isDirect), + description = descriptionFromRoomMembershipInvite( + senderDisambiguatedDisplayName = senderDisambiguatedDisplayName, + isDirectRoom = isDirect, + isSpace = isSpace + ), // TODO check if type is needed anymore type = null, // TODO check if title is needed anymore @@ -294,13 +313,18 @@ class DefaultNotifiableEventResolver( private fun descriptionFromMessageContent( content: NotificationContent.MessageLike.RoomMessage, senderDisambiguatedDisplayName: String, - ): String { + hasImageUri: Boolean, + ): String? { return when (val messageType = content.messageType) { is AudioMessageType -> messageType.bestDescription is VoiceMessageType -> stringProvider.getString(CommonStrings.common_voice_message) is EmoteMessageType -> "* $senderDisambiguatedDisplayName ${messageType.body}" is FileMessageType -> messageType.bestDescription - is ImageMessageType -> messageType.bestDescription + is ImageMessageType -> if (hasImageUri) { + messageType.caption + } else { + messageType.bestDescription + } is StickerMessageType -> messageType.bestDescription is NoticeMessageType -> messageType.body is TextMessageType -> messageType.toPlainText(permalinkParser = permalinkParser) @@ -312,23 +336,50 @@ class DefaultNotifiableEventResolver( private fun descriptionFromRoomMembershipInvite( senderDisambiguatedDisplayName: String, - isDirectRoom: Boolean + isDirectRoom: Boolean, + isSpace: Boolean, ): String { - return if (isDirectRoom) { - stringProvider.getString(R.string.notification_invite_body_with_sender, senderDisambiguatedDisplayName) - } else { - stringProvider.getString(R.string.notification_room_invite_body_with_sender, senderDisambiguatedDisplayName) + return when { + isDirectRoom -> { + stringProvider.getString(R.string.notification_invite_body_with_sender, senderDisambiguatedDisplayName) + } + isSpace -> { + stringProvider.getString(R.string.notification_space_invite_body_with_sender, senderDisambiguatedDisplayName) + } + else -> { + stringProvider.getString(R.string.notification_room_invite_body_with_sender, senderDisambiguatedDisplayName) + } } } - private suspend fun NotificationContent.MessageLike.RoomMessage.fetchImageIfPresent(client: MatrixClient): Uri? { + /** + * Fetch the image for message type, only if the mime type is supported, as recommended + * per [NotificationCompat.MessagingStyle.Message.setData] documentation. + * Then convert to a [Uri] accessible to the Notification Service. + */ + private suspend fun NotificationContent.MessageLike.RoomMessage.fetchImageIfPresent( + client: MatrixClient, + mimeType: String, + ): Uri? { val fileResult = when (val messageType = messageType) { - is ImageMessageType -> notificationMediaRepoFactory.create(client) - .getMediaFile( - mediaSource = messageType.source, - mimeType = messageType.info?.mimetype, - filename = messageType.filename, - ) + is ImageMessageType -> { + val isMimeTypeSupported = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ImageDecoder.isMimeTypeSupported(mimeType) + } else { + // Assume it's supported on old systems... + true + } + if (isMimeTypeSupported) { + notificationMediaRepoFactory.create(client).getMediaFile( + mediaSource = messageType.source, + mimeType = messageType.info?.mimetype, + filename = messageType.filename, + ) + } else { + Timber.tag(loggerTag.value).d("Mime type $mimeType not supported by the system") + null + } + } is VideoMessageType -> null // Use the thumbnail here? else -> null } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationBitmapLoader.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationBitmapLoader.kt index 545ef59c6d..086914ecf5 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationBitmapLoader.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationBitmapLoader.kt @@ -1,13 +1,15 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.notifications import android.content.Context +import android.content.res.Configuration import android.graphics.Bitmap import android.os.Build import androidx.core.graphics.drawable.IconCompat @@ -18,62 +20,85 @@ import coil3.toBitmap import coil3.transform.CircleCropTransformation import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject +import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.ui.media.AVATAR_THUMBNAIL_SIZE_IN_PIXEL +import io.element.android.libraries.matrix.ui.media.InitialsAvatarBitmapGenerator import io.element.android.libraries.matrix.ui.media.MediaRequestData import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider import timber.log.Timber @ContributesBinding(AppScope::class) -@Inject class DefaultNotificationBitmapLoader( @ApplicationContext private val context: Context, private val sdkIntProvider: BuildVersionSdkIntProvider, + private val initialsAvatarBitmapGenerator: InitialsAvatarBitmapGenerator, ) : NotificationBitmapLoader { - override suspend fun getRoomBitmap(path: String?, imageLoader: ImageLoader, targetSize: Long): Bitmap? { - if (path == null) { - return null - } - return loadRoomBitmap(path, imageLoader, targetSize) - } - - private suspend fun loadRoomBitmap(path: String, imageLoader: ImageLoader, targetSize: Long): Bitmap? { + override suspend fun getRoomBitmap( + avatarData: AvatarData, + imageLoader: ImageLoader, + targetSize: Long, + ): Bitmap? { return try { - val imageRequest = ImageRequest.Builder(context) - .data(MediaRequestData(MediaSource(path), MediaRequestData.Kind.Thumbnail(targetSize))) - .transformations(CircleCropTransformation()) - .build() - val result = imageLoader.execute(imageRequest) - result.image?.toBitmap() + loadBitmap( + avatarData = avatarData, + imageLoader = imageLoader, + targetSize = targetSize, + ) } catch (e: Throwable) { Timber.e(e, "Unable to load room bitmap") null } } - override suspend fun getUserIcon(path: String?, imageLoader: ImageLoader): IconCompat? { - if (path == null || sdkIntProvider.get() < Build.VERSION_CODES.P) { + override suspend fun getUserIcon( + avatarData: AvatarData, + imageLoader: ImageLoader, + ): IconCompat? { + if (sdkIntProvider.get() < Build.VERSION_CODES.P) { return null } - - return loadUserIcon(path, imageLoader) - } - - private suspend fun loadUserIcon(path: String, imageLoader: ImageLoader): IconCompat? { return try { - val imageRequest = ImageRequest.Builder(context) - .data(MediaRequestData(MediaSource(path), MediaRequestData.Kind.Thumbnail(AVATAR_THUMBNAIL_SIZE_IN_PIXEL))) - .transformations(CircleCropTransformation()) - .build() - val result = imageLoader.execute(imageRequest) - val bitmap = result.image?.toBitmap() - return bitmap?.let { IconCompat.createWithBitmap(it) } + loadBitmap( + avatarData = avatarData, + imageLoader = imageLoader, + targetSize = AVATAR_THUMBNAIL_SIZE_IN_PIXEL, + ) + ?.let { IconCompat.createWithBitmap(it) } } catch (e: Throwable) { Timber.e(e, "Unable to load user bitmap") null } } + + private fun isDarkTheme(): Boolean { + return context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES + } + + private suspend fun loadBitmap( + avatarData: AvatarData, + imageLoader: ImageLoader, + targetSize: Long + ): Bitmap? { + val path = avatarData.url + val data = if (path != null) { + MediaRequestData( + source = MediaSource(path), + kind = MediaRequestData.Kind.Thumbnail(targetSize), + ) + } else { + initialsAvatarBitmapGenerator.generateBitmap( + size = targetSize.toInt(), + avatarData = avatarData, + useDarkTheme = isDarkTheme(), + ) + } + val imageRequest = ImageRequest.Builder(context) + .data(data) + .transformations(CircleCropTransformation()) + .build() + return imageLoader.execute(imageRequest).image?.toBitmap() + } } 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 41b9f66f38..ee72c34b9e 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 @@ -1,22 +1,17 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.notifications -import androidx.annotation.VisibleForTesting -import androidx.core.app.NotificationManagerCompat import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn -import io.element.android.libraries.core.data.tryOrNull -import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.di.annotations.AppCoroutineScope -import io.element.android.libraries.matrix.api.MatrixClient 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 @@ -26,17 +21,14 @@ import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder import io.element.android.libraries.push.api.notifications.NotificationCleaner import io.element.android.libraries.push.api.notifications.NotificationIdProvider +import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent import io.element.android.libraries.push.impl.notifications.model.shouldIgnoreEventInRoom 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.Job import kotlinx.coroutines.launch -import timber.log.Timber - -private val loggerTag = LoggerTag("DefaultNotificationDrawerManager", LoggerTag.NotificationLoggerTag) /** * This class receives notification events as they arrive from the PushHandler calling [onNotifiableEventReceived] and @@ -45,9 +37,8 @@ private val loggerTag = LoggerTag("DefaultNotificationDrawerManager", LoggerTag. */ @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DefaultNotificationDrawerManager( - private val notificationManager: NotificationManagerCompat, + private val notificationDisplayer: NotificationDisplayer, private val notificationRenderer: NotificationRenderer, private val appNavigationStateService: AppNavigationStateService, @AppCoroutineScope @@ -56,25 +47,17 @@ class DefaultNotificationDrawerManager( private val imageLoaderHolder: ImageLoaderHolder, private val activeNotificationsProvider: ActiveNotificationsProvider, ) : NotificationCleaner { - private var appNavigationStateObserver: Job? = null - // TODO EAx add a setting per user for this private var useCompleteNotificationFormat = true init { // Observe application state - appNavigationStateObserver = coroutineScope.launch { + coroutineScope.launch { appNavigationStateService.appNavigationState .collect { onAppNavigationStateChange(it.navigationState) } } } - // For test only - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - internal fun destroy() { - appNavigationStateObserver?.cancel() - } - private var currentAppNavigationState: NavigationState? = null private fun onAppNavigationStateChange(navigationState: NavigationState) { @@ -95,10 +78,10 @@ class DefaultNotificationDrawerManager( ) } is NavigationState.Thread -> { - onEnteringThread( - navigationState.parentRoom.parentSpace.parentSession.sessionId, - navigationState.parentRoom.roomId, - navigationState.threadId + clearMessagesForThread( + sessionId = navigationState.parentRoom.parentSpace.parentSession.sessionId, + roomId = navigationState.parentRoom.roomId, + threadId = navigationState.threadId, ) } } @@ -125,7 +108,7 @@ class DefaultNotificationDrawerManager( * Clear all known message events for a [sessionId]. */ override fun clearAllMessagesEvents(sessionId: SessionId) { - notificationManager.cancel(null, NotificationIdProvider.getRoomMessagesNotificationId(sessionId)) + notificationDisplayer.cancelNotification(null, NotificationIdProvider.getRoomMessagesNotificationId(sessionId)) clearSummaryNotificationIfNeeded(sessionId) } @@ -134,7 +117,7 @@ class DefaultNotificationDrawerManager( */ fun clearAllEvents(sessionId: SessionId) { activeNotificationsProvider.getNotificationsForSession(sessionId) - .forEach { notificationManager.cancel(it.tag, it.id) } + .forEach { notificationDisplayer.cancelNotification(it.tag, it.id) } } /** @@ -143,13 +126,23 @@ class DefaultNotificationDrawerManager( * Can also be called when a notification for this room is dismissed by the user. */ override fun clearMessagesForRoom(sessionId: SessionId, roomId: RoomId) { - notificationManager.cancel(roomId.value, NotificationIdProvider.getRoomMessagesNotificationId(sessionId)) + notificationDisplayer.cancelNotification(roomId.value, NotificationIdProvider.getRoomMessagesNotificationId(sessionId)) + clearSummaryNotificationIfNeeded(sessionId) + } + + /** + * Should be called when the application is currently opened and showing timeline for the given threadId. + * Used to ignore events related to that thread (no need to display notification) and clean any existing notification on this room. + */ + override fun clearMessagesForThread(sessionId: SessionId, roomId: RoomId, threadId: ThreadId) { + val tag = NotificationCreator.messageTag(roomId, threadId) + notificationDisplayer.cancelNotification(tag, NotificationIdProvider.getRoomMessagesNotificationId(sessionId)) clearSummaryNotificationIfNeeded(sessionId) } override fun clearMembershipNotificationForSession(sessionId: SessionId) { activeNotificationsProvider.getMembershipNotificationForSession(sessionId) - .forEach { notificationManager.cancel(it.tag, it.id) } + .forEach { notificationDisplayer.cancelNotification(it.tag, it.id) } clearSummaryNotificationIfNeeded(sessionId) } @@ -158,7 +151,7 @@ class DefaultNotificationDrawerManager( */ override fun clearMembershipNotificationForRoom(sessionId: SessionId, roomId: RoomId) { activeNotificationsProvider.getMembershipNotificationForRoom(sessionId, roomId) - .forEach { notificationManager.cancel(it.tag, it.id) } + .forEach { notificationDisplayer.cancelNotification(it.tag, it.id) } clearSummaryNotificationIfNeeded(sessionId) } @@ -167,27 +160,17 @@ class DefaultNotificationDrawerManager( */ override fun clearEvent(sessionId: SessionId, eventId: EventId) { val id = NotificationIdProvider.getRoomEventNotificationId(sessionId) - notificationManager.cancel(eventId.value, id) + notificationDisplayer.cancelNotification(eventId.value, id) clearSummaryNotificationIfNeeded(sessionId) } private fun clearSummaryNotificationIfNeeded(sessionId: SessionId) { val summaryNotification = activeNotificationsProvider.getSummaryNotification(sessionId) if (summaryNotification != null && activeNotificationsProvider.count(sessionId) == 1) { - notificationManager.cancel(null, summaryNotification.id) + notificationDisplayer.cancelNotification(null, summaryNotification.id) } } - /** - * Should be called when the application is currently opened and showing timeline for the given threadId. - * Used to ignore events related to that thread (no need to display notification) and clean any existing notification on this room. - */ - @Suppress("UNUSED_PARAMETER") - private fun onEnteringThread(sessionId: SessionId, roomId: RoomId, threadId: ThreadId) { - // TODO maybe we'll have to embed more data in the tag to get a threadId - // Do nothing for now - } - private suspend fun renderEvents(eventsToRender: List) { // Group by sessionId val eventsForSessions = eventsToRender.groupBy { @@ -202,29 +185,9 @@ class DefaultNotificationDrawerManager( // We have an avatar and a display name, use it userFromCache } else { - client.getSafeUserProfile() + client.getUserProfile().getOrNull() ?: MatrixUser(sessionId) } - notificationRenderer.render(currentUser, useCompleteNotificationFormat, notifiableEvents, imageLoader) } } - - private suspend fun MatrixClient.getSafeUserProfile(): MatrixUser { - return tryOrNull( - onException = { Timber.tag(loggerTag.value).e(it, "Unable to retrieve info for user ${sessionId.value}") }, - operation = { - val profile = getUserProfile().getOrNull() - // displayName cannot be empty else NotificationCompat.MessagingStyle() will crash - if (profile?.displayName.isNullOrEmpty()) { - profile?.copy(displayName = sessionId.value) - } else { - profile - } - } - ) ?: MatrixUser( - userId = sessionId, - displayName = sessionId.value, - avatarUrl = null - ) - } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandler.kt index 084ad8d832..413425deeb 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.push.impl.notifications import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject 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 @@ -17,7 +17,6 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.push.api.notifications.OnMissedCallNotificationHandler @ContributesBinding(AppScope::class) -@Inject class DefaultOnMissedCallNotificationHandler( private val matrixClientProvider: MatrixClientProvider, private val defaultNotificationDrawerManager: DefaultNotificationDrawerManager, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FallbackNotificationFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FallbackNotificationFactory.kt index 57bd9bcf49..7b06aae39f 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FallbackNotificationFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FallbackNotificationFactory.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationAction.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationAction.kt index cac583a695..6b2eeaa1f4 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationAction.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationAction.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationActionIds.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationActionIds.kt index bd85e71b89..a0306005d9 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationActionIds.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationActionIds.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 92029f84e4..7ead2f322b 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverBindings.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverBindings.kt index 0e5588b7b4..79dc61668d 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverBindings.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverBindings.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt index c3df89b4a9..eb08a25c6a 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,9 +17,11 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.ThreadId +import io.element.android.libraries.matrix.api.room.CreateTimelineParams import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.timeline.ReceiptType +import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.preferences.api.store.SessionPreferencesStoreFactory import io.element.android.libraries.push.api.notifications.NotificationCleaner import io.element.android.libraries.push.impl.R @@ -72,8 +75,12 @@ class NotificationBroadcastReceiverHandler( notificationCleaner.clearEvent(sessionId, eventId) } actionIds.markRoomRead -> if (roomId != null) { - notificationCleaner.clearMessagesForRoom(sessionId, roomId) - handleMarkAsRead(sessionId, roomId) + if (threadId == null) { + notificationCleaner.clearMessagesForRoom(sessionId, roomId) + } else { + notificationCleaner.clearMessagesForThread(sessionId, roomId, threadId) + } + handleMarkAsRead(sessionId, roomId, threadId) } actionIds.join -> if (roomId != null) { notificationCleaner.clearMembershipNotificationForRoom(sessionId, roomId) @@ -96,7 +103,8 @@ class NotificationBroadcastReceiverHandler( client.getRoom(roomId)?.leave() } - private fun handleMarkAsRead(sessionId: SessionId, roomId: RoomId) = appCoroutineScope.launch { + @Suppress("unused") + private fun handleMarkAsRead(sessionId: SessionId, roomId: RoomId, threadId: ThreadId?) = appCoroutineScope.launch { val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return@launch val isSendPublicReadReceiptsEnabled = sessionPreferencesStore.get(sessionId, this).isSendPublicReadReceiptsEnabled().first() val receiptType = if (isSendPublicReadReceiptsEnabled) { @@ -104,7 +112,26 @@ class NotificationBroadcastReceiverHandler( } else { ReceiptType.READ_PRIVATE } - client.getRoom(roomId)?.markAsRead(receiptType = receiptType) + val room = client.getJoinedRoom(roomId) ?: return@launch + val timeline = if (threadId != null) { + room.createTimeline(CreateTimelineParams.Threaded(threadId)).getOrNull() + } else { + room.liveTimeline + } + timeline?.markAsRead(receiptType) + ?.onSuccess { + if (threadId != null) { + Timber.d("Marked thread $threadId in room $roomId as read with receipt type $receiptType") + } else { + Timber.d("Marked room $roomId as read with receipt type $receiptType") + } + } + ?.onFailure { + Timber.e(it, "Fails to mark as read with receipt type $receiptType") + } + if (timeline?.mode != Timeline.Mode.Live) { + timeline?.close() + } } private fun handleSmartReply( diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt index d5a35b046f..5489fa0894 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2021-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2021-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,11 +16,11 @@ import androidx.core.text.inSpans import coil3.ImageLoader import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId -import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.push.impl.R +import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent @@ -30,31 +31,41 @@ import io.element.android.services.toolbox.api.strings.StringProvider interface NotificationDataFactory { suspend fun toNotifications( messages: List, - currentUser: MatrixUser, imageLoader: ImageLoader, + notificationAccountParams: NotificationAccountParams, ): List @JvmName("toNotificationInvites") @Suppress("INAPPLICABLE_JVM_NAME") - fun toNotifications(invites: List): List + fun toNotifications( + invites: List, + notificationAccountParams: NotificationAccountParams, + ): List + @JvmName("toNotificationSimpleEvents") @Suppress("INAPPLICABLE_JVM_NAME") - fun toNotifications(simpleEvents: List): List + fun toNotifications( + simpleEvents: List, + notificationAccountParams: NotificationAccountParams, + ): List + @JvmName("toNotificationFallbackEvents") @Suppress("INAPPLICABLE_JVM_NAME") - fun toNotifications(fallback: List): List + fun toNotifications( + fallback: List, + notificationAccountParams: NotificationAccountParams, + ): List fun createSummaryNotification( - currentUser: MatrixUser, roomNotifications: List, invitationNotifications: List, simpleNotifications: List, fallbackNotifications: List, + notificationAccountParams: NotificationAccountParams, ): SummaryNotification } @ContributesBinding(AppScope::class) -@Inject class DefaultNotificationDataFactory( private val notificationCreator: NotificationCreator, private val roomGroupMessageCreator: RoomGroupMessageCreator, @@ -64,45 +75,54 @@ class DefaultNotificationDataFactory( ) : NotificationDataFactory { override suspend fun toNotifications( messages: List, - currentUser: MatrixUser, imageLoader: ImageLoader, + notificationAccountParams: NotificationAccountParams, ): List { val messagesToDisplay = messages.filterNot { it.canNotBeDisplayed() } .groupBy { it.roomId } - return messagesToDisplay.map { (roomId, events) -> + return messagesToDisplay.flatMap { (roomId, events) -> val roomName = events.lastOrNull()?.roomName ?: roomId.value val isDm = events.lastOrNull()?.roomIsDm ?: false - val notification = roomGroupMessageCreator.createRoomMessage( - currentUser = currentUser, - events = events, - roomId = roomId, - imageLoader = imageLoader, - existingNotification = getExistingNotificationForMessages(currentUser.userId, roomId), - ) - RoomNotification( - notification = notification, - roomId = roomId, - summaryLine = createRoomMessagesGroupSummaryLine(events, roomName, isDm), - messageCount = events.size, - latestTimestamp = events.maxOf { it.timestamp }, - shouldBing = events.any { it.noisy } - ) + val eventsByThreadId = events.groupBy { it.threadId } + + eventsByThreadId.map { (threadId, events) -> + val notification = roomGroupMessageCreator.createRoomMessage( + events = events, + roomId = roomId, + threadId = threadId, + imageLoader = imageLoader, + existingNotification = getExistingNotificationForMessages(notificationAccountParams.user.userId, roomId, threadId), + notificationAccountParams = notificationAccountParams, + ) + RoomNotification( + notification = notification, + roomId = roomId, + threadId = threadId, + summaryLine = createRoomMessagesGroupSummaryLine(events, roomName, isDm), + messageCount = events.size, + latestTimestamp = events.maxOf { it.timestamp }, + shouldBing = events.any { it.noisy } + ) + } } } private fun NotifiableMessageEvent.canNotBeDisplayed() = isRedacted - private fun getExistingNotificationForMessages(sessionId: SessionId, roomId: RoomId): Notification? { - return activeNotificationsProvider.getMessageNotificationsForRoom(sessionId, roomId).firstOrNull()?.notification + private fun getExistingNotificationForMessages(sessionId: SessionId, roomId: RoomId, threadId: ThreadId?): Notification? { + return activeNotificationsProvider.getMessageNotificationsForRoom(sessionId, roomId, threadId).firstOrNull()?.notification } @JvmName("toNotificationInvites") @Suppress("INAPPLICABLE_JVM_NAME") - override fun toNotifications(invites: List): List { + override fun toNotifications( + invites: List, + notificationAccountParams: NotificationAccountParams, + ): List { return invites.map { event -> OneShotNotification( - key = event.roomId.value, - notification = notificationCreator.createRoomInvitationNotification(event), + tag = event.roomId.value, + notification = notificationCreator.createRoomInvitationNotification(notificationAccountParams, event), summaryLine = event.description, isNoisy = event.noisy, timestamp = event.timestamp @@ -112,11 +132,14 @@ class DefaultNotificationDataFactory( @JvmName("toNotificationSimpleEvents") @Suppress("INAPPLICABLE_JVM_NAME") - override fun toNotifications(simpleEvents: List): List { + override fun toNotifications( + simpleEvents: List, + notificationAccountParams: NotificationAccountParams, + ): List { return simpleEvents.map { event -> OneShotNotification( - key = event.eventId.value, - notification = notificationCreator.createSimpleEventNotification(event), + tag = event.eventId.value, + notification = notificationCreator.createSimpleEventNotification(notificationAccountParams, event), summaryLine = event.description, isNoisy = event.noisy, timestamp = event.timestamp @@ -126,11 +149,14 @@ class DefaultNotificationDataFactory( @JvmName("toNotificationFallbackEvents") @Suppress("INAPPLICABLE_JVM_NAME") - override fun toNotifications(fallback: List): List { + override fun toNotifications( + fallback: List, + notificationAccountParams: NotificationAccountParams, + ): List { return fallback.map { event -> OneShotNotification( - key = event.eventId.value, - notification = notificationCreator.createFallbackNotification(event), + tag = event.eventId.value, + notification = notificationCreator.createFallbackNotification(notificationAccountParams, event), summaryLine = event.description.orEmpty(), isNoisy = false, timestamp = event.timestamp @@ -139,21 +165,21 @@ class DefaultNotificationDataFactory( } override fun createSummaryNotification( - currentUser: MatrixUser, roomNotifications: List, invitationNotifications: List, simpleNotifications: List, fallbackNotifications: List, + notificationAccountParams: NotificationAccountParams, ): SummaryNotification { return when { roomNotifications.isEmpty() && invitationNotifications.isEmpty() && simpleNotifications.isEmpty() -> SummaryNotification.Removed else -> SummaryNotification.Update( summaryGroupMessageCreator.createSummaryNotification( - currentUser = currentUser, roomNotifications = roomNotifications, invitationNotifications = invitationNotifications, simpleNotifications = simpleNotifications, fallbackNotifications = fallbackNotifications, + notificationAccountParams = notificationAccountParams, ) ) } @@ -203,6 +229,7 @@ class DefaultNotificationDataFactory( data class RoomNotification( val notification: Notification, val roomId: RoomId, + val threadId: ThreadId?, val summaryLine: CharSequence, val messageCount: Int, val latestTimestamp: Long, @@ -211,6 +238,7 @@ data class RoomNotification( fun isDataEqualTo(other: RoomNotification): Boolean { return notification == other.notification && roomId == other.roomId && + threadId == other.threadId && summaryLine.toString() == other.summaryLine.toString() && messageCount == other.messageCount && latestTimestamp == other.latestTimestamp && @@ -220,7 +248,7 @@ data class RoomNotification( data class OneShotNotification( val notification: Notification, - val key: String, + val tag: String, val summaryLine: CharSequence, val isNoisy: Boolean, val timestamp: Long, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDisplayer.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDisplayer.kt index 34f141c09f..d46bffce73 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDisplayer.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDisplayer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2021-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2021-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,24 +16,23 @@ import androidx.core.app.ActivityCompat import androidx.core.app.NotificationManagerCompat import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.annotations.ApplicationContext import timber.log.Timber interface NotificationDisplayer { - fun showNotificationMessage(tag: String?, id: Int, notification: Notification): Boolean - fun cancelNotificationMessage(tag: String?, id: Int) + fun showNotification(tag: String?, id: Int, notification: Notification): Boolean + fun cancelNotification(tag: String?, id: Int) fun displayDiagnosticNotification(notification: Notification): Boolean fun dismissDiagnosticNotification() + fun displayUnregistrationNotification(notification: Notification): Boolean } @ContributesBinding(AppScope::class) -@Inject class DefaultNotificationDisplayer( @ApplicationContext private val context: Context, private val notificationManager: NotificationManagerCompat ) : NotificationDisplayer { - override fun showNotificationMessage(tag: String?, id: Int, notification: Notification): Boolean { + override fun showNotification(tag: String?, id: Int, notification: Notification): Boolean { if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { Timber.w("Not allowed to notify.") return false @@ -42,29 +42,40 @@ class DefaultNotificationDisplayer( return true } - override fun cancelNotificationMessage(tag: String?, id: Int) { + override fun cancelNotification(tag: String?, id: Int) { notificationManager.cancel(tag, id) } override fun displayDiagnosticNotification(notification: Notification): Boolean { - return showNotificationMessage( - tag = "DIAGNOSTIC", + return showNotification( + tag = TAG_DIAGNOSTIC, id = NOTIFICATION_ID_DIAGNOSTIC, notification = notification ) } override fun dismissDiagnosticNotification() { - cancelNotificationMessage( - tag = "DIAGNOSTIC", + cancelNotification( + tag = TAG_DIAGNOSTIC, id = NOTIFICATION_ID_DIAGNOSTIC ) } + override fun displayUnregistrationNotification(notification: Notification): Boolean { + return showNotification( + tag = TAG_DIAGNOSTIC, + id = NOTIFICATION_ID_UNREGISTRATION, + notification = notification, + ) + } + companion object { + private const val TAG_DIAGNOSTIC = "DIAGNOSTIC" + /* ========================================================================================== * IDs for notifications * ========================================================================================== */ private const val NOTIFICATION_ID_DIAGNOSTIC = 888 + private const val NOTIFICATION_ID_UNREGISTRATION = 889 } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationMediaRepo.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationMediaRepo.kt index 2bbc7208be..f309f754c3 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationMediaRepo.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationMediaRepo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt index 5d901abbc9..26769f09ba 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRenderer.kt @@ -1,23 +1,31 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.notifications +import androidx.compose.ui.graphics.toArgb import coil3.ImageLoader import dev.zacsweers.metro.Inject +import io.element.android.appconfig.NotificationConfig +import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.push.api.notifications.NotificationIdProvider +import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams +import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent +import io.element.android.libraries.sessionstorage.api.SessionStore +import kotlinx.coroutines.flow.first import timber.log.Timber private val loggerTag = LoggerTag("NotificationRenderer", LoggerTag.NotificationLoggerTag) @@ -26,6 +34,8 @@ private val loggerTag = LoggerTag("NotificationRenderer", LoggerTag.Notification class NotificationRenderer( private val notificationDisplayer: NotificationDisplayer, private val notificationDataFactory: NotificationDataFactory, + private val enterpriseService: EnterpriseService, + private val sessionStore: SessionStore, ) { suspend fun render( currentUser: MatrixUser, @@ -33,31 +43,43 @@ class NotificationRenderer( eventsToProcess: List, imageLoader: ImageLoader, ) { + val color = enterpriseService.brandColorsFlow(currentUser.userId).first()?.toArgb() + ?: NotificationConfig.NOTIFICATION_ACCENT_COLOR + val numberOfAccounts = sessionStore.numberOfSessions() + val notificationAccountParams = NotificationAccountParams( + user = currentUser, + color = color, + showSessionId = numberOfAccounts > 1, + ) val groupedEvents = eventsToProcess.groupByType() - val roomNotifications = notificationDataFactory.toNotifications(groupedEvents.roomEvents, currentUser, imageLoader) - val invitationNotifications = notificationDataFactory.toNotifications(groupedEvents.invitationEvents) - val simpleNotifications = notificationDataFactory.toNotifications(groupedEvents.simpleEvents) - val fallbackNotifications = notificationDataFactory.toNotifications(groupedEvents.fallbackEvents) + val roomNotifications = notificationDataFactory.toNotifications(groupedEvents.roomEvents, imageLoader, notificationAccountParams) + val invitationNotifications = notificationDataFactory.toNotifications(groupedEvents.invitationEvents, notificationAccountParams) + val simpleNotifications = notificationDataFactory.toNotifications(groupedEvents.simpleEvents, notificationAccountParams) + val fallbackNotifications = notificationDataFactory.toNotifications(groupedEvents.fallbackEvents, notificationAccountParams) val summaryNotification = notificationDataFactory.createSummaryNotification( - currentUser = currentUser, roomNotifications = roomNotifications, invitationNotifications = invitationNotifications, simpleNotifications = simpleNotifications, fallbackNotifications = fallbackNotifications, + notificationAccountParams = notificationAccountParams, ) // Remove summary first to avoid briefly displaying it after dismissing the last notification if (summaryNotification == SummaryNotification.Removed) { Timber.tag(loggerTag.value).d("Removing summary notification") - notificationDisplayer.cancelNotificationMessage( + notificationDisplayer.cancelNotification( tag = null, id = NotificationIdProvider.getSummaryNotificationId(currentUser.userId) ) } roomNotifications.forEach { notificationData -> - notificationDisplayer.showNotificationMessage( - tag = notificationData.roomId.value, + val tag = NotificationCreator.messageTag( + roomId = notificationData.roomId, + threadId = notificationData.threadId + ) + notificationDisplayer.showNotification( + tag = tag, id = NotificationIdProvider.getRoomMessagesNotificationId(currentUser.userId), notification = notificationData.notification ) @@ -65,9 +87,9 @@ class NotificationRenderer( invitationNotifications.forEach { notificationData -> if (useCompleteNotificationFormat) { - Timber.tag(loggerTag.value).d("Updating invitation notification ${notificationData.key}") - notificationDisplayer.showNotificationMessage( - tag = notificationData.key, + Timber.tag(loggerTag.value).d("Updating invitation notification ${notificationData.tag}") + notificationDisplayer.showNotification( + tag = notificationData.tag, id = NotificationIdProvider.getRoomInvitationNotificationId(currentUser.userId), notification = notificationData.notification ) @@ -76,9 +98,9 @@ class NotificationRenderer( simpleNotifications.forEach { notificationData -> if (useCompleteNotificationFormat) { - Timber.tag(loggerTag.value).d("Updating simple notification ${notificationData.key}") - notificationDisplayer.showNotificationMessage( - tag = notificationData.key, + Timber.tag(loggerTag.value).d("Updating simple notification ${notificationData.tag}") + notificationDisplayer.showNotification( + tag = notificationData.tag, id = NotificationIdProvider.getRoomEventNotificationId(currentUser.userId), notification = notificationData.notification ) @@ -88,7 +110,7 @@ class NotificationRenderer( // Show only the first fallback notification if (fallbackNotifications.isNotEmpty()) { Timber.tag(loggerTag.value).d("Showing fallback notification") - notificationDisplayer.showNotificationMessage( + notificationDisplayer.showNotification( tag = "FALLBACK", id = NotificationIdProvider.getFallbackNotificationId(currentUser.userId), notification = fallbackNotifications.first().notification @@ -98,7 +120,7 @@ class NotificationRenderer( // Update summary last to avoid briefly displaying it before other notifications if (summaryNotification is SummaryNotification.Update) { Timber.tag(loggerTag.value).d("Updating summary notification") - notificationDisplayer.showNotificationMessage( + notificationDisplayer.showNotification( tag = null, id = NotificationIdProvider.getSummaryNotificationId(currentUser.userId), notification = summaryNotification.notification diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationResolverQueue.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationResolverQueue.kt index f2031bdda6..b40b3fe79f 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationResolverQueue.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationResolverQueue.kt @@ -1,20 +1,25 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.notifications import dev.zacsweers.metro.AppScope -import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.SingleIn import io.element.android.libraries.di.annotations.AppCoroutineScope -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.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.push.api.push.NotificationEventRequest import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent +import io.element.android.libraries.push.impl.workmanager.SyncNotificationWorkManagerRequest +import io.element.android.libraries.push.impl.workmanager.SyncNotificationsWorkerDataConverter +import io.element.android.libraries.workmanager.api.WorkManagerScheduler +import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job @@ -27,21 +32,31 @@ import kotlinx.coroutines.launch import timber.log.Timber import kotlin.time.Duration.Companion.milliseconds +interface NotificationResolverQueue { + val results: SharedFlow, Map>>> + suspend fun enqueue(request: NotificationEventRequest) +} + /** * This class is responsible for periodically batching notification requests and resolving them in a single call, * so that we can avoid having to resolve each notification individually in the SDK. */ @OptIn(ExperimentalCoroutinesApi::class) @SingleIn(AppScope::class) -@Inject -class NotificationResolverQueue( +@ContributesBinding(AppScope::class) +class DefaultNotificationResolverQueue( private val notifiableEventResolver: NotifiableEventResolver, @AppCoroutineScope private val appCoroutineScope: CoroutineScope, -) { + private val workManagerScheduler: WorkManagerScheduler, + private val featureFlagService: FeatureFlagService, + private val workerDataConverter: SyncNotificationsWorkerDataConverter, + private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider, +) : NotificationResolverQueue { companion object { private const val BATCH_WINDOW_MS = 250L } + private val requestQueue = Channel(capacity = 100) private var currentProcessingJob: Job? = null @@ -50,7 +65,7 @@ class NotificationResolverQueue( * A flow that emits pairs of a list of notification event requests and a map of the resolved events. * The map contains the original request as the key and the resolved event as the value. */ - val results: SharedFlow, Map>>> = MutableSharedFlow() + override val results = MutableSharedFlow, Map>>>() /** * Enqueues a notification event request to be resolved. @@ -58,7 +73,7 @@ class NotificationResolverQueue( * * @param request The notification event request to enqueue. */ - suspend fun enqueue(request: NotificationEventRequest) { + override suspend fun enqueue(request: NotificationEventRequest) { // Cancel previous processing job if it exists, acting as a debounce operation Timber.d("Cancelling job: $currentProcessingJob") currentProcessingJob?.cancel() @@ -77,28 +92,34 @@ class NotificationResolverQueue( appCoroutineScope.launch { val groupedRequestsById = buildList { while (!requestQueue.isEmpty) { - requestQueue.receiveCatching().getOrNull()?.let(this::add) + requestQueue.receiveCatching().getOrNull()?.let(::add) } }.groupBy { it.sessionId } - val sessionIds = groupedRequestsById.keys - for (sessionId in sessionIds) { - val requests = groupedRequestsById[sessionId].orEmpty() - Timber.d("Fetching notifications for $sessionId: $requests. Pending requests: ${!requestQueue.isEmpty}") - // Resolving the events in parallel should improve performance since each session id will query a different Client - launch { - // No need for a Mutex since the SDK already has one internally - val notifications = notifiableEventResolver.resolveEvents(sessionId, requests).getOrNull().orEmpty() - (results as MutableSharedFlow).emit(requests to notifications) + if (featureFlagService.isFeatureEnabled(FeatureFlags.SyncNotificationsWithWorkManager)) { + for ((sessionId, requests) in groupedRequestsById) { + workManagerScheduler.submit( + SyncNotificationWorkManagerRequest( + sessionId = sessionId, + notificationEventRequests = requests, + workerDataConverter = workerDataConverter, + buildVersionSdkIntProvider = buildVersionSdkIntProvider, + ) + ) + } + } else { + val sessionIds = groupedRequestsById.keys + for (sessionId in sessionIds) { + val requests = groupedRequestsById[sessionId].orEmpty() + Timber.d("Fetching notifications for $sessionId: $requests. Pending requests: ${!requestQueue.isEmpty}") + // Resolving the events in parallel should improve performance since each session id will query a different Client + launch { + // No need for a Mutex since the SDK already has one internally + val notifications = notifiableEventResolver.resolveEvents(sessionId, requests).getOrNull().orEmpty() + results.emit(requests to notifications) + } } } } } } - -data class NotificationEventRequest( - val sessionId: SessionId, - val roomId: RoomId, - val eventId: EventId, - val providerInfo: String, -) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationsFileProvider.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationsFileProvider.kt index 65da83d148..5a2bd361fe 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationsFileProvider.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationsFileProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/ReplyMessageExtractor.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/ReplyMessageExtractor.kt index 323c32f64a..360f3762e1 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/ReplyMessageExtractor.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/ReplyMessageExtractor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,14 +12,12 @@ import android.content.Intent import androidx.core.app.RemoteInput import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject interface ReplyMessageExtractor { fun getReplyMessage(intent: Intent): String? } @ContributesBinding(AppScope::class) -@Inject class AndroidReplyMessageExtractor : ReplyMessageExtractor { override fun getReplyMessage(intent: Intent): String? { return RemoteInput.getResultsFromIntent(intent) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomEventGroupInfo.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomEventGroupInfo.kt index 705c52dc0a..87234202a5 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomEventGroupInfo.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/RoomEventGroupInfo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 0ab6ff27b2..8683b58493 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 @@ -1,7 +1,8 @@ /* - * Copyright 2021-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2021-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,11 +13,13 @@ import android.graphics.Bitmap import coil3.ImageLoader import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader import io.element.android.libraries.push.impl.R +import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator import io.element.android.libraries.push.impl.notifications.factories.isSmartReplyError import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent @@ -24,25 +27,26 @@ import io.element.android.services.toolbox.api.strings.StringProvider interface RoomGroupMessageCreator { suspend fun createRoomMessage( - currentUser: MatrixUser, + notificationAccountParams: NotificationAccountParams, events: List, roomId: RoomId, + threadId: ThreadId?, imageLoader: ImageLoader, existingNotification: Notification?, ): Notification } @ContributesBinding(AppScope::class) -@Inject class DefaultRoomGroupMessageCreator( private val bitmapLoader: NotificationBitmapLoader, private val stringProvider: StringProvider, private val notificationCreator: NotificationCreator, ) : RoomGroupMessageCreator { override suspend fun createRoomMessage( - currentUser: MatrixUser, + notificationAccountParams: NotificationAccountParams, events: List, roomId: RoomId, + threadId: ThreadId?, imageLoader: ImageLoader, existingNotification: Notification?, ): Notification { @@ -62,24 +66,24 @@ class DefaultRoomGroupMessageCreator( val smartReplyErrors = events.filter { it.isSmartReplyError() } val roomIsDm = !roomIsGroup return notificationCreator.createMessagesListNotification( - RoomEventGroupInfo( - sessionId = currentUser.userId, - roomId = roomId, - roomDisplayName = roomName, - isDm = roomIsDm, - hasSmartReplyError = smartReplyErrors.isNotEmpty(), - shouldBing = events.any { it.noisy }, - customSound = events.last().soundName, - isUpdated = events.last().isUpdated, - ), - threadId = lastKnownRoomEvent.threadId, - largeIcon = largeBitmap, - lastMessageTimestamp = lastMessageTimestamp, - tickerText = tickerText, - currentUser = currentUser, - existingNotification = existingNotification, - imageLoader = imageLoader, - events = events, + notificationAccountParams = notificationAccountParams, + RoomEventGroupInfo( + sessionId = notificationAccountParams.user.userId, + roomId = roomId, + roomDisplayName = roomName, + isDm = roomIsDm, + hasSmartReplyError = smartReplyErrors.isNotEmpty(), + shouldBing = events.any { it.noisy }, + customSound = events.last().soundName, + isUpdated = events.last().isUpdated, + ), + threadId = threadId, + largeIcon = largeBitmap, + lastMessageTimestamp = lastMessageTimestamp, + tickerText = tickerText, + existingNotification = existingNotification, + imageLoader = imageLoader, + events = events, ) } @@ -88,7 +92,18 @@ class DefaultRoomGroupMessageCreator( imageLoader: ImageLoader, ): Bitmap? { // Use the last event (most recent?) - return events.reversed().firstNotNullOfOrNull { it.roomAvatarPath } - ?.let { bitmapLoader.getRoomBitmap(it, imageLoader) } + val event = events.reversed().firstOrNull { it.roomAvatarPath != null } + ?: events.reversed().firstOrNull() + return event?.let { event -> + bitmapLoader.getRoomBitmap( + avatarData = AvatarData( + id = event.roomId.value, + name = event.roomName, + url = event.roomAvatarPath, + size = AvatarSize.RoomDetailsHeader, + ), + imageLoader = imageLoader, + ) + } } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt index 5c31f03d5d..f7f0c057c6 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/SummaryGroupMessageCreator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2021-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2021-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,15 +11,14 @@ package io.element.android.libraries.push.impl.notifications import android.app.Notification import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject -import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.push.impl.R +import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator import io.element.android.services.toolbox.api.strings.StringProvider interface SummaryGroupMessageCreator { fun createSummaryNotification( - currentUser: MatrixUser, + notificationAccountParams: NotificationAccountParams, roomNotifications: List, invitationNotifications: List, simpleNotifications: List, @@ -36,13 +36,12 @@ interface SummaryGroupMessageCreator { * https://developer.android.com/training/notify-user/group */ @ContributesBinding(AppScope::class) -@Inject class DefaultSummaryGroupMessageCreator( private val stringProvider: StringProvider, private val notificationCreator: NotificationCreator, ) : SummaryGroupMessageCreator { override fun createSummaryNotification( - currentUser: MatrixUser, + notificationAccountParams: NotificationAccountParams, roomNotifications: List, invitationNotifications: List, simpleNotifications: List, @@ -51,19 +50,16 @@ class DefaultSummaryGroupMessageCreator( val summaryIsNoisy = roomNotifications.any { it.shouldBing } || invitationNotifications.any { it.isNoisy } || simpleNotifications.any { it.isNoisy } - val lastMessageTimestamp = roomNotifications.lastOrNull()?.latestTimestamp ?: invitationNotifications.lastOrNull()?.timestamp ?: simpleNotifications.last().timestamp - - // FIXME roomIdToEventMap.size is not correct, this is the number of rooms - val nbEvents = roomNotifications.size + simpleNotifications.size + val nbEvents = roomNotifications.size + invitationNotifications.size + simpleNotifications.size val sumTitle = stringProvider.getQuantityString(R.plurals.notification_compat_summary_title, nbEvents, nbEvents) return notificationCreator.createSummaryListNotification( - currentUser, + notificationAccountParams = notificationAccountParams, sumTitle, noisy = summaryIsNoisy, - lastMessageTimestamp = lastMessageTimestamp + lastMessageTimestamp = lastMessageTimestamp, ) } } 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 442562fe2f..5312dc640f 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiverBinding.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiverBinding.kt index c4020c575c..331f4f4c3e 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiverBinding.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiverBinding.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 f5f0ce5cca..9d1452fdae 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -20,7 +21,6 @@ import androidx.core.app.NotificationChannelCompat import androidx.core.app.NotificationManagerCompat import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.appconfig.NotificationConfig import io.element.android.libraries.di.annotations.ApplicationContext @@ -62,7 +62,6 @@ private fun supportNotificationChannels() = Build.VERSION.SDK_INT >= Build.VERSI @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DefaultNotificationChannels( private val notificationManager: NotificationManagerCompat, private val stringProvider: StringProvider, @@ -73,10 +72,6 @@ class DefaultNotificationChannels( createNotificationChannels() } - /* ========================================================================================== - * Channel names - * ========================================================================================== */ - /** * Create notification channels. */ @@ -111,10 +106,7 @@ class DefaultNotificationChannels( } } - /** - * Default notification importance: shows everywhere, makes noise, but does not visually - * intrude. - */ + // Default notification importance: shows everywhere, makes noise, but does not visually intrude. notificationManager.createNotificationChannel( NotificationChannelCompat.Builder( NOISY_NOTIFICATION_CHANNEL_ID, @@ -139,9 +131,7 @@ class DefaultNotificationChannels( .build() ) - /** - * Low notification importance: shows everywhere, but is not intrusive. - */ + // Low notification importance: shows everywhere, but is not intrusive. notificationManager.createNotificationChannel( NotificationChannelCompat.Builder( SILENT_NOTIFICATION_CHANNEL_ID, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationService.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationService.kt index bda5a593d5..ce20234385 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationService.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,14 +10,12 @@ package io.element.android.libraries.push.impl.notifications.conversations import android.content.Context import android.content.pm.ShortcutInfo -import android.content.res.Configuration import android.os.Build import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.graphics.drawable.IconCompat import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.features.lockscreen.api.LockScreenService import io.element.android.libraries.core.coroutine.withPreviousValue @@ -29,7 +28,6 @@ import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder -import io.element.android.libraries.matrix.ui.media.InitialsAvatarBitmapGenerator import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader import io.element.android.libraries.push.api.notifications.conversations.NotificationConversationService import io.element.android.libraries.push.impl.intent.IntentProvider @@ -46,7 +44,6 @@ import timber.log.Timber @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DefaultNotificationConversationService( @ApplicationContext private val context: Context, private val intentProvider: IntentProvider, @@ -61,9 +58,7 @@ class DefaultNotificationConversationService( init { sessionObserver.addListener(object : SessionListener { - override suspend fun onSessionCreated(userId: String) = Unit - - override suspend fun onSessionDeleted(userId: String) { + override suspend fun onSessionDeleted(userId: String, wasLastSession: Boolean) { onSessionLogOut(SessionId(userId)) } }) @@ -98,20 +93,21 @@ class DefaultNotificationConversationService( val imageLoader = imageLoaderHolder.get(client) val defaultShortcutIconSize = ShortcutManagerCompat.getIconMaxWidth(context) - val useDarkTheme = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES val icon = bitmapLoader.getRoomBitmap( - path = roomAvatarUrl, + avatarData = AvatarData( + id = roomId.value, + name = roomName, + url = roomAvatarUrl, + size = AvatarSize.RoomDetailsHeader, + ), imageLoader = imageLoader, targetSize = defaultShortcutIconSize.toLong() )?.let(IconCompat::createWithBitmap) - ?: InitialsAvatarBitmapGenerator(useDarkTheme = useDarkTheme) - .generateBitmap(defaultShortcutIconSize, AvatarData(id = roomId.value, name = roomName, size = AvatarSize.RoomDetailsHeader)) - ?.let(IconCompat::createWithAdaptiveBitmap) val shortcutInfo = ShortcutInfoCompat.Builder(context, createShortcutId(sessionId, roomId)) .setShortLabel(roomName) .setIcon(icon) - .setIntent(intentProvider.getViewRoomIntent(sessionId, roomId, threadId = null)) + .setIntent(intentProvider.getViewRoomIntent(sessionId, roomId, threadId = null, eventId = null)) .setCategories(categories) .setLongLived(true) .let { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/debug/DebugNotification.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/debug/DebugNotification.kt index 1a2f00f11a..7afe1b99d8 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/debug/DebugNotification.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/debug/DebugNotification.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationAccountParams.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationAccountParams.kt new file mode 100644 index 0000000000..858f102afd --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationAccountParams.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.impl.notifications.factories + +import androidx.annotation.ColorInt +import io.element.android.libraries.matrix.api.user.MatrixUser + +data class NotificationAccountParams( + val user: MatrixUser, + @ColorInt val color: Int, + val showSessionId: Boolean, +) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt index 3f07e12691..9533f6b0ac 100755 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,24 +11,25 @@ package io.element.android.libraries.push.impl.notifications.factories import android.app.Notification import android.content.Context import android.graphics.Bitmap -import android.graphics.Canvas -import androidx.annotation.DrawableRes +import androidx.annotation.ColorInt import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.MessagingStyle import androidx.core.app.Person -import androidx.core.content.res.ResourcesCompat +import androidx.core.os.bundleOf import coil3.ImageLoader import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject -import io.element.android.appconfig.NotificationConfig import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.di.annotations.ApplicationContext -import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.timeline.item.event.EventType import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.ui.model.getAvatarData +import io.element.android.libraries.matrix.ui.model.getBestName import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.notifications.RoomEventGroupInfo @@ -42,6 +44,8 @@ import io.element.android.libraries.push.impl.notifications.model.InviteNotifiab import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent import io.element.android.libraries.push.impl.notifications.shortcut.createShortcutId +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.services.appnavstate.api.ROOM_OPENED_FROM_NOTIFICATION import io.element.android.services.toolbox.api.strings.StringProvider interface NotificationCreator { @@ -49,26 +53,29 @@ interface NotificationCreator { * Create a notification for a Room. */ suspend fun createMessagesListNotification( + notificationAccountParams: NotificationAccountParams, roomInfo: RoomEventGroupInfo, threadId: ThreadId?, largeIcon: Bitmap?, lastMessageTimestamp: Long, tickerText: String, - currentUser: MatrixUser, existingNotification: Notification?, imageLoader: ImageLoader, events: List, ): Notification fun createRoomInvitationNotification( - inviteNotifiableEvent: InviteNotifiableEvent + notificationAccountParams: NotificationAccountParams, + inviteNotifiableEvent: InviteNotifiableEvent, ): Notification fun createSimpleEventNotification( + notificationAccountParams: NotificationAccountParams, simpleNotifiableEvent: SimpleNotifiableEvent, ): Notification fun createFallbackNotification( + notificationAccountParams: NotificationAccountParams, fallbackNotifiableEvent: FallbackNotifiableEvent, ): Notification @@ -76,17 +83,33 @@ interface NotificationCreator { * Create the summary notification. */ fun createSummaryListNotification( - currentUser: MatrixUser, + notificationAccountParams: NotificationAccountParams, compatSummary: String, noisy: Boolean, - lastMessageTimestamp: Long + lastMessageTimestamp: Long, ): Notification - fun createDiagnosticNotification(): Notification + fun createDiagnosticNotification( + @ColorInt color: Int, + ): Notification + + fun createUnregistrationNotification( + notificationAccountParams: NotificationAccountParams, + ): Notification + + companion object { + /** + * Creates a tag for a message notification given its [roomId] and optional [threadId]. + */ + fun messageTag(roomId: RoomId, threadId: ThreadId?): String = if (threadId != null) { + "$roomId|$threadId" + } else { + roomId.value + } + } } @ContributesBinding(AppScope::class) -@Inject class DefaultNotificationCreator( @ApplicationContext private val context: Context, private val notificationChannels: NotificationChannels, @@ -97,199 +120,173 @@ class DefaultNotificationCreator( private val quickReplyActionFactory: QuickReplyActionFactory, private val bitmapLoader: NotificationBitmapLoader, private val acceptInvitationActionFactory: AcceptInvitationActionFactory, - private val rejectInvitationActionFactory: RejectInvitationActionFactory + private val rejectInvitationActionFactory: RejectInvitationActionFactory, ) : NotificationCreator { - private val accentColor = NotificationConfig.NOTIFICATION_ACCENT_COLOR - /** * Create a notification for a Room. */ override suspend fun createMessagesListNotification( + notificationAccountParams: NotificationAccountParams, roomInfo: RoomEventGroupInfo, threadId: ThreadId?, largeIcon: Bitmap?, lastMessageTimestamp: Long, tickerText: String, - currentUser: MatrixUser, existingNotification: Notification?, imageLoader: ImageLoader, events: List, ): Notification { // Build the pending intent for when the notification is clicked + val eventId = events.firstOrNull()?.eventId val openIntent = when { - threadId != null -> pendingIntentFactory.createOpenThreadPendingIntent(roomInfo, threadId) - else -> pendingIntentFactory.createOpenRoomPendingIntent(roomInfo.sessionId, roomInfo.roomId) + threadId != null -> pendingIntentFactory.createOpenThreadPendingIntent(roomInfo.sessionId, roomInfo.roomId, eventId, threadId) + else -> pendingIntentFactory.createOpenRoomPendingIntent( + sessionId = roomInfo.sessionId, + roomId = roomInfo.roomId, + eventId = eventId, + extras = bundleOf(ROOM_OPENED_FROM_NOTIFICATION to true), + ) } - - val smallIcon = CommonDrawables.ic_notification - val containsMissedCall = events.any { it.type == EventType.RTC_NOTIFICATION } val channelId = if (containsMissedCall) { notificationChannels.getChannelForIncomingCall(false) } else { notificationChannels.getChannelIdForMessage(noisy = roomInfo.shouldBing) } + // A category allows groups of notifications to be ranked and filtered – per user or system settings. + // For example, alarm notifications should display before promo notifications, or message from known contact + // that can be displayed in not disturb mode if white listed (the later will need compat28.x) + // If any of the events are of rtc notification type it means a missed call, set the category to the right value + val category = if (containsMissedCall) { + NotificationCompat.CATEGORY_MISSED_CALL + } else { + NotificationCompat.CATEGORY_MESSAGE + } val builder = if (existingNotification != null) { NotificationCompat.Builder(context, existingNotification) + // Clear existing actions + .clearActions() } else { NotificationCompat.Builder(context, channelId) - // A category allows groups of notifications to be ranked and filtered – per user or system settings. - // For example, alarm notifications should display before promo notifications, or message from known contact - // that can be displayed in not disturb mode if white listed (the later will need compat28.x) - .setCategory(NotificationCompat.CATEGORY_MESSAGE) // ID of the corresponding shortcut, for conversation features under API 30+ // Must match those created in the ShortcutInfoCompat.Builder() // for the notification to appear as a "Conversation": // https://developer.android.com/develop/ui/views/notifications/conversations - .setShortcutId(createShortcutId(roomInfo.sessionId, roomInfo.roomId)) - // Auto-bundling is enabled for 4 or more notifications on API 24+ (N+) - // devices and all Wear devices. But we want a custom grouping, so we specify the groupID - .setGroup(roomInfo.sessionId.value) + .apply { + if (threadId == null) { + setShortcutId(createShortcutId(roomInfo.sessionId, roomInfo.roomId)) + } + } + .setGroupSummary(false) // In order to avoid notification making sound twice (due to the summary notification) - .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) + .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN) // Remove notification after opening it or using an action .setAutoCancel(true) } - val messagingStyle = existingNotification?.let { MessagingStyle.extractMessagingStyleFromNotification(it) - } ?: messagingStyleFromCurrentUser(roomInfo.sessionId, currentUser, imageLoader, roomInfo.roomDisplayName, !roomInfo.isDm) - + } ?: createMessagingStyleFromCurrentUser( + user = notificationAccountParams.user, + imageLoader = imageLoader, + roomName = roomInfo.roomDisplayName, + isThread = threadId != null, + roomIsGroup = !roomInfo.isDm, + ) messagingStyle.addMessagesFromEvents(events, imageLoader) - return builder + .setCategory(category) .setNumber(events.size) .setOnlyAlertOnce(roomInfo.isUpdated) .setWhen(lastMessageTimestamp) // MESSAGING_STYLE sets title and content for API 16 and above devices. .setStyle(messagingStyle) - // Not needed anymore? - // Title for API < 16 devices. - .setContentTitle(roomInfo.roomDisplayName.annotateForDebug(1)) - // Content for API < 16 devices. - .setContentText(stringProvider.getString(R.string.notification_new_messages).annotateForDebug(2)) - // Number of new notifications for API <24 (M and below) devices. - .setSubText( - stringProvider.getQuantityString( - R.plurals.notification_new_messages_for_room, - messagingStyle.messages.size, - messagingStyle.messages.size - ).annotateForDebug(3) - ) - .setSmallIcon(smallIcon) - // Set primary color (important for Wear 2.0 Notifications). - .setColor(accentColor) - // Sets priority for 25 and below. For 26 and above, 'priority' is deprecated for - // 'importance' which is set in the NotificationChannel. The integers representing - // 'priority' are different from 'importance', so make sure you don't mix them. + .configureWith(notificationAccountParams) + // Mark room/thread as read + .addAction(markAsReadActionFactory.create(roomInfo, threadId)) + .setContentIntent(openIntent) + .setLargeIcon(largeIcon) + .setDeleteIntent(pendingIntentFactory.createDismissRoomPendingIntent(roomInfo.sessionId, roomInfo.roomId)) .apply { + // Sets priority for 25 and below. For 26 and above, 'priority' is deprecated for + // 'importance' which is set in the NotificationChannel. The integers representing + // 'priority' are different from 'importance', so make sure you don't mix them. if (roomInfo.shouldBing) { - // Compat priority = NotificationCompat.PRIORITY_DEFAULT - /* - vectorPreferences.getNotificationRingTone()?.let { - setSound(it) - } - */ - setLights(accentColor, 500, 500) + setLights(notificationAccountParams.color, 500, 500) } else { priority = NotificationCompat.PRIORITY_LOW } - // Clear existing actions since we might be updating an existing notification - clearActions() - // Add actions and notification intents - // Mark room as read - addAction(markAsReadActionFactory.create(roomInfo)) // Quick reply if (!roomInfo.hasSmartReplyError) { val latestEventId = events.lastOrNull()?.eventId addAction(quickReplyActionFactory.create(roomInfo, latestEventId, threadId)) } - if (openIntent != null) { - setContentIntent(openIntent) - } - if (largeIcon != null) { - setLargeIcon(largeIcon) - } - setDeleteIntent(pendingIntentFactory.createDismissRoomPendingIntent(roomInfo.sessionId, roomInfo.roomId)) - - // If any of the events are of rtc notification type it means a missed call, set the category to the right value - if (events.any { it.type == EventType.RTC_NOTIFICATION }) { - setCategory(NotificationCompat.CATEGORY_MISSED_CALL) - } } .setTicker(tickerText) .build() } override fun createRoomInvitationNotification( - inviteNotifiableEvent: InviteNotifiableEvent + notificationAccountParams: NotificationAccountParams, + inviteNotifiableEvent: InviteNotifiableEvent, ): Notification { - val smallIcon = CommonDrawables.ic_notification val channelId = notificationChannels.getChannelIdForMessage(inviteNotifiableEvent.noisy) return NotificationCompat.Builder(context, channelId) .setOnlyAlertOnce(true) .setContentTitle((inviteNotifiableEvent.roomName ?: buildMeta.applicationName).annotateForDebug(5)) .setContentText(inviteNotifiableEvent.description.annotateForDebug(6)) - .setGroup(inviteNotifiableEvent.sessionId.value) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) - .setSmallIcon(smallIcon) - .setColor(accentColor) + .configureWith(notificationAccountParams) + .addAction(rejectInvitationActionFactory.create(inviteNotifiableEvent)) + .addAction(acceptInvitationActionFactory.create(inviteNotifiableEvent)) + // Build the pending intent for when the notification is clicked + .setContentIntent(pendingIntentFactory.createOpenRoomPendingIntent( + sessionId = inviteNotifiableEvent.sessionId, + roomId = inviteNotifiableEvent.roomId, + eventId = null, + )) .apply { - addAction(rejectInvitationActionFactory.create(inviteNotifiableEvent)) - addAction(acceptInvitationActionFactory.create(inviteNotifiableEvent)) - // Build the pending intent for when the notification is clicked - setContentIntent(pendingIntentFactory.createOpenRoomPendingIntent(inviteNotifiableEvent.sessionId, inviteNotifiableEvent.roomId)) - if (inviteNotifiableEvent.noisy) { // Compat priority = NotificationCompat.PRIORITY_DEFAULT - /* - vectorPreferences.getNotificationRingTone()?.let { - setSound(it) - } - */ - setLights(accentColor, 500, 500) + setLights(notificationAccountParams.color, 500, 500) } else { priority = NotificationCompat.PRIORITY_LOW } - setDeleteIntent( - pendingIntentFactory.createDismissInvitePendingIntent( - inviteNotifiableEvent.sessionId, - inviteNotifiableEvent.roomId, - ) - ) - setAutoCancel(true) } + .setDeleteIntent( + pendingIntentFactory.createDismissInvitePendingIntent( + inviteNotifiableEvent.sessionId, + inviteNotifiableEvent.roomId, + ) + ) + .setAutoCancel(true) .build() } override fun createSimpleEventNotification( + notificationAccountParams: NotificationAccountParams, simpleNotifiableEvent: SimpleNotifiableEvent, ): Notification { - val smallIcon = CommonDrawables.ic_notification - val channelId = notificationChannels.getChannelIdForMessage(simpleNotifiableEvent.noisy) return NotificationCompat.Builder(context, channelId) .setOnlyAlertOnce(true) .setContentTitle(buildMeta.applicationName.annotateForDebug(7)) .setContentText(simpleNotifiableEvent.description.annotateForDebug(8)) - .setGroup(simpleNotifiableEvent.sessionId.value) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) - .setSmallIcon(smallIcon) - .setColor(accentColor) + .configureWith(notificationAccountParams) .setAutoCancel(true) - .setContentIntent(pendingIntentFactory.createOpenRoomPendingIntent(simpleNotifiableEvent.sessionId, simpleNotifiableEvent.roomId)) + .setContentIntent(pendingIntentFactory.createOpenRoomPendingIntent( + sessionId = simpleNotifiableEvent.sessionId, + roomId = simpleNotifiableEvent.roomId, + eventId = null, + extras = bundleOf(ROOM_OPENED_FROM_NOTIFICATION to true), + )) .apply { if (simpleNotifiableEvent.noisy) { // Compat priority = NotificationCompat.PRIORITY_DEFAULT - /* - vectorPreferences.getNotificationRingTone()?.let { - setSound(it) - } - */ - setLights(accentColor, 500, 500) + setLights(notificationAccountParams.color, 500, 500) } else { priority = NotificationCompat.PRIORITY_LOW } @@ -298,19 +295,16 @@ class DefaultNotificationCreator( } override fun createFallbackNotification( + notificationAccountParams: NotificationAccountParams, fallbackNotifiableEvent: FallbackNotifiableEvent, ): Notification { - val smallIcon = CommonDrawables.ic_notification - val channelId = notificationChannels.getChannelIdForMessage(false) return NotificationCompat.Builder(context, channelId) .setOnlyAlertOnce(true) .setContentTitle(buildMeta.applicationName.annotateForDebug(7)) .setContentText(fallbackNotifiableEvent.description.orEmpty().annotateForDebug(8)) - .setGroup(fallbackNotifiableEvent.sessionId.value) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) - .setSmallIcon(smallIcon) - .setColor(accentColor) + .configureWith(notificationAccountParams) .setAutoCancel(true) .setWhen(fallbackNotifiableEvent.timestamp) // Ideally we'd use `createOpenRoomPendingIntent` here, but the broken notification might apply to an invite @@ -331,51 +325,45 @@ class DefaultNotificationCreator( * Create the summary notification. */ override fun createSummaryListNotification( - currentUser: MatrixUser, + notificationAccountParams: NotificationAccountParams, compatSummary: String, noisy: Boolean, - lastMessageTimestamp: Long + lastMessageTimestamp: Long, ): Notification { - val smallIcon = CommonDrawables.ic_notification val channelId = notificationChannels.getChannelIdForMessage(noisy) + val userId = notificationAccountParams.user.userId return NotificationCompat.Builder(context, channelId) .setOnlyAlertOnce(true) // used in compat < N, after summary is built based on child notifications .setWhen(lastMessageTimestamp) .setCategory(NotificationCompat.CATEGORY_MESSAGE) - .setSmallIcon(smallIcon) - .setGroup(currentUser.userId.value) // set this notification as the summary for the group .setGroupSummary(true) - .setColor(accentColor) + .configureWith(notificationAccountParams) .apply { if (noisy) { // Compat priority = NotificationCompat.PRIORITY_DEFAULT - /* - vectorPreferences.getNotificationRingTone()?.let { - setSound(it) - } - */ - setLights(accentColor, 500, 500) + setLights(notificationAccountParams.color, 500, 500) } else { // compat priority = NotificationCompat.PRIORITY_LOW } } - .setContentIntent(pendingIntentFactory.createOpenSessionPendingIntent(currentUser.userId)) - .setDeleteIntent(pendingIntentFactory.createDismissSummaryPendingIntent(currentUser.userId)) + .setContentIntent(pendingIntentFactory.createOpenSessionPendingIntent(userId)) + .setDeleteIntent(pendingIntentFactory.createDismissSummaryPendingIntent(userId)) .build() } - override fun createDiagnosticNotification(): Notification { + override fun createDiagnosticNotification( + @ColorInt color: Int, + ): Notification { val intent = pendingIntentFactory.createTestPendingIntent() return NotificationCompat.Builder(context, notificationChannels.getChannelIdForTest()) .setContentTitle(buildMeta.applicationName) .setContentText(stringProvider.getString(R.string.notification_test_push_notification_content)) .setSmallIcon(CommonDrawables.ic_notification) - .setLargeIcon(getBitmap(R.drawable.element_logo_green)) - .setColor(accentColor) + .setColor(color) .setPriority(NotificationCompat.PRIORITY_MAX) .setCategory(NotificationCompat.CATEGORY_STATUS) .setAutoCancel(true) @@ -384,6 +372,25 @@ class DefaultNotificationCreator( .build() } + override fun createUnregistrationNotification( + notificationAccountParams: NotificationAccountParams, + ): Notification { + val userId = notificationAccountParams.user.userId + val text = stringProvider.getString(R.string.notification_error_unified_push_unregistered_android) + return NotificationCompat.Builder(context, notificationChannels.getChannelIdForTest()) + .setSubText(userId.value) + // The text is long and can be truncated so use BigTextStyle. + .setStyle(NotificationCompat.BigTextStyle().bigText(text)) + .setContentTitle(stringProvider.getString(CommonStrings.dialog_title_warning)) + .setContentText(text) + .configureWith(notificationAccountParams) + .setPriority(NotificationCompat.PRIORITY_MAX) + .setCategory(NotificationCompat.CATEGORY_ERROR) + .setAutoCancel(true) + .setContentIntent(pendingIntentFactory.createOpenSessionPendingIntent(userId)) + .build() + } + private suspend fun MessagingStyle.addMessagesFromEvents( events: List, imageLoader: ImageLoader, @@ -406,7 +413,17 @@ class DefaultNotificationCreator( } Person.Builder() .setName(displayName.annotateForDebug(70)) - .setIcon(bitmapLoader.getUserIcon(event.senderAvatarPath, imageLoader)) + .setIcon( + bitmapLoader.getUserIcon( + avatarData = AvatarData( + id = event.senderId.value, + name = senderName, + url = event.senderAvatarPath, + size = AvatarSize.UserHeader, + ), + imageLoader = imageLoader, + ) + ) .setKey(key) .build() } @@ -417,65 +434,85 @@ class DefaultNotificationCreator( senderPerson ) else -> { - val message = MessagingStyle.Message( - event.body?.annotateForDebug(71), - event.timestamp, - senderPerson - ).also { message -> - event.imageUri?.let { - message.setData(event.imageMimeType ?: "image/", it) - } - message.extras.putString(MESSAGE_EVENT_ID, event.eventId.value) - } - addMessage(message) - - // Add additional message for captions - if (event.imageUri != null && event.body != null) { - addMessage( - MessagingStyle.Message( - event.body, - event.timestamp, - senderPerson, - ) + if (event.imageMimeType != null && event.imageUri != null) { + // Image case + val message = MessagingStyle.Message( + // This text will not be rendered, but some systems does not render the image + // if the text is null + stringProvider.getString(CommonStrings.common_image), + event.timestamp, + senderPerson, ) + .setData(event.imageMimeType, event.imageUri) + message.extras.putString(MESSAGE_EVENT_ID, event.eventId.value) + addMessage(message) + // Add additional message for captions + if (event.body != null) { + addMessage( + MessagingStyle.Message( + event.body.annotateForDebug(72), + event.timestamp, + senderPerson, + ) + ) + } + } else { + // Text case + val message = MessagingStyle.Message( + event.body?.annotateForDebug(71), + event.timestamp, + senderPerson + ) + message.extras.putString(MESSAGE_EVENT_ID, event.eventId.value) + addMessage(message) } } } } } - private suspend fun messagingStyleFromCurrentUser( - sessionId: SessionId, + private suspend fun createMessagingStyleFromCurrentUser( user: MatrixUser, imageLoader: ImageLoader, roomName: String, + isThread: Boolean, roomIsGroup: Boolean ): MessagingStyle { return MessagingStyle( Person.Builder() - .setName(user.displayName?.annotateForDebug(50)) - .setIcon(bitmapLoader.getUserIcon(user.avatarUrl, imageLoader)) - .setKey(sessionId.value) + // Note: name cannot be empty else NotificationCompat.MessagingStyle() will crash + .setName(user.getBestName().annotateForDebug(50)) + .setIcon( + bitmapLoader.getUserIcon( + avatarData = user.getAvatarData(AvatarSize.UserHeader), + imageLoader = imageLoader, + ) + ) + .setKey(user.userId.value) .build() ).also { - it.conversationTitle = roomName.takeIf { roomIsGroup } - it.isGroupConversation = roomIsGroup + it.conversationTitle = if (isThread) { + stringProvider.getString(R.string.notification_thread_in_room, roomName) + } else { + roomName + } + // So the avatar is displayed even if they're part of a conversation + it.isGroupConversation = roomIsGroup || isThread } } - private fun getBitmap(@DrawableRes drawableRes: Int): Bitmap? { - val drawable = ResourcesCompat.getDrawable(context.resources, drawableRes, null) ?: return null - val canvas = Canvas() - val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888) - canvas.setBitmap(bitmap) - drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight) - drawable.draw(canvas) - return bitmap - } - companion object { const val MESSAGE_EVENT_ID = "message_event_id" } } +private fun NotificationCompat.Builder.configureWith(notificationAccountParams: NotificationAccountParams) = apply { + setSmallIcon(CommonDrawables.ic_notification) + setColor(notificationAccountParams.color) + setGroup(notificationAccountParams.user.userId.value) + if (notificationAccountParams.showSessionId) { + setSubText(notificationAccountParams.user.userId.value) + } +} + fun NotifiableMessageEvent.isSmartReplyError() = outGoingMessage && outGoingMessageFailed diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/PendingIntentFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/PendingIntentFactory.kt index 9b36b0d370..07fa70a6c8 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/PendingIntentFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/PendingIntentFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,6 +11,7 @@ package io.element.android.libraries.push.impl.notifications.factories import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.os.Bundle import dev.zacsweers.metro.Inject import io.element.android.libraries.androidutils.uri.createIgnoredUri import io.element.android.libraries.di.annotations.ApplicationContext @@ -20,7 +22,6 @@ import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.push.impl.intent.IntentProvider import io.element.android.libraries.push.impl.notifications.NotificationActionIds import io.element.android.libraries.push.impl.notifications.NotificationBroadcastReceiver -import io.element.android.libraries.push.impl.notifications.RoomEventGroupInfo import io.element.android.libraries.push.impl.notifications.TestNotificationReceiver import io.element.android.services.toolbox.api.systemclock.SystemClock @@ -31,20 +32,20 @@ class PendingIntentFactory( private val clock: SystemClock, private val actionIds: NotificationActionIds, ) { - fun createOpenSessionPendingIntent(sessionId: SessionId): PendingIntent? { - return createRoomPendingIntent(sessionId = sessionId, roomId = null, threadId = null) + fun createOpenSessionPendingIntent(sessionId: SessionId, extras: Bundle? = null): PendingIntent? { + return createRoomPendingIntent(sessionId = sessionId, roomId = null, eventId = null, threadId = null, extras = extras) } - fun createOpenRoomPendingIntent(sessionId: SessionId, roomId: RoomId): PendingIntent? { - return createRoomPendingIntent(sessionId = sessionId, roomId = roomId, threadId = null) + fun createOpenRoomPendingIntent(sessionId: SessionId, roomId: RoomId, eventId: EventId?, extras: Bundle? = null): PendingIntent? { + return createRoomPendingIntent(sessionId = sessionId, roomId = roomId, eventId = eventId, threadId = null, extras = extras) } - fun createOpenThreadPendingIntent(roomInfo: RoomEventGroupInfo, threadId: ThreadId?): PendingIntent? { - return createRoomPendingIntent(sessionId = roomInfo.sessionId, roomId = roomInfo.roomId, threadId = threadId) + fun createOpenThreadPendingIntent(sessionId: SessionId, roomId: RoomId, eventId: EventId?, threadId: ThreadId, extras: Bundle? = null): PendingIntent? { + return createRoomPendingIntent(sessionId = sessionId, roomId = roomId, eventId = eventId, threadId = threadId, extras = extras) } - private fun createRoomPendingIntent(sessionId: SessionId, roomId: RoomId?, threadId: ThreadId?): PendingIntent? { - val intent = intentProvider.getViewRoomIntent(sessionId = sessionId, roomId = roomId, threadId = threadId) + private fun createRoomPendingIntent(sessionId: SessionId, roomId: RoomId?, eventId: EventId?, threadId: ThreadId?, extras: Bundle? = null): PendingIntent? { + val intent = intentProvider.getViewRoomIntent(sessionId = sessionId, roomId = roomId, eventId = eventId, threadId = threadId, extras = extras) return PendingIntent.getActivity( context, clock.epochMillis().toInt(), diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/AcceptInvitationActionFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/AcceptInvitationActionFactory.kt index 6e3fe95742..c951bbdb14 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/AcceptInvitationActionFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/AcceptInvitationActionFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,8 +15,8 @@ import androidx.core.app.NotificationCompat import dev.zacsweers.metro.Inject import io.element.android.appconfig.NotificationConfig import io.element.android.libraries.androidutils.uri.createIgnoredUri +import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.di.annotations.ApplicationContext -import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.notifications.NotificationActionIds import io.element.android.libraries.push.impl.notifications.NotificationBroadcastReceiver import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent @@ -46,7 +47,7 @@ class AcceptInvitationActionFactory( PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) return NotificationCompat.Action.Builder( - R.drawable.vector_notification_accept_invitation, + CompoundDrawables.ic_compound_check, stringProvider.getString(CommonStrings.action_accept), pendingIntent ).build() diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/MarkAsReadActionFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/MarkAsReadActionFactory.kt index 1feaee7954..599fcb5ce3 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/MarkAsReadActionFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/MarkAsReadActionFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,7 +15,9 @@ import androidx.core.app.NotificationCompat import dev.zacsweers.metro.Inject import io.element.android.appconfig.NotificationConfig import io.element.android.libraries.androidutils.uri.createIgnoredUri +import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.di.annotations.ApplicationContext +import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.notifications.NotificationActionIds import io.element.android.libraries.push.impl.notifications.NotificationBroadcastReceiver @@ -29,15 +32,16 @@ class MarkAsReadActionFactory( private val stringProvider: StringProvider, private val clock: SystemClock, ) { - fun create(roomInfo: RoomEventGroupInfo): NotificationCompat.Action? { + fun create(roomInfo: RoomEventGroupInfo, threadId: ThreadId?): NotificationCompat.Action? { if (!NotificationConfig.SHOW_MARK_AS_READ_ACTION) return null val sessionId = roomInfo.sessionId.value val roomId = roomInfo.roomId.value val intent = Intent(context, NotificationBroadcastReceiver::class.java) intent.action = actionIds.markRoomRead - intent.data = createIgnoredUri("markRead/$sessionId/$roomId") + intent.data = createIgnoredUri("markRead/$sessionId/$roomId" + threadId?.let { "/$it" }.orEmpty()) intent.putExtra(NotificationBroadcastReceiver.KEY_SESSION_ID, sessionId) intent.putExtra(NotificationBroadcastReceiver.KEY_ROOM_ID, roomId) + threadId?.let { intent.putExtra(NotificationBroadcastReceiver.KEY_THREAD_ID, threadId.value) } val pendingIntent = PendingIntent.getBroadcast( context, clock.epochMillis().toInt(), @@ -46,7 +50,7 @@ class MarkAsReadActionFactory( ) return NotificationCompat.Action.Builder( - R.drawable.ic_material_done_all_white, + CompoundDrawables.ic_compound_mark_as_read, stringProvider.getString(R.string.notification_room_action_mark_as_read), pendingIntent ) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/QuickReplyActionFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/QuickReplyActionFactory.kt index eb54bf9b36..c037adfb78 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/QuickReplyActionFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/QuickReplyActionFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,6 +17,7 @@ import androidx.core.app.RemoteInput import dev.zacsweers.metro.Inject import io.element.android.appconfig.NotificationConfig import io.element.android.libraries.androidutils.uri.createIgnoredUri +import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId @@ -45,7 +47,7 @@ class QuickReplyActionFactory( .build() return NotificationCompat.Action.Builder( - R.drawable.vector_notification_quick_reply, + CompoundDrawables.ic_compound_reply, stringProvider.getString(R.string.notification_room_action_quick_reply), replyPendingIntent ) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/RejectInvitationActionFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/RejectInvitationActionFactory.kt index 73b3fc2fd9..94ff565ad9 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/RejectInvitationActionFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/action/RejectInvitationActionFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,8 +15,8 @@ import androidx.core.app.NotificationCompat import dev.zacsweers.metro.Inject import io.element.android.appconfig.NotificationConfig import io.element.android.libraries.androidutils.uri.createIgnoredUri +import io.element.android.libraries.designsystem.icons.CompoundDrawables import io.element.android.libraries.di.annotations.ApplicationContext -import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.notifications.NotificationActionIds import io.element.android.libraries.push.impl.notifications.NotificationBroadcastReceiver import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent @@ -46,7 +47,7 @@ class RejectInvitationActionFactory( PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) return NotificationCompat.Action.Builder( - R.drawable.vector_notification_reject_invitation, + CompoundDrawables.ic_compound_close, stringProvider.getString(CommonStrings.action_reject), pendingIntent ).build() diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/FallbackNotifiableEvent.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/FallbackNotifiableEvent.kt index f9a08de195..97f66face0 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/FallbackNotifiableEvent.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/FallbackNotifiableEvent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/InviteNotifiableEvent.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/InviteNotifiableEvent.kt index 37aaeeda47..91d5230c2e 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/InviteNotifiableEvent.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/InviteNotifiableEvent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.notifications.model diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableEvent.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableEvent.kt index 3fc5871f32..4a26b9f8cc 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableEvent.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableEvent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt index ba7cd2f78b..07f7f8b3c7 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableMessageEvent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.notifications.model diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableRingingCallEvent.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableRingingCallEvent.kt index 7d92ad3a8b..35432e972d 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableRingingCallEvent.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/NotifiableRingingCallEvent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/ResolvedPushEvent.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/ResolvedPushEvent.kt index 2363e38e1d..3c79896c6e 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/ResolvedPushEvent.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/ResolvedPushEvent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/SimpleNotifiableEvent.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/SimpleNotifiableEvent.kt index f77bf855ba..c1c2ce9187 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/SimpleNotifiableEvent.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/SimpleNotifiableEvent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.notifications.model diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/shortcut/Utils.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/shortcut/Utils.kt index 6176886dbc..4a09c28f24 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/shortcut/Utils.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/shortcut/Utils.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 3a4cf4d6aa..7aec719084 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,14 +10,17 @@ package io.element.android.libraries.push.impl.push import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.features.call.api.CallType import io.element.android.features.call.api.ElementCallEntryPoint import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.annotations.AppCoroutineScope +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.exception.NotificationResolverException +import io.element.android.libraries.push.api.push.NotificationEventRequest +import io.element.android.libraries.push.api.push.SyncOnNotifiableEvent import io.element.android.libraries.push.impl.history.PushHistoryService import io.element.android.libraries.push.impl.history.onDiagnosticPush import io.element.android.libraries.push.impl.history.onInvalidPushReceived @@ -24,7 +28,6 @@ import io.element.android.libraries.push.impl.history.onSuccess import io.element.android.libraries.push.impl.history.onUnableToResolveEvent import io.element.android.libraries.push.impl.history.onUnableToRetrieveSession import io.element.android.libraries.push.impl.notifications.FallbackNotificationFactory -import io.element.android.libraries.push.impl.notifications.NotificationEventRequest import io.element.android.libraries.push.impl.notifications.NotificationResolverQueue import io.element.android.libraries.push.impl.notifications.channels.NotificationChannels import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent @@ -48,7 +51,6 @@ private val loggerTag = LoggerTag("PushHandler", LoggerTag.PushLoggerTag) @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DefaultPushHandler( private val onNotifiableEventReceived: OnNotifiableEventReceived, private val onRedactedEventReceived: OnRedactedEventReceived, @@ -65,6 +67,8 @@ class DefaultPushHandler( @AppCoroutineScope private val appCoroutineScope: CoroutineScope, private val fallbackNotificationFactory: FallbackNotificationFactory, + private val syncOnNotifiableEvent: SyncOnNotifiableEvent, + private val featureFlagService: FeatureFlagService, ) : PushHandler { init { processPushEventResults() @@ -180,9 +184,9 @@ class DefaultPushHandler( } } - // Process redactions of messages + // Process redactions of messages in background to not block operations with higher priority if (redactions.isNotEmpty()) { - onRedactedEventReceived.onRedactedEventsReceived(redactions) + appCoroutineScope.launch { onRedactedEventReceived.onRedactedEventsReceived(redactions) } } // Find and process ringing call notifications separately @@ -196,6 +200,10 @@ class DefaultPushHandler( if (nonRingingCallEvents.isNotEmpty()) { onNotifiableEventReceived.onNotifiableEventsReceived(nonRingingCallEvents) } + + if (!featureFlagService.isFeatureEnabled(FeatureFlags.SyncNotificationsWithWorkManager)) { + syncOnNotifiableEvent(requests) + } } .launchIn(appCoroutineScope) } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/SyncOnNotifiableEvent.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultSyncOnNotifiableEvent.kt similarity index 70% rename from libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/SyncOnNotifiableEvent.kt rename to libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultSyncOnNotifiableEvent.kt index e3384a9b9b..8b8e671fcc 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/SyncOnNotifiableEvent.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultSyncOnNotifiableEvent.kt @@ -1,47 +1,50 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.push -import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.MatrixClientProvider -import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent +import io.element.android.libraries.push.api.push.NotificationEventRequest +import io.element.android.libraries.push.api.push.SyncOnNotifiableEvent import io.element.android.services.appnavstate.api.AppForegroundStateService import kotlinx.coroutines.delay import kotlinx.coroutines.withContext import timber.log.Timber import kotlin.time.Duration.Companion.seconds -@Inject -class SyncOnNotifiableEvent( +@ContributesBinding(AppScope::class) +class DefaultSyncOnNotifiableEvent( private val matrixClientProvider: MatrixClientProvider, private val featureFlagService: FeatureFlagService, private val appForegroundStateService: AppForegroundStateService, private val dispatchers: CoroutineDispatchers, -) { - suspend operator fun invoke(notifiableEvents: List) = withContext(dispatchers.io) { +) : SyncOnNotifiableEvent { + override suspend operator fun invoke(requests: List) = withContext(dispatchers.io) { if (!featureFlagService.isFeatureEnabled(FeatureFlags.SyncOnPush)) { return@withContext } try { - val eventsBySession = notifiableEvents.groupBy { it.sessionId } + val eventsBySession = requests.groupBy { it.sessionId } appForegroundStateService.updateIsSyncingNotificationEvent(true) Timber.d("Starting opportunistic room list sync | In foreground: ${appForegroundStateService.isInForeground.value}") for ((sessionId, events) in eventsBySession) { val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: continue - val eventsByRoomId = events.groupBy { it.roomId } + val roomIds = events.map { it.roomId }.distinct() - client.roomListService.subscribeToVisibleRooms(eventsByRoomId.keys.toList()) + client.roomListService.subscribeToVisibleRooms(roomIds) if (!appForegroundStateService.isInForeground.value) { // Give the sync some time to complete in background diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/IncrementPushDataStore.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/IncrementPushDataStore.kt index cf7145e3ef..0a6467f884 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/IncrementPushDataStore.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/IncrementPushDataStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.push.impl.push import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.push.impl.store.DefaultPushDataStore interface IncrementPushDataStore { @@ -17,7 +17,6 @@ interface IncrementPushDataStore { } @ContributesBinding(AppScope::class) -@Inject class DefaultIncrementPushDataStore( private val defaultPushDataStore: DefaultPushDataStore ) : IncrementPushDataStore { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/MutableBatteryOptimizationStore.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/MutableBatteryOptimizationStore.kt index 3c5efe70ba..0cb0d94d3c 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/MutableBatteryOptimizationStore.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/MutableBatteryOptimizationStore.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.push.impl.push import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.push.impl.store.DefaultPushDataStore interface MutableBatteryOptimizationStore { @@ -19,7 +19,6 @@ interface MutableBatteryOptimizationStore { } @ContributesBinding(AppScope::class) -@Inject class DefaultMutableBatteryOptimizationStore( private val defaultPushDataStore: DefaultPushDataStore, ) : MutableBatteryOptimizationStore { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/OnNotifiableEventReceived.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/OnNotifiableEventReceived.kt index 6daee8f137..2c4339d16a 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/OnNotifiableEventReceived.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/OnNotifiableEventReceived.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.push.impl.push import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.annotations.AppCoroutineScope import io.element.android.libraries.push.impl.notifications.DefaultNotificationDrawerManager import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent @@ -22,16 +22,13 @@ interface OnNotifiableEventReceived { } @ContributesBinding(AppScope::class) -@Inject class DefaultOnNotifiableEventReceived( private val defaultNotificationDrawerManager: DefaultNotificationDrawerManager, @AppCoroutineScope private val coroutineScope: CoroutineScope, - private val syncOnNotifiableEvent: SyncOnNotifiableEvent, ) : OnNotifiableEventReceived { override fun onNotifiableEventsReceived(notifiableEvents: List) { coroutineScope.launch { - launch { syncOnNotifiableEvent(notifiableEvents) } defaultNotificationDrawerManager.onNotifiableEventsReceived(notifiableEvents.filter { it !is NotifiableRingingCallEvent }) } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/OnRedactedEventReceived.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/OnRedactedEventReceived.kt index dda61f1eb0..18388ba522 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/OnRedactedEventReceived.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/OnRedactedEventReceived.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,8 +17,6 @@ import androidx.core.text.buildSpannedString import androidx.core.text.inSpans import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject -import io.element.android.libraries.di.annotations.AppCoroutineScope import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.push.impl.notifications.ActiveNotificationsProvider import io.element.android.libraries.push.impl.notifications.NotificationDisplayer @@ -25,72 +24,63 @@ import io.element.android.libraries.push.impl.notifications.factories.DefaultNot import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.toolbox.api.strings.StringProvider -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch import timber.log.Timber interface OnRedactedEventReceived { - fun onRedactedEventsReceived(redactions: List) + suspend fun onRedactedEventsReceived(redactions: List) } @ContributesBinding(AppScope::class) -@Inject class DefaultOnRedactedEventReceived( private val activeNotificationsProvider: ActiveNotificationsProvider, private val notificationDisplayer: NotificationDisplayer, - @AppCoroutineScope - private val coroutineScope: CoroutineScope, @ApplicationContext private val context: Context, private val stringProvider: StringProvider, ) : OnRedactedEventReceived { - override fun onRedactedEventsReceived(redactions: List) { - coroutineScope.launch { - val redactionsBySessionIdAndRoom = redactions.groupBy { redaction -> - redaction.sessionId to redaction.roomId + override suspend fun onRedactedEventsReceived(redactions: List) { + val redactionsBySessionIdAndRoom = redactions.groupBy { redaction -> + redaction.sessionId to redaction.roomId + } + for ((keys, roomRedactions) in redactionsBySessionIdAndRoom) { + val (sessionId, roomId) = keys + // Get all notifications for the room, including those for threads + val notifications = activeNotificationsProvider.getAllMessageNotificationsForRoom(sessionId, roomId) + if (notifications.isEmpty()) { + Timber.d("No notifications found for redacted event") } - for ((keys, roomRedactions) in redactionsBySessionIdAndRoom) { - val (sessionId, roomId) = keys - val notifications = activeNotificationsProvider.getMessageNotificationsForRoom( - sessionId, - roomId, + notifications.forEach { statusBarNotification -> + val notification = statusBarNotification.notification + val messagingStyle = MessagingStyle.extractMessagingStyleFromNotification(notification) + if (messagingStyle == null) { + Timber.w("Unable to retrieve messaging style from notification") + return@forEach + } + val messageToRedactIndex = messagingStyle.messages.indexOfFirst { message -> + roomRedactions.any { it.redactedEventId.value == message.extras.getString(DefaultNotificationCreator.MESSAGE_EVENT_ID) } + } + if (messageToRedactIndex == -1) { + Timber.d("Unable to find the message to remove from notification") + return@forEach + } + val oldMessage = messagingStyle.messages[messageToRedactIndex] + val content = buildSpannedString { + inSpans(StyleSpan(Typeface.ITALIC)) { + append(stringProvider.getString(CommonStrings.common_message_removed)) + } + } + val newMessage = MessagingStyle.Message( + content, + oldMessage.timestamp, + oldMessage.person + ) + messagingStyle.messages[messageToRedactIndex] = newMessage + notificationDisplayer.showNotification( + statusBarNotification.tag, + statusBarNotification.id, + NotificationCompat.Builder(context, notification) + .setStyle(messagingStyle) + .build() ) - if (notifications.isEmpty()) { - Timber.d("No notifications found for redacted event") - } - notifications.forEach { statusBarNotification -> - val notification = statusBarNotification.notification - val messagingStyle = MessagingStyle.extractMessagingStyleFromNotification(notification) - if (messagingStyle == null) { - Timber.w("Unable to retrieve messaging style from notification") - return@forEach - } - val messageToRedactIndex = messagingStyle.messages.indexOfFirst { message -> - roomRedactions.any { it.redactedEventId.value == message.extras.getString(DefaultNotificationCreator.MESSAGE_EVENT_ID) } - } - if (messageToRedactIndex == -1) { - Timber.d("Unable to find the message to remove from notification") - return@forEach - } - val oldMessage = messagingStyle.messages[messageToRedactIndex] - val content = buildSpannedString { - inSpans(StyleSpan(Typeface.ITALIC)) { - append(stringProvider.getString(CommonStrings.common_message_removed)) - } - } - val newMessage = MessagingStyle.Message( - content, - oldMessage.timestamp, - oldMessage.person - ) - messagingStyle.messages[messageToRedactIndex] = newMessage - notificationDisplayer.showNotificationMessage( - statusBarNotification.tag, - statusBarNotification.id, - NotificationCompat.Builder(context, notification) - .setStyle(messagingStyle) - .build() - ) - } } } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayAPI.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayAPI.kt index 070074f36b..3458687a9e 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayAPI.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayAPI.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.pushgateway diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayApiFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayApiFactory.kt index e57d98d796..9e1d370f9a 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayApiFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayApiFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.push.impl.pushgateway import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.network.RetrofitFactory interface PushGatewayApiFactory { @@ -17,7 +17,6 @@ interface PushGatewayApiFactory { } @ContributesBinding(AppScope::class) -@Inject class DefaultPushGatewayApiFactory( private val retrofitFactory: RetrofitFactory, ) : PushGatewayApiFactory { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayConfig.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayConfig.kt index 9b4bb58e83..8cdf0e6137 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayConfig.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayDevice.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayDevice.kt index 47ebc2754b..20a9e3ebed 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayDevice.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayDevice.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -21,5 +22,14 @@ data class PushGatewayDevice( * Required. The pushkey given when the pusher was created. */ @SerialName("pushkey") - val pushKey: String + val pushKey: String, + /** Optional. Additional pusher data. */ + @SerialName("data") + val data: PusherData? = null, +) + +@Serializable +data class PusherData( + @SerialName("default_payload") + val defaultPayload: Map, ) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotification.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotification.kt index ebbc305a75..a7e4438866 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotification.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotification.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -20,5 +21,5 @@ data class PushGatewayNotification( * Required. This is an array of devices that the notification should be sent to. */ @SerialName("devices") - val devices: List + val devices: List, ) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyBody.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyBody.kt index 2bf4e1c64b..6848ca3493 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyBody.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyBody.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyRequest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyRequest.kt index 65f5f8a139..6315285559 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyRequest.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyRequest.kt @@ -1,14 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.pushgateway import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.push.api.gateway.PushGatewayFailure @@ -26,7 +26,6 @@ interface PushGatewayNotifyRequest { } @ContributesBinding(AppScope::class) -@Inject class DefaultPushGatewayNotifyRequest( private val pushGatewayApiFactory: PushGatewayApiFactory, ) : PushGatewayNotifyRequest { @@ -42,9 +41,12 @@ class DefaultPushGatewayNotifyRequest( devices = listOf( PushGatewayDevice( params.appId, - params.pushKey + params.pushKey, + PusherData(mapOf( + "cs" to "A_FAKE_SECRET", + )) ) - ) + ), ) ) ) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyResponse.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyResponse.kt index 87dfc44c21..9c2f682da6 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyResponse.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyResponse.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/store/DefaultPushDataStore.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/store/DefaultPushDataStore.kt index e84ffaf7f0..170b8e8d8b 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/store/DefaultPushDataStore.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/store/DefaultPushDataStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,6 @@ import app.cash.sqldelight.coroutines.asFlow import app.cash.sqldelight.coroutines.mapToList import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.dateformatter.api.DateFormatter import io.element.android.libraries.dateformatter.api.DateFormatterMode @@ -30,7 +30,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @ContributesBinding(AppScope::class) -@Inject class DefaultPushDataStore( private val pushDatabase: PushDatabase, private val dateFormatter: DateFormatter, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/store/PushDataStore.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/store/PushDataStore.kt index de8f6dbbc1..a8ea6cc532 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/store/PushDataStore.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/store/PushDataStore.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/test/TestPush.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/test/TestPush.kt index c49bc48b6f..4ed8ca8338 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/test/TestPush.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/test/TestPush.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,23 +10,21 @@ package io.element.android.libraries.push.impl.test import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.appconfig.PushConfig import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.push.impl.pushgateway.PushGatewayNotifyRequest -import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig +import io.element.android.libraries.pushproviders.api.Config interface TestPush { - suspend fun execute(config: CurrentUserPushConfig) + suspend fun execute(config: Config) } @ContributesBinding(AppScope::class) -@Inject class DefaultTestPush( private val pushGatewayNotifyRequest: PushGatewayNotifyRequest, ) : TestPush { - override suspend fun execute(config: CurrentUserPushConfig) { + override suspend fun execute(config: Config) { pushGatewayNotifyRequest.execute( PushGatewayNotifyRequest.Params( url = config.url, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTest.kt index 3eeaae1b2d..347ee3bee4 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTest.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -37,7 +38,7 @@ class CurrentPushProviderTest( override suspend fun run(coroutineScope: CoroutineScope) { delegate.start() - val pushProvider = pushService.getCurrentPushProvider() + val pushProvider = pushService.getCurrentPushProvider(sessionId) if (pushProvider == null) { delegate.updateState( description = stringProvider.getString(R.string.troubleshoot_notifications_test_current_push_provider_failure), diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/DiagnosticPushHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/DiagnosticPushHandler.kt index 86cd2f630c..72153fa960 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/DiagnosticPushHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/DiagnosticPushHandler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTest.kt index 93e855c94d..59ba1eba0e 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTest.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -62,7 +63,7 @@ class IgnoredUsersTest( coroutineScope: CoroutineScope, navigator: NotificationTroubleshootNavigator, ) { - navigator.openIgnoredUsers() + navigator.navigateToBlockedUsers() } override suspend fun reset() = delegate.reset() diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationClickHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationClickHandler.kt index e20876198e..c370c043be 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationClickHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationClickHandler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTest.kt index da390f85f2..96860bf3e0 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTest.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTest.kt @@ -1,15 +1,20 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.troubleshoot -import dev.zacsweers.metro.AppScope +import androidx.compose.ui.graphics.toArgb import dev.zacsweers.metro.ContributesIntoSet import dev.zacsweers.metro.Inject +import io.element.android.appconfig.NotificationConfig +import io.element.android.features.enterprise.api.EnterpriseService +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.notifications.NotificationDisplayer import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator @@ -25,13 +30,15 @@ import kotlinx.coroutines.withTimeout import timber.log.Timber import kotlin.time.Duration.Companion.seconds -@ContributesIntoSet(AppScope::class) +@ContributesIntoSet(SessionScope::class) @Inject class NotificationTest( + private val sessionId: SessionId, private val notificationCreator: NotificationCreator, private val notificationDisplayer: NotificationDisplayer, private val notificationClickHandler: NotificationClickHandler, private val stringProvider: StringProvider, + private val enterpriseService: EnterpriseService, ) : NotificationTroubleshootTest { override val order = 50 private val delegate = NotificationTroubleshootTestDelegate( @@ -43,7 +50,9 @@ class NotificationTest( override suspend fun run(coroutineScope: CoroutineScope) { delegate.start() - val notification = notificationCreator.createDiagnosticNotification() + val color = enterpriseService.brandColorsFlow(sessionId).first()?.toArgb() + ?: NotificationConfig.NOTIFICATION_ACCENT_COLOR + val notification = notificationCreator.createDiagnosticNotification(color) val result = notificationDisplayer.displayDiagnosticNotification(notification) if (result) { coroutineScope.listenToNotificationClick() diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTest.kt index 5b9a9e2fd1..91ac1ca0ee 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTest.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTest.kt @@ -1,15 +1,17 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.troubleshoot -import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet import dev.zacsweers.metro.Inject +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.push.api.PushService import io.element.android.libraries.push.api.gateway.PushGatewayFailure import io.element.android.libraries.push.impl.R @@ -28,9 +30,10 @@ import kotlinx.coroutines.withTimeout import timber.log.Timber import kotlin.time.Duration.Companion.seconds -@ContributesIntoSet(AppScope::class) +@ContributesIntoSet(SessionScope::class) @Inject class PushLoopbackTest( + private val sessionId: SessionId, private val pushService: PushService, private val diagnosticPushHandler: DiagnosticPushHandler, private val clock: SystemClock, @@ -52,9 +55,9 @@ class PushLoopbackTest( completable.complete(clock.epochMillis() - startTime) } val testPushResult = try { - pushService.testPush() - } catch (pusherRejected: PushGatewayFailure.PusherRejected) { - val hasQuickFix = pushService.getCurrentPushProvider()?.canRotateToken() == true + pushService.testPush(sessionId) + } catch (_: PushGatewayFailure.PusherRejected) { + val hasQuickFix = pushService.getCurrentPushProvider(sessionId)?.canRotateToken() == true delegate.updateState( description = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_failure_1), status = NotificationTroubleshootTestState.Status.Failure(hasQuickFix = hasQuickFix) @@ -105,7 +108,7 @@ class PushLoopbackTest( navigator: NotificationTroubleshootNavigator, ) { delegate.start() - pushService.getCurrentPushProvider()?.rotateToken() + pushService.getCurrentPushProvider(sessionId)?.rotateToken() run(coroutineScope) } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTest.kt index 8e4d1db639..1a6368b75f 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTest.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/unregistration/ServiceUnregisteredHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/unregistration/ServiceUnregisteredHandler.kt new file mode 100644 index 0000000000..29052b42ca --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/unregistration/ServiceUnregisteredHandler.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.impl.unregistration + +import androidx.compose.ui.graphics.toArgb +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import io.element.android.appconfig.NotificationConfig +import io.element.android.features.enterprise.api.EnterpriseService +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.push.impl.notifications.NotificationDisplayer +import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams +import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator +import io.element.android.libraries.sessionstorage.api.SessionStore +import kotlinx.coroutines.flow.first + +interface ServiceUnregisteredHandler { + suspend fun handle(userId: UserId) +} + +@ContributesBinding(AppScope::class) +class DefaultServiceUnregisteredHandler( + private val enterpriseService: EnterpriseService, + private val notificationCreator: NotificationCreator, + private val notificationDisplayer: NotificationDisplayer, + private val sessionStore: SessionStore, +) : ServiceUnregisteredHandler { + override suspend fun handle(userId: UserId) { + val color = enterpriseService.brandColorsFlow(userId).first()?.toArgb() + ?: NotificationConfig.NOTIFICATION_ACCENT_COLOR + val hasMultipleAccounts = sessionStore.numberOfSessions() > 1 + val notification = notificationCreator.createUnregistrationNotification( + NotificationAccountParams( + user = MatrixUser(userId), + color = color, + showSessionId = hasMultipleAccounts, + ) + ) + notificationDisplayer.displayUnregistrationNotification(notification) + } +} diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/DataForWorkManagerIsTooBig.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/DataForWorkManagerIsTooBig.kt new file mode 100644 index 0000000000..e700a58a7e --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/DataForWorkManagerIsTooBig.kt @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.impl.workmanager + +class DataForWorkManagerIsTooBig : Exception() diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/FetchNotificationsWorker.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/FetchNotificationsWorker.kt new file mode 100644 index 0000000000..eeac5ff66f --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/FetchNotificationsWorker.kt @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.impl.workmanager + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedFactory +import dev.zacsweers.metro.AssistedInject +import dev.zacsweers.metro.ContributesIntoMap +import dev.zacsweers.metro.binding +import io.element.android.features.networkmonitor.api.NetworkMonitor +import io.element.android.features.networkmonitor.api.NetworkStatus +import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.extensions.runCatchingExceptions +import io.element.android.libraries.di.annotations.ApplicationContext +import io.element.android.libraries.matrix.api.auth.SessionRestorationException +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.push.api.push.NotificationEventRequest +import io.element.android.libraries.push.api.push.SyncOnNotifiableEvent +import io.element.android.libraries.push.impl.notifications.NotifiableEventResolver +import io.element.android.libraries.push.impl.notifications.NotificationResolverQueue +import io.element.android.libraries.workmanager.api.WorkManagerScheduler +import io.element.android.libraries.workmanager.api.di.MetroWorkerFactory +import io.element.android.libraries.workmanager.api.di.WorkerKey +import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeoutOrNull +import timber.log.Timber +import kotlin.time.Duration.Companion.seconds + +@AssistedInject +class FetchNotificationsWorker( + @Assisted workerParams: WorkerParameters, + @ApplicationContext private val context: Context, + private val networkMonitor: NetworkMonitor, + private val eventResolver: NotifiableEventResolver, + private val queue: NotificationResolverQueue, + private val workManagerScheduler: WorkManagerScheduler, + private val syncOnNotifiableEvent: SyncOnNotifiableEvent, + private val coroutineDispatchers: CoroutineDispatchers, + private val workerDataConverter: SyncNotificationsWorkerDataConverter, + private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider, +) : CoroutineWorker(context, workerParams) { + override suspend fun doWork(): Result = withContext(coroutineDispatchers.io) { + Timber.d("FetchNotificationsWorker started") + val requests = workerDataConverter.deserialize(inputData) ?: return@withContext Result.failure() + // Wait for network to be available, but not more than 10 seconds + val hasNetwork = withTimeoutOrNull(10.seconds) { + networkMonitor.connectivity.first { it == NetworkStatus.Connected } + } != null + if (!hasNetwork) { + Timber.w("No network, retrying later") + return@withContext Result.retry() + } + + val failedSyncForSessions = mutableMapOf() + + val groupedRequests = requests.groupBy { it.sessionId }.toMutableMap() + for ((sessionId, notificationRequests) in groupedRequests) { + Timber.d("Processing notification requests for session $sessionId") + eventResolver.resolveEvents(sessionId, notificationRequests) + .fold( + onSuccess = { result -> + // Update the resolved results in the queue + (queue.results as MutableSharedFlow).emit(requests to result) + }, + onFailure = { + failedSyncForSessions[sessionId] = it + Timber.e(it, "Failed to resolve notification events for session $sessionId") + } + ) + } + + // If there were failures for whole sessions, we retry all their requests + if (failedSyncForSessions.isNotEmpty()) { + @Suppress("LoopWithTooManyJumpStatements") + for ((failedSessionId, exception) in failedSyncForSessions) { + if (exception.cause is SessionRestorationException) { + Timber.e(exception, "Session $failedSessionId could not be restored, not retrying notification fetching") + groupedRequests.remove(failedSessionId) + continue + } + val requestsToRetry = groupedRequests[failedSessionId] ?: continue + Timber.d("Re-scheduling ${requestsToRetry.size} failed notification requests for session $failedSessionId") + workManagerScheduler.submit( + SyncNotificationWorkManagerRequest( + sessionId = failedSessionId, + notificationEventRequests = requestsToRetry, + workerDataConverter = workerDataConverter, + buildVersionSdkIntProvider = buildVersionSdkIntProvider, + ) + ) + } + } + + Timber.d("Notifications processed successfully") + + performOpportunisticSyncIfNeeded(groupedRequests) + + Result.success() + } + + private suspend fun performOpportunisticSyncIfNeeded( + groupedRequests: Map>, + ) { + for ((sessionId, notificationRequests) in groupedRequests) { + runCatchingExceptions { + syncOnNotifiableEvent(notificationRequests) + }.onFailure { + Timber.e(it, "Failed to sync on notifiable events for session $sessionId") + } + } + } + + @ContributesIntoMap(AppScope::class, binding = binding>()) + @WorkerKey(FetchNotificationsWorker::class) + @AssistedFactory + interface Factory : MetroWorkerFactory.WorkerInstanceFactory +} diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationWorkManagerRequest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationWorkManagerRequest.kt new file mode 100644 index 0000000000..50ef28903c --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationWorkManagerRequest.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.impl.workmanager + +import android.os.Build +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.OutOfQuotaPolicy +import androidx.work.WorkRequest +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.push.api.push.NotificationEventRequest +import io.element.android.libraries.workmanager.api.WorkManagerRequest +import io.element.android.libraries.workmanager.api.WorkManagerRequestType +import io.element.android.libraries.workmanager.api.workManagerTag +import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import timber.log.Timber +import java.security.InvalidParameterException + +class SyncNotificationWorkManagerRequest( + private val sessionId: SessionId, + private val notificationEventRequests: List, + private val workerDataConverter: SyncNotificationsWorkerDataConverter, + private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider, +) : WorkManagerRequest { + override fun build(): Result> { + if (notificationEventRequests.isEmpty()) { + return Result.failure(InvalidParameterException("notificationEventRequests cannot be empty")) + } + Timber.d("Scheduling ${notificationEventRequests.size} notification requests with WorkManager for $sessionId") + return workerDataConverter.serialize(notificationEventRequests).map { dataList -> + dataList.map { data -> + OneTimeWorkRequestBuilder() + .setInputData(data) + .apply { + // Expedited workers aren't needed on Android 12 or lower: + // They force displaying a foreground sync notification for no good reason, since they sync almost immediately anyway + // See https://developer.android.com/develop/background-work/background-tasks/persistent/getting-started/define-work#backwards-compat + if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) { + setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) + } + } + .setTraceTag(workManagerTag(sessionId, WorkManagerRequestType.NOTIFICATION_SYNC)) + // TODO investigate using this instead of the resolver queue + // .setInputMerger() + .build() + } + } + } + + @Serializable + data class Data( + @SerialName("session_id") + val sessionId: String, + @SerialName("room_id") + val roomId: String, + @SerialName("event_id") + val eventId: String, + @SerialName("provider_info") + val providerInfo: String, + ) +} diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationsWorkerDataConverter.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationsWorkerDataConverter.kt new file mode 100644 index 0000000000..46b7d760c0 --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationsWorkerDataConverter.kt @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.impl.workmanager + +import androidx.work.Data +import androidx.work.workDataOf +import dev.zacsweers.metro.Inject +import io.element.android.libraries.androidutils.json.JsonProvider +import io.element.android.libraries.core.extensions.mapCatchingExceptions +import io.element.android.libraries.core.extensions.runCatchingExceptions +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.push.api.push.NotificationEventRequest +import timber.log.Timber + +@Inject +class SyncNotificationsWorkerDataConverter( + private val json: JsonProvider, +) { + fun serialize(notificationEventRequests: List): Result> { + // First try to serialize all requests at once. In the vast majority of cases this will work. + return serializeRequests(notificationEventRequests) + .map { listOf(it) } + .recoverCatching { t -> + if (t is DataForWorkManagerIsTooBig) { + // Perform serialization on sublists, workDataOf have failed because of size limit + Timber.w(t, "Failed to serialize ${notificationEventRequests.size} notification requests, split the requests per room.") + // Group the requests per rooms + val requestsSortedPerRoom = notificationEventRequests.groupBy { it.roomId }.values + // Build a list of sublist with size at most CHUNK_SIZE, and with all rooms kept together + buildList { + val currentChunk = mutableListOf() + for (requests in requestsSortedPerRoom) { + if (currentChunk.size + requests.size <= CHUNK_SIZE) { + // Can add the whole room requests to the current chunk + currentChunk.addAll(requests) + } else { + // Add the current chunk + add(currentChunk.toList()) + // Start a new chunk with the current room requests + currentChunk.clear() + // If a room has more requests than CHUNK_SIZE, we need to split them + requests.chunked(CHUNK_SIZE) { chunk -> + if (chunk.size == CHUNK_SIZE) { + add(chunk.toList()) + } else { + currentChunk.addAll(chunk) + } + } + } + } + // Add any remaining requests + add(currentChunk.toList()) + } + .filter { it.isNotEmpty() } + .also { + Timber.d("Split notification requests into ${it.size} chunks for WorkManager serialization") + it.forEach { requests -> + Timber.d(" - Chunk with ${requests.size} requests") + } + } + .mapNotNull { serializeRequests(it).getOrNull() } + } else { + throw t + } + } + } + + private fun serializeRequests(notificationEventRequests: List): Result { + return runCatchingExceptions { json().encodeToString(notificationEventRequests.map { it.toData() }) } + .onFailure { + Timber.e(it, "Failed to serialize notification requests") + } + .mapCatchingExceptions { str -> + // Note: workDataOf can fail if the data is too large + try { + workDataOf(REQUESTS_KEY to str) + } catch (_: IllegalStateException) { + throw DataForWorkManagerIsTooBig() + } + } + } + + fun deserialize(data: Data): List? { + val rawRequestsJson = data.getString(REQUESTS_KEY) ?: return null + return runCatchingExceptions { + json().decodeFromString>(rawRequestsJson).map { it.toRequest() } + }.fold( + onSuccess = { + Timber.d("Deserialized ${it.size} requests") + it + }, + onFailure = { + Timber.e(it, "Failed to deserialize notification requests") + null + } + ) + } + + companion object { + private const val REQUESTS_KEY = "requests" + internal const val CHUNK_SIZE = 20 + } +} + +private fun NotificationEventRequest.toData(): SyncNotificationWorkManagerRequest.Data { + return SyncNotificationWorkManagerRequest.Data( + sessionId = sessionId.value, + roomId = roomId.value, + eventId = eventId.value, + providerInfo = providerInfo, + ) +} + +private fun SyncNotificationWorkManagerRequest.Data.toRequest(): NotificationEventRequest { + return NotificationEventRequest( + sessionId = SessionId(sessionId), + roomId = RoomId(roomId), + eventId = EventId(eventId), + providerInfo = providerInfo, + ) +} diff --git a/libraries/push/impl/src/main/res/drawable-xxhdpi/element_logo_green.xml b/libraries/push/impl/src/main/res/drawable-xxhdpi/element_logo_green.xml deleted file mode 100644 index e9b119c969..0000000000 --- a/libraries/push/impl/src/main/res/drawable-xxhdpi/element_logo_green.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - diff --git a/libraries/push/impl/src/main/res/drawable-xxhdpi/ic_material_done_all_white.png b/libraries/push/impl/src/main/res/drawable-xxhdpi/ic_material_done_all_white.png deleted file mode 100755 index 1f3132a3f2..0000000000 Binary files a/libraries/push/impl/src/main/res/drawable-xxhdpi/ic_material_done_all_white.png and /dev/null differ diff --git a/libraries/push/impl/src/main/res/drawable-xxhdpi/vector_notification_accept_invitation.png b/libraries/push/impl/src/main/res/drawable-xxhdpi/vector_notification_accept_invitation.png deleted file mode 100755 index eb2be25187..0000000000 Binary files a/libraries/push/impl/src/main/res/drawable-xxhdpi/vector_notification_accept_invitation.png and /dev/null differ diff --git a/libraries/push/impl/src/main/res/drawable-xxhdpi/vector_notification_quick_reply.png b/libraries/push/impl/src/main/res/drawable-xxhdpi/vector_notification_quick_reply.png deleted file mode 100755 index 4af4ae634b..0000000000 Binary files a/libraries/push/impl/src/main/res/drawable-xxhdpi/vector_notification_quick_reply.png and /dev/null differ diff --git a/libraries/push/impl/src/main/res/drawable-xxhdpi/vector_notification_reject_invitation.png b/libraries/push/impl/src/main/res/drawable-xxhdpi/vector_notification_reject_invitation.png deleted file mode 100755 index 51b4401ca0..0000000000 Binary files a/libraries/push/impl/src/main/res/drawable-xxhdpi/vector_notification_reject_invitation.png and /dev/null differ diff --git a/libraries/push/impl/src/main/res/raw/message.mp3 b/libraries/push/impl/src/main/res/raw/message.mp3 index 5e9645adaf..abc056786c 100644 Binary files a/libraries/push/impl/src/main/res/raw/message.mp3 and b/libraries/push/impl/src/main/res/raw/message.mp3 differ diff --git a/libraries/push/impl/src/main/res/values-be/translations.xml b/libraries/push/impl/src/main/res/values-be/translations.xml index 7a30be6f01..aec22f9910 100644 --- a/libraries/push/impl/src/main/res/values-be/translations.xml +++ b/libraries/push/impl/src/main/res/values-be/translations.xml @@ -59,6 +59,7 @@ "Фонавая сінхранізацыя" "Сэрвісы Google" "Службы Google Play не знойдзены. Апавяшчэнні могуць не працаваць належным чынам." + "Заблакіраваныя карыстальнікі" "Атрымаць назву бягучага пастаўшчыка." "Пастаўшчыкі push-апавяшчэнняў не выбраны." "Бягучы пастаўшчык push-апавяшчэнняў: %1$s." diff --git a/libraries/push/impl/src/main/res/values-cs/translations.xml b/libraries/push/impl/src/main/res/values-cs/translations.xml index 002c6d18ac..667f181536 100644 --- a/libraries/push/impl/src/main/res/values-cs/translations.xml +++ b/libraries/push/impl/src/main/res/values-cs/translations.xml @@ -15,6 +15,7 @@ "%d oznámení" "%d oznámení" + "Distributor oznámení UnifiedPush se nepodařilo zaregistrovat, takže již nebudete dostávat oznámení. Zkontrolujte nastavení oznámení v aplikaci a stav distributora push oznámění." "Oznámení" "📹 Příchozí hovor" "** Nepodařilo se odeslat - otevřete prosím místnost" @@ -42,6 +43,7 @@ "Já" "%1$s zmínil(a) nebo odpověděl(a)" "Prohlížíte si oznámení! Klikněte na mě!" + "Vlákno v %1$s" "%1$s: %2$s" "%1$s: %2$s %3$s" @@ -71,7 +73,10 @@ "Blokovaní uživatelé" "Získat název aktuálního poskytovatele." "Nebyli vybráni žádní push poskytovatelé." + "Aktuální poskytovatel push oznámení: %1$s a současný distributor: %2$s. Ale distributor %3$s nebyl nalezen. Možná byla aplikace odinstalována?" + "Aktuální poskytovatel push oznámení: %1$s , ale nebyli nakonfigurováni žádní distributoři." "Aktuální push poskytovatel: %1$s." + "Aktuální poskytovatel push oznámení: %1$s (%2$s)" "Aktuální push poskytovatel" "Ujistěte se, že aplikace má alespoň jednoho push poskytovatele." "Nebyli nalezeni žádní push poskytovatelé." diff --git a/libraries/push/impl/src/main/res/values-da/translations.xml b/libraries/push/impl/src/main/res/values-da/translations.xml index 255605953b..5f16a8cf8a 100644 --- a/libraries/push/impl/src/main/res/values-da/translations.xml +++ b/libraries/push/impl/src/main/res/values-da/translations.xml @@ -13,6 +13,7 @@ "%d notifikation" "%d notifikationer" + "UnifiedPush-push notification-distributøren kunne ikke registreres, så du vil ikke længere modtage notifikationer. Kontrollér appens notifikationsindstillinger og push-distributørens status." "Du har nye beskeder." "📹 Indgående opkald" "** Kunne ikke sende - åbn venligst rummet" @@ -38,6 +39,7 @@ "Mig" "%1$s nævnt eller besvaret" "Du ser notifikationen! Klik på mig!" + "Tråd i %1$s" "%1$s: %2$s" "%1$s: %2$s %3$s" diff --git a/libraries/push/impl/src/main/res/values-de/translations.xml b/libraries/push/impl/src/main/res/values-de/translations.xml index dff6371773..6997c9f160 100644 --- a/libraries/push/impl/src/main/res/values-de/translations.xml +++ b/libraries/push/impl/src/main/res/values-de/translations.xml @@ -38,6 +38,7 @@ "Ich" "%1$s hat Dich erwähnt oder geantwortet" "Du siehst dir die Benachrichtigung an! Klicke hier!" + "Thread in %1$s" "%1$s: %2$s" "%1$s: %2$s %3$s" @@ -61,7 +62,7 @@ "Du hast %1$d Nutzer gesperrt. Du wirst für diesen Nutzer keine Benachrichtigungen erhalten." "Du hast %1$d Nutzer gesperrt. Du wirst für diese Nutzer keine Benachrichtigungen erhalten." - "Gesperrte Nutzer" + "Blockierte Nutzer" "Ermittele den Namen des aktuellen Anbieters." "Kein Dienst für Push-Benachrichtigungen ausgewählt." "Aktueller Push-Dienst: %1$s und aktueller UnifiedPush-Distributor: %2$s. Aber der Distributor %3$s kann nicht gefunden werden. Vielleicht wurde die App deinstalliert?" diff --git a/libraries/push/impl/src/main/res/values-el/translations.xml b/libraries/push/impl/src/main/res/values-el/translations.xml index da21ccfce7..1229218f09 100644 --- a/libraries/push/impl/src/main/res/values-el/translations.xml +++ b/libraries/push/impl/src/main/res/values-el/translations.xml @@ -54,6 +54,7 @@ "Συγχρονισμός στο παρασκήνιο" "Υπηρεσίες Google" "Δεν βρέθηκαν έγκυρες υπηρεσίες Google Play. Οι ειδοποιήσεις ενδέχεται να μην λειτουργούν σωστά." + "Αποκλεισμένοι χρήστες" "Λάβε το όνομα του τρέχοντος παρόχου." "Δεν έχουν επιλεγεί πάροχοι push." "Τρέχων πάροχος push: %1$s." diff --git a/libraries/push/impl/src/main/res/values-en-rUS/translations.xml b/libraries/push/impl/src/main/res/values-en-rUS/translations.xml new file mode 100644 index 0000000000..95c732ea03 --- /dev/null +++ b/libraries/push/impl/src/main/res/values-en-rUS/translations.xml @@ -0,0 +1,4 @@ + + + "Background synchronization" + diff --git a/libraries/push/impl/src/main/res/values-es/translations.xml b/libraries/push/impl/src/main/res/values-es/translations.xml index 4200cb9baf..cee68d6072 100644 --- a/libraries/push/impl/src/main/res/values-es/translations.xml +++ b/libraries/push/impl/src/main/res/values-es/translations.xml @@ -53,6 +53,7 @@ "Sincronización en segundo plano" "Servicios de Google" "No se han encontrado Servicios de Google Play válidos. Es posible que las notificaciones no funcionen correctamente." + "Usuarios bloqueados" "Obtener el nombre del proveedor actual." "No se ha seleccionado ningún proveedor de push." "Proveedor de push actual: %1$s." diff --git a/libraries/push/impl/src/main/res/values-et/translations.xml b/libraries/push/impl/src/main/res/values-et/translations.xml index 45d8e3c8c8..980129d748 100644 --- a/libraries/push/impl/src/main/res/values-et/translations.xml +++ b/libraries/push/impl/src/main/res/values-et/translations.xml @@ -13,6 +13,7 @@ "%d teavitus" "%d teavitust" + "UnifiedPushi levitajas registreerimine ei õnnestunud ja seega sa ei saa enam teavitusi. Palun kontrolli selle rakenduse teavituste seadistusi ja tõuketeenuste levitaja olekut." "Sulle on uusi sõnumeid." "📹 Sissetulev kõne" "** Saatmine ei õnnestunud - palun ava jututoa täisvaade" @@ -37,7 +38,10 @@ "%1$s saatis sulle kutse jututoaga liitumiseks" "Mina" "%1$s mainis või vastas" + "Kutsus sind liituma kogukonnaga" + "%1$s kutsus sind liituma kogukonnaga" "See ongi teavitus! Klõpsi mind!" + "Jutulõng „%1$s“ jututoas" "%1$s: %2$s" "%1$s: %2$s %3$s" diff --git a/libraries/push/impl/src/main/res/values-eu/translations.xml b/libraries/push/impl/src/main/res/values-eu/translations.xml index c72badbb52..826b9a762f 100644 --- a/libraries/push/impl/src/main/res/values-eu/translations.xml +++ b/libraries/push/impl/src/main/res/values-eu/translations.xml @@ -53,6 +53,7 @@ "Atzeko planoko sinkronizazioa" "Google Services" "Ez da baliozko Google Play Servicerik aurkitu. Litekeena da jakinarazpenak behar bezala ez ibiltzea." + "Blokeatutako erabiltzaileak" "Lortu uneko hornitzailearen izena." "Ez da push hornitzailerik hautatu." "Uneko push hornitzailea: %1$s." diff --git a/libraries/push/impl/src/main/res/values-fa/translations.xml b/libraries/push/impl/src/main/res/values-fa/translations.xml index f36ff36e98..ef24f09c1e 100644 --- a/libraries/push/impl/src/main/res/values-fa/translations.xml +++ b/libraries/push/impl/src/main/res/values-fa/translations.xml @@ -54,6 +54,7 @@ "همگام سازی پس‌زمینه" "خدمات گوگل" "خدمت پلی گوگل معتبری پیدا نشد. ممکن است آگاهی‌ها به درستی کار نکنند." + "کاربران مسدود" "روی آگاهی کلیک شد!" "خطا: فرستنده درخواست را رد کرد." "خطا: %1$s." diff --git a/libraries/push/impl/src/main/res/values-fi/translations.xml b/libraries/push/impl/src/main/res/values-fi/translations.xml index f3cec970d5..1af69c2da9 100644 --- a/libraries/push/impl/src/main/res/values-fi/translations.xml +++ b/libraries/push/impl/src/main/res/values-fi/translations.xml @@ -13,6 +13,7 @@ "%d ilmoitus" "%d ilmoitusta" + "UnifiedPush ilmoitusten jakelijaa ei voitu rekisteröidä, joten et enää vastaanota ilmoituksia. Tarkista sovelluksen ilmoitusasetukset ja push-jakelijan tila." "Sinulle on uusia viestejä." "📹 Saapuva puhelu" "** Lähetys epäonnistui - avaa huone" diff --git a/libraries/push/impl/src/main/res/values-fr/translations.xml b/libraries/push/impl/src/main/res/values-fr/translations.xml index 5324597f8b..8927f9eccc 100644 --- a/libraries/push/impl/src/main/res/values-fr/translations.xml +++ b/libraries/push/impl/src/main/res/values-fr/translations.xml @@ -13,6 +13,7 @@ "%d notification" "%d notifications" + "Le distributeur de notifications UnifiedPush n’a pas pu être enregistré, vous ne recevrez donc plus de notifications. Veuillez vérifier les paramètres de notification de l’application et l’état du distributeur." "Vous avez de nouveau(x) message(s)." "📹 Appel entrant" "** Échec de l’envoi - veuillez ouvrir le salon" @@ -38,6 +39,7 @@ "Moi" "%1$s mentionné ou en réponse" "Vous êtes en train de voir la notification ! Cliquez-moi !" + "Discussion dans %1$s" "%1$s : %2$s" "%1$s : %2$s %3$s" diff --git a/libraries/push/impl/src/main/res/values-hr/translations.xml b/libraries/push/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..86c458dbf9 --- /dev/null +++ b/libraries/push/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,105 @@ + + + "Poziv" + "Osluškivanje događaja" + "Glasne obavijesti" + "Pozivi (telefon zvoni)" + "Tihe obavijesti" + + "%1$s: %2$d poruka" + "%1$s: %2$d poruke" + "%1$s: %2$d poruka" + + + "%d obavijest" + "%d obavijesti" + "%d obavijesti" + + "Distributer obavijesti UnifiedPush nije mogao biti registriran, tako da više nećete primati obavijesti. Provjerite postavke obavijesti u aplikaciji i status distributera push obavijesti." + "Imate nove poruke." + "📹 Dolazni poziv" + "** Slanje nije uspjelo – otvorite sobu" + "Pridruži se" + "Odbij" + + "%d pozivnica" + "%d pozivnice" + "%d pozivnica" + + "Pozvao/la te na razgovor" + "%1$s pozvao/la te na razgovor" + "Korisnik vas je spomenuo: %1$s" + "Nove poruke" + + "%d nova poruka" + "%d nove poruke" + "%d novih poruka" + + "Reagirao/la je s %1$s" + "Označi kao pročitano" + "Brzi odgovor" + "Pozvao/la te je da se pridružiš sobi" + "%1$s pozvao/la te da se pridružiš sobi" + "Ja" + "%1$s je spomenuo/la ili odgovorio/la" + "Pozvao/la vas je da se pridružite prostoru" + "%1$s pozvao/la vas je da se pridružite prostoru" + "Gledate obavijest! Kliknite me!" + "Nit u %1$s" + "%1$s: %2$s" + "%1$s: %2$s %3$s" + + "%d nepročitana poruka s obavijesti" + "%d nepročitane poruke s obavijesti" + "%d nepročitanih poruka s obavijesti" + + "%1$s i %2$s" + "%1$s u %2$s" + "%1$s u %2$s i %3$s" + + "%d soba" + "%d sobe" + "%d soba" + + "Sinkronizacija u pozadini" + "Googleove usluge" + "Nisu pronađene valjane usluge Google Play. Obavijesti možda neće ispravno funkcionirati." + "Provjera blokiranih korisnika" + "Prikaži blokirane korisnike" + "Nijedan korisnik nije blokiran." + + "Blokirali ste %1$d korisnika. Nećete primati obavijesti za ovog korisnika." + "Blokirali ste %1$d korisnika. Nećete primati obavijesti za te korisnike." + "Blokirali ste %1$d korisnika. Nećete primati obavijesti za te korisnike." + + "Blokirani korisnici" + "Dohvatite naziv trenutačnog pružatelja." + "Nije odabran nijedan pružatelj push obavijesti." + "Trenutačni pružatelj push obavijesti je %1$s i trenutačni distributer je %2$s. Ali distributer %3$s nije pronađen. Možda je aplikacija deinstalirana?" + "Trenutačni pružatelj push obavijesti je %1$s, ali nijedan distributer nije konfiguriran." + "Trenutačni pružatelj push obavijesti: %1$s ." + "Trenutačni pružatelj push obavijesti: %1$s (%2$s)" + "Trenutačni pružatelj push obavijesti" + "Provjerite podržava li aplikacija barem jednog pružatelja push obavijesti" + "Nije pronađena podrška za pružatelja push obavijesti." + + "Pronađen je %1$d pružatelj push obavijesti: %2$s" + "Pronađena su %1$d pružatelja push obavijesti: %2$s" + "Pronađeno je %1$d pružatelja push obavijesti: %2$s" + + "Aplikacija je razvijena s podrškom za: %1$s" + "Podrška za pružatelja push obavijesti" + "Provjerite može li aplikacija prikazivati ​​obavijesti." + "Obavijest nije kliknuta." + "Obavijest nije moguće prikazati." + "Obavijest je kliknuta!" + "Prikaz obavijesti" + "Kliknite na obavijest za nastavak testa." + "Provjerite prima li aplikacija push obavijesti." + "Pogreška: sustav za push obavijesti odbio je zahtjev." + "Pogreška: %1$s ." + "Pogreška, nije moguće testirati push obavijesti." + "Pogreška, isteklo je vrijeme čekanja na push obavijest." + "Povratna push obavijest trajala je %1$d ms." + "Testiraj povratnu push obavijest" + diff --git a/libraries/push/impl/src/main/res/values-hu/translations.xml b/libraries/push/impl/src/main/res/values-hu/translations.xml index fb067af5c4..69d4082871 100644 --- a/libraries/push/impl/src/main/res/values-hu/translations.xml +++ b/libraries/push/impl/src/main/res/values-hu/translations.xml @@ -13,6 +13,7 @@ "%d értesítés" "%d értesítés" + "A UnifiedPush leküldéses értesítési terjesztő nem regisztrálható, ezért többé nem fog értesítéseket kapni. Ellenőrizze az alkalmazás értesítési beállításait és a leküldés értesítési terjesztő állapotát." "Értesítés" "📹 Bejövő hívás" "** Nem sikerült elküldeni – nyissa meg a szobát" @@ -38,6 +39,7 @@ "Én" "%1$s megemlítette vagy válaszolt" "Az értesítést nézi! Kattintson ide!" + "Üzenetszál itt: %1$s" "%1$s: %2$s" "%1$s: %2$s %3$s" @@ -64,7 +66,10 @@ "Letiltott felhasználók" "A jelenlegi szolgáltató nevének lekérdezése." "Nincs kiválasztva leküldéses értesítési szolgáltató." + "Jelenlegi leküldéses értesítések szolgáltatója: %1$s és jelenlegi terjesztő: %2$s. De a terjesztő %3$s nem található. Lehet, hogy az alkalmazást eltávolították?" + "Jelenlegi leküldéses értesítések szolgáltatója: %1$s, de még nem konfiguráltak forgalmazókat." "Jelenlegi leküldéses értesítési szolgáltató: %1$s." + "Jelenlegi leküldéses értesítések szolgáltatója: %1$s (%2$s)" "Jelenlegi leküldéses értesítési szolgáltató" "Győződjön meg arról, hogy az alkalmazás legalább egy leküldéses értesítési szolgáltatóval rendelkezik." "Nem található leküldéses értesítési szolgáltató." diff --git a/libraries/push/impl/src/main/res/values-in/translations.xml b/libraries/push/impl/src/main/res/values-in/translations.xml index 760d507e08..4baa46d5a9 100644 --- a/libraries/push/impl/src/main/res/values-in/translations.xml +++ b/libraries/push/impl/src/main/res/values-in/translations.xml @@ -48,6 +48,7 @@ "Sinkronisasi latar belakang" "Layanan Google" "Tidak ditemukan Layanan Google Play yang valid. Pemberitahuan mungkin tidak berfungsi dengan baik." + "Pengguna yang diblokir" "Dapatkan nama penyedia saat ini." "Tidak ada penyedia notifikasi dorongan yang dipilih." "Penyedia notifikasi dorongan saat ini: %1$s." diff --git a/libraries/push/impl/src/main/res/values-it/translations.xml b/libraries/push/impl/src/main/res/values-it/translations.xml index 07a85e9f42..9e44c2d7f9 100644 --- a/libraries/push/impl/src/main/res/values-it/translations.xml +++ b/libraries/push/impl/src/main/res/values-it/translations.xml @@ -13,6 +13,7 @@ "%d notifica" "%d notifiche" + "Non è stato possibile registrare il distributore di notifiche UnifiedPush, quindi non riceverai più notifiche. Controlla le impostazioni delle notifiche dell\'app e lo stato del distributore push." "Hai nuovi messaggi." "📹 Chiamata in arrivo" "** Invio fallito - si prega di aprire la stanza" @@ -36,8 +37,9 @@ "Ti ha invitato ad entrare nella stanza" "%1$s ti ha invitato a unirti alla stanza" "Io" - "%1$s è stato menzionato o risposto" + "%1$s ti ha menzionato o risposto" "Stai visualizzando la notifica! Cliccami!" + "Discussione in %1$s" "%1$s: %2$s" "%1$s: %2$s %3$s" @@ -54,9 +56,20 @@ "Sincronizzazione in background" "Servizi Google" "Google Play Services non trovato. Le notifiche non funzioneranno bene." + "Controllo degli utenti bloccati" + "Visualizza gli utenti bloccati" + "Nessun utente è bloccato." + + "Hai bloccato%1$d utente. Non riceverai notifiche da questo utente." + "Hai bloccato%1$d utenti. Non riceverai notifiche da questi utenti." + + "Utenti bloccati" "Ottieni il nome del fornitore attuale." "Nessun provider push selezionato." + "Fornitore push attuale: %1$s e attuale distributore: %2$s Ma il distributore %3$s non viene trovato. Forse l\'applicazione è stata disinstallata?" + "Fornitore push attuale:%1$s , ma non è stato configurato alcun distributore." "Provider push attuale: %1$s." + "Fornitore push attuale: %1$s (%2$s )" "Provider push attuale" "Assicurati che l\'applicazione abbia almeno un fornitore push." "Nessun provider push trovato." diff --git a/libraries/push/impl/src/main/res/values-ka/translations.xml b/libraries/push/impl/src/main/res/values-ka/translations.xml index 3a0d626016..9cf2ddcab2 100644 --- a/libraries/push/impl/src/main/res/values-ka/translations.xml +++ b/libraries/push/impl/src/main/res/values-ka/translations.xml @@ -47,6 +47,7 @@ "ფონის სინქრონიზაცია" "Google სერვისები" "მოქმედი Google Play სერვისები ვერ მოიძებნა. შეტყობინებები შეიძლება ვერ იმუშაოს სწორად." + "დაბლოკილი მომხმარებლები" "მიმდინარე პროვაიდერის სახელის გაგება" "push პროვაიდერები არაა არჩეული." "მიმდინარე push პროვაიდერი: %1$s." diff --git a/libraries/push/impl/src/main/res/values-ko/translations.xml b/libraries/push/impl/src/main/res/values-ko/translations.xml index 9c2aa8c710..c65b6c3388 100644 --- a/libraries/push/impl/src/main/res/values-ko/translations.xml +++ b/libraries/push/impl/src/main/res/values-ko/translations.xml @@ -48,6 +48,7 @@ "백그라운드 동기화" "Google 서비스" "유효한 Google Play 서비스를 찾지 못했습니다. 알림이 정상적으로 동작하지 않을 수 있습니다." + "차단한 사용자" "현재 제공자의 이름을 가져옵니다." "푸시 제공자가 선택되지 않았습니다." "현재 푸시 제공자: %1$s." diff --git a/libraries/push/impl/src/main/res/values-nb/translations.xml b/libraries/push/impl/src/main/res/values-nb/translations.xml index 7f8cc41413..933b5b886b 100644 --- a/libraries/push/impl/src/main/res/values-nb/translations.xml +++ b/libraries/push/impl/src/main/res/values-nb/translations.xml @@ -38,6 +38,7 @@ "Meg" "%1$s nevnt eller besvart" "Du ser på varselet! Klikk på meg!" + "Tråd i %1$s" "%1$s: %2$s" "%1$s: %2$s %3$s" @@ -64,6 +65,7 @@ "Blokkerte brukere" "Få navnet på den nåværende tilbyderen." "Ingen push-leverandører er valgt." + "Nåværende push-leverandør: %1$s og nåværende distributør: %2$s. Men distributøren %3$s blir ikke funnet. Kanskje er applikasjonen avinstallert?" "Nåværende push-leverandør: %1$s, men ingen distributører er konfigurert." "Gjeldende push-leverandør: %1$s." "Nåværende push-leverandør: %1$s (%2$s)" diff --git a/libraries/push/impl/src/main/res/values-nl/translations.xml b/libraries/push/impl/src/main/res/values-nl/translations.xml index 833c6e7107..79244669e6 100644 --- a/libraries/push/impl/src/main/res/values-nl/translations.xml +++ b/libraries/push/impl/src/main/res/values-nl/translations.xml @@ -53,6 +53,7 @@ "Achtergrondsynchronisatie" "Google-services" "Geen geldige Google Play-services gevonden. Meldingen werken mogelijk niet goed." + "Geblokkeerde gebruikers" "Naam van de huidige provider aan het ophalen." "Er zijn geen push-providers geselecteerd." "Huidige push-provider: %1$s." diff --git a/libraries/push/impl/src/main/res/values-pl/translations.xml b/libraries/push/impl/src/main/res/values-pl/translations.xml index 1525997b12..b9ae289507 100644 --- a/libraries/push/impl/src/main/res/values-pl/translations.xml +++ b/libraries/push/impl/src/main/res/values-pl/translations.xml @@ -60,9 +60,21 @@ "Synchronizacja w tle" "Usługi Google" "Nie znaleziono usług Google Play. Powiadomienia mogą nie działać prawidłowo." + "Sprawdzam zablokowanych użytkowników" + "Wyświetl zablokowanych użytkowników" + "Żaden użytkownik nie jest zablokowany." + + "Zablokowano %1$d użytkownika. Nie otrzymasz od niego żadnych powiadomień." + "Zablokowano %1$d użytkowników. Nie otrzymasz od nich żadnych powiadomień." + "Zablokowano %1$d użytkowników. Nie otrzymasz od nich żadnych powiadomień." + + "Zablokowani użytkownicy" "Uzyskaj nazwę bieżącego dostawcy." "Nie wybrano dostawców push." + "Aktualny dostawca usług push: %1$s, dystrybutor: %2$s. Nie znaleziono dystrybutora %3$s. Sprawdź czy aplikacja nie została usunięta." + "Aktualny dostawca usługi push: %1$s, nie skonfigurowano żadnych dystrybutorów." "Bieżący dostawca push: %1$s." + "Aktualny dostawca usług push: %1$s (%2$s)" "Bieżący dostawca push" "Upewnij się, że aplikacja ma co najmniej jednego dostawcę push." "Nie znaleziono dostawców push." diff --git a/libraries/push/impl/src/main/res/values-pt-rBR/translations.xml b/libraries/push/impl/src/main/res/values-pt-rBR/translations.xml index 6d2e96d82c..0e01d67105 100644 --- a/libraries/push/impl/src/main/res/values-pt-rBR/translations.xml +++ b/libraries/push/impl/src/main/res/values-pt-rBR/translations.xml @@ -13,6 +13,7 @@ "%d notificação" "%d notificações" + "O distribuidor de notificações do UnifiedPush não pôde ser cadastrado, então você não receberá mais notificações. Confira as configurações do app e o estado do distribuidor de push." "Você tem mensagens novas." "📹 Chamada recebida" "** Falha ao enviar - por favor, abra a sala" @@ -37,7 +38,10 @@ "%1$s te convidou para entrar na sala" "Eu" "%1$s mencionado ou respondido" + "Te convidou para entrar no espaço" + "%1$s te convidou para entrar no espaço" "Você está visualizando a notificação! Clique em mim!" + "Tópico em %1$s" "%1$s: %2$s" "%1$s: %2$s %3$s" @@ -54,9 +58,20 @@ "Sincronização em segundo plano" "Serviços do Google" "O Google Play Services não foi encontrado. As notificações podem não funcionar corretamente." + "Verificando usuários bloqueados" + "Ver usuários bloqueados" + "Nenhum usuário está bloqueado." + + "Você bloqueou %1$d usuário. Você não receberá notificações deste usuário." + "Você bloqueou %1$d usuários. Você não receberá notificações deles." + + "Usuários bloqueados" "Obtenha o nome do provedor atual." "Nenhum provedor de push foi selecionado." + "Provedor de push atual: %1$s e distribuidor atual: %2$s. Mas o distribuidor %3$s não foi encontrado. Talvez o aplicativo foi desinstalado?" + "Provedor de push atual: %1$s, mas nenhum distribuidor foi configurado." "Provedor de push atual: %1$s." + "Provedor de push atual: %1$s (%2$s)" "Provedor de push atual" "Certifique-se de que o aplicativo tenha suporte a pelo menos um provedor de push." "Nenhum provedor de push com suporte foi encontrado." diff --git a/libraries/push/impl/src/main/res/values-ro/translations.xml b/libraries/push/impl/src/main/res/values-ro/translations.xml index 7a5bcf2f06..0a83e6afad 100644 --- a/libraries/push/impl/src/main/res/values-ro/translations.xml +++ b/libraries/push/impl/src/main/res/values-ro/translations.xml @@ -13,6 +13,7 @@ "%d notificare" "%d notificări" + "Distribuitorul de notificări UnifiedPush nu a putut fi înregistrat, așadar nu veți mai primi notificări. Verificați setările de notificări ale aplicației și starea distribuitorului push." "Aveți mesaje noi" "Apel primit" "** Trimiterea eșuată - vă rugăm să deschideți camera" @@ -37,7 +38,10 @@ "%1$s v-a invitat să vă alăturați camerei" "Eu" "%1$s v-a menționat sau răspuns" + "V-a invitat să vă alăturați spațiului" + "%1$s v-a invitat să vă alăturați spațiului" "Vizualizați o notificare! Faceți clic pe mine!" + "Fir în: %1$s" "%1$s: %2$s" "%1$s: %2$s %3$s" diff --git a/libraries/push/impl/src/main/res/values-ru/translations.xml b/libraries/push/impl/src/main/res/values-ru/translations.xml index 60a9d965f0..b2a79defce 100644 --- a/libraries/push/impl/src/main/res/values-ru/translations.xml +++ b/libraries/push/impl/src/main/res/values-ru/translations.xml @@ -15,6 +15,7 @@ "%d уведомления" "%d уведомлений" + "Не удалось зарегистрировать дистрибьютора уведомлений UnifiedPush, поэтому вы больше не будете получать уведомления. Проверьте настройки уведомлений в приложении и статус дистрибьютора push-уведомлений." "У вас есть новые сообщения." "📹 Входящий вызов" "** Не удалось отправить - пожалуйста, откройте комнату" @@ -42,6 +43,7 @@ "Я" "%1$s упомянул или ответил" "Вы просматриваете уведомление! Нажмите на меня!" + "Ветка обсуждения в %1$s" "%1$s: %2$s" "%1$s: %2$s %3$s" diff --git a/libraries/push/impl/src/main/res/values-sk/translations.xml b/libraries/push/impl/src/main/res/values-sk/translations.xml index 74ba29d78a..764dbefa70 100644 --- a/libraries/push/impl/src/main/res/values-sk/translations.xml +++ b/libraries/push/impl/src/main/res/values-sk/translations.xml @@ -15,6 +15,7 @@ "%d oznámenia" "%d oznámení" + "Distribútora oznámení UnifiedPush sa nepodarilo zaregistrovať, takže už nebudete dostávať oznámenia. Skontrolujte nastavenia oznámení v aplikácii a stav distribútora push oznámení." "Máte nové správy." "📹 Prichádzajúci hovor" "** Nepodarilo sa odoslať - prosím otvorte miestnosť" @@ -42,6 +43,7 @@ "Ja" "%1$s spomenul/a alebo odpovedal/a" "Prezeráte si oznámenie! Kliknite na mňa!" + "Vlákno v %1$s" "%1$s: %2$s" "%1$s: %2$s %3$s" @@ -60,9 +62,21 @@ "Synchronizácia na pozadí" "Služby Google" "Nenašli sa žiadne platné služby Google Play. Oznámenia nemusia fungovať správne." + "Kontrola blokovaných používateľov" + "Zobraziť blokovaných používateľov" + "Žiadni používatelia nie sú blokovaní." + + "Zablokovali ste %1$d používateľa. Nebudete dostávať oznámenia od tohto používateľa." + "Zablokovali ste %1$d používateľov. Nebudete dostávať oznámenia od týchto používateľov." + "Zablokovali ste %1$d používateľov. Nebudete dostávať oznámenia od týchto používateľov." + + "Blokovaní používatelia" "Získaťe názov aktuálneho poskytovateľa." "Nie sú vybraní žiadni poskytovatelia push." + "Aktuálny poskytovateľ push oznámení: %1$s a súčasný distribútor: %2$s. Ale distribútor %3$s sa nenašiel. Možno bola aplikácia odinštalovaná?" + "Aktuálny poskytovateľ push oznámení: %1$s, ale neboli nakonfigurovaní žiadni distribútori." "Aktuálny poskytovateľ push: %1$s." + "Aktuálny poskytovateľ push oznámení: %1$s (%2$s)" "Aktuálny poskytovateľ push" "Uistite sa, že aplikácia má aspoň jedného poskytovateľa push." "Nenašli sa žiadni poskytovatelia push." diff --git a/libraries/push/impl/src/main/res/values-sv/translations.xml b/libraries/push/impl/src/main/res/values-sv/translations.xml index aff078b2c0..9f978c253f 100644 --- a/libraries/push/impl/src/main/res/values-sv/translations.xml +++ b/libraries/push/impl/src/main/res/values-sv/translations.xml @@ -54,6 +54,7 @@ "Bakgrundssynkronisering" "Google-tjänster" "Inga giltiga Google Play-tjänster hittades. Aviseringar kanske inte fungerar korrekt." + "Blockerade användare" "Hämta namnet på den nuvarande leverantören." "Inga push-leverantörer valda." "Nuvarande push-leverantör: %1$s." diff --git a/libraries/push/impl/src/main/res/values-tr/translations.xml b/libraries/push/impl/src/main/res/values-tr/translations.xml index a3c415a210..558c206749 100644 --- a/libraries/push/impl/src/main/res/values-tr/translations.xml +++ b/libraries/push/impl/src/main/res/values-tr/translations.xml @@ -54,6 +54,7 @@ "Arkaplan senkronizasyonu" "Google Hizmetleri" "Geçerli bir Google Play Hizmeti bulunamadı. Bildirimler düzgün çalışmayabilir." + "Engellenen kullanıcılar" "Geçerli sağlayıcının adını al." "Hiçbir gönderme sağlayıcısı seçilmedi." "Geçerli gönderme sağlayıcısı: %1$s." diff --git a/libraries/push/impl/src/main/res/values-uk/translations.xml b/libraries/push/impl/src/main/res/values-uk/translations.xml index 1aa535b1e2..43ca0faaca 100644 --- a/libraries/push/impl/src/main/res/values-uk/translations.xml +++ b/libraries/push/impl/src/main/res/values-uk/translations.xml @@ -60,6 +60,7 @@ "Фонова синхронізація" "Сервіси Google" "Не знайдено дійсних сервісів Google Play. Сповіщення можуть не працювати належним чином." + "Заблоковані користувачі" "Отримує назву поточного постачальника." "Постачальників push-сповіщень не вибрано." "Поточний постачальник: %1$s." diff --git a/libraries/push/impl/src/main/res/values-ur/translations.xml b/libraries/push/impl/src/main/res/values-ur/translations.xml index 40a54ab313..8f358b1616 100644 --- a/libraries/push/impl/src/main/res/values-ur/translations.xml +++ b/libraries/push/impl/src/main/res/values-ur/translations.xml @@ -54,6 +54,7 @@ "پس منظر مطابقت پذیری" "گوگل سروسز" "کوئی درست گوگل پلے سروسز نہیں ملی۔ ہو سکتا ہے اطلاعات ٹھیک سے کام نہ کریں۔" + "مسدود صارفین" "موجودہ فراہم کنندہ کا نام حاصل کریں۔" "کوئی دھکا فراہم کنندہ منتخب نہیں کیا گیا" "موجودہ دھکا فراہم کنندہ: %1$s۔" diff --git a/libraries/push/impl/src/main/res/values-uz/translations.xml b/libraries/push/impl/src/main/res/values-uz/translations.xml index 4a9e4bffbc..e967b47d34 100644 --- a/libraries/push/impl/src/main/res/values-uz/translations.xml +++ b/libraries/push/impl/src/main/res/values-uz/translations.xml @@ -54,6 +54,7 @@ "Orqa Fon sinxronizatsiyasi" "Google xizmatlari" "Yaroqli Google Play xizmatlari topilmadi. Bildirishnomalar to\'g\'ri ishlamasligi mumkin." + "Bloklangan foydalanuvchilar" "Joriy provayder nomini oling." "Hech qanday push-provayder tanlanmagan." "Joriy push provider: %1$s." @@ -64,6 +65,7 @@ "Topildi %1$d push provider: %2$s" "Topildi %1$d push provayderlar: %2$s" + "Ilova quyidagilar uchun yaratilgan: %1$s" "Provayderni qoʻllab-quvvatlash" "Ilova bildirishnomani koʻrsata olishini tekshiring." "Bildirishnoma bosilmagan." @@ -76,4 +78,5 @@ "Xato: %1$s." "Xatolik, push qilishni sinab bo‘lmadi." "Xatolik, taym aut pushni kutmoqda." + "Test Push loop back" diff --git a/libraries/push/impl/src/main/res/values-zh-rTW/translations.xml b/libraries/push/impl/src/main/res/values-zh-rTW/translations.xml index 91a73c678f..2d289e9a9e 100644 --- a/libraries/push/impl/src/main/res/values-zh-rTW/translations.xml +++ b/libraries/push/impl/src/main/res/values-zh-rTW/translations.xml @@ -11,6 +11,7 @@ "%d 個通知" + "Unified Push 通知散佈程式註冊失敗,因此您無法再收到通知。請檢查應用程式的通知設定與推播散佈程式的狀態。" "您有新訊息。" "📹 來電" "** 無法傳送,請開啟聊天室" @@ -34,6 +35,7 @@ "我" "%1$s 提及或回覆" "您正在查看通知!點我!" + "在 %1$s 的討論串" "%1$s:%2$s" "%1$s:%2$s %3$s" @@ -48,9 +50,19 @@ "背景同步" "Google 服務" "找不到有效的 Google Play 服務。通知可能無法正常運作。" + "檢查被封鎖的使用者" + "檢視被封鎖的使用者" + "無被封鎖的使用者。" + + "您已封鎖 %1$d 個使用者。您將不會收到來自這些使用者的通知。" + + "封鎖的使用者" "取得目前提供者的名稱。" "未選取推播提供者。" + "目前的推播提供者 %1$s 與目前的散佈者 %2$s。但找不到散佈者 %3$s。可能已解除安裝應用程式?" + "目前的推播提供者:%1$s,但尚未設定散佈者。" "目前的推播提供者:%1$s。" + "目前的推播提供者:%1$s (%2$s)" "目前的推播提供者" "確保應用程式至少有一個推播提供者。" "找不到推播提供者。" diff --git a/libraries/push/impl/src/main/res/values-zh/translations.xml b/libraries/push/impl/src/main/res/values-zh/translations.xml index 2fe8d3e4c7..3f2bd7e836 100644 --- a/libraries/push/impl/src/main/res/values-zh/translations.xml +++ b/libraries/push/impl/src/main/res/values-zh/translations.xml @@ -34,6 +34,7 @@ "我" "%1$s提及或回复" "您正在查看通知!点击我!" + "线程 %1$s" "%1$s:%2$s" "%1$s: %2$s %3$s" @@ -48,12 +49,19 @@ "后台同步" "谷歌服务" "找不到有效的 Google Play 服务。通知可能无法正常工作。" + "检查被阻止的用户" + "查看被屏蔽的用户" + "没有用户被阻止。" "您已屏蔽 %1$d 位用户。您将不再收到这些用户的推送通知。" + "已屏蔽用户" "获取当前推送提供者的名称。" "未选择任何推送提供者。" + "当前推送提供商:%1$s和当前分销商:%2$s . 但经销商%3$s未找到。应用程序可能已被卸载?" + "当前推送提供商:%1$s ,但尚未配置分销商。" "当前推送提供者:%1$s。" + "当前推送提供商:%1$s (%2$s )" "当前推送提供者" "确保应用程序至少有一个推送提供者。" "未找到推送提供者。" diff --git a/libraries/push/impl/src/main/res/values/localazy.xml b/libraries/push/impl/src/main/res/values/localazy.xml index 45a3235ad5..aac5c6c72b 100644 --- a/libraries/push/impl/src/main/res/values/localazy.xml +++ b/libraries/push/impl/src/main/res/values/localazy.xml @@ -13,6 +13,7 @@ "%d notification" "%d notifications" + "The UnifiedPush notification distributor couldn\'t be registered, so you will not receive notifications anymore. Please check the notifications settings of the app and the status of the push distributor." "You have new messages." "📹 Incoming call" "** Failed to send - please open room" @@ -37,7 +38,10 @@ "%1$s invited you to join the room" "Me" "%1$s mentioned or replied" + "Invited you to join the space" + "%1$s invited you to join the space" "You are viewing the notification! Click me!" + "Thread in %1$s" "%1$s: %2$s" "%1$s: %2$s %3$s" @@ -51,7 +55,7 @@ "%d room" "%d rooms" - "Background synchronization" + "Background synchronisation" "Google Services" "No valid Google Play Services found. Notifications may not work properly." "Checking blocked users" diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/DefaultPushServiceTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/DefaultPushServiceTest.kt index 5ae8ab261e..f48b7ee612 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/DefaultPushServiceTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/DefaultPushServiceTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,12 +12,15 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService import io.element.android.libraries.push.api.GetCurrentPushProvider +import io.element.android.libraries.push.api.PusherRegistrationFailure import io.element.android.libraries.push.api.history.PushHistoryItem import io.element.android.libraries.push.impl.push.FakeMutableBatteryOptimizationStore import io.element.android.libraries.push.impl.push.MutableBatteryOptimizationStore @@ -24,12 +28,14 @@ import io.element.android.libraries.push.impl.store.InMemoryPushDataStore import io.element.android.libraries.push.impl.store.PushDataStore import io.element.android.libraries.push.impl.test.FakeTestPush import io.element.android.libraries.push.impl.test.TestPush +import io.element.android.libraries.push.impl.unregistration.FakeServiceUnregisteredHandler +import io.element.android.libraries.push.impl.unregistration.ServiceUnregisteredHandler import io.element.android.libraries.push.test.FakeGetCurrentPushProvider -import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig +import io.element.android.libraries.pushproviders.api.Config import io.element.android.libraries.pushproviders.api.Distributor import io.element.android.libraries.pushproviders.api.PushProvider import io.element.android.libraries.pushproviders.test.FakePushProvider -import io.element.android.libraries.pushproviders.test.aCurrentUserPushConfig +import io.element.android.libraries.pushproviders.test.aSessionPushConfig import io.element.android.libraries.pushstore.api.UserPushStoreFactory import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecretStore import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStore @@ -37,6 +43,7 @@ import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushSto import io.element.android.libraries.pushstore.test.userpushstore.clientsecret.InMemoryPushClientSecretStore import io.element.android.libraries.sessionstorage.api.observer.SessionObserver import io.element.android.libraries.sessionstorage.test.observer.NoOpSessionObserver +import io.element.android.tests.testutils.lambda.any import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import kotlinx.coroutines.flow.first @@ -47,7 +54,7 @@ class DefaultPushServiceTest { @Test fun `test push no push provider`() = runTest { val defaultPushService = createDefaultPushService() - assertThat(defaultPushService.testPush()).isFalse() + assertThat(defaultPushService.testPush(A_SESSION_ID)).isFalse() } @Test @@ -57,22 +64,22 @@ class DefaultPushServiceTest { pushProviders = setOf(aPushProvider), getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider = aPushProvider.name), ) - assertThat(defaultPushService.testPush()).isFalse() + assertThat(defaultPushService.testPush(A_SESSION_ID)).isFalse() } @Test fun `test push ok`() = runTest { - val aConfig = aCurrentUserPushConfig() - val testPushResult = lambdaRecorder { } + val aConfig = aSessionPushConfig() + val testPushResult = lambdaRecorder { } val aPushProvider = FakePushProvider( - currentUserPushConfig = aConfig + config = aConfig ) val defaultPushService = createDefaultPushService( pushProviders = setOf(aPushProvider), getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider = aPushProvider.name), testPush = FakeTestPush(executeResult = testPushResult), ) - assertThat(defaultPushService.testPush()).isTrue() + assertThat(defaultPushService.testPush(A_SESSION_ID)).isTrue() testPushResult.assertions() .isCalledOnce() .with(value(aConfig)) @@ -81,7 +88,7 @@ class DefaultPushServiceTest { @Test fun `getCurrentPushProvider null`() = runTest { val defaultPushService = createDefaultPushService() - val result = defaultPushService.getCurrentPushProvider() + val result = defaultPushService.getCurrentPushProvider(A_SESSION_ID) assertThat(result).isNull() } @@ -92,7 +99,7 @@ class DefaultPushServiceTest { pushProviders = setOf(aPushProvider), getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider = aPushProvider.name), ) - val result = defaultPushService.getCurrentPushProvider() + val result = defaultPushService.getCurrentPushProvider(A_SESSION_ID) assertThat(result).isEqualTo(aPushProvider) } @@ -248,7 +255,7 @@ class DefaultPushServiceTest { ), pushClientSecretStore = pushClientSecretStore, ) - defaultPushService.onSessionDeleted(A_SESSION_ID.value) + defaultPushService.onSessionDeleted(A_SESSION_ID.value, false) assertThat(userPushStore.getPushProviderName()).isNull() assertThat(pushClientSecretStore.getSecret(A_SESSION_ID)).isNull() onSessionDeletedLambda.assertions().isCalledOnce().with(value(A_SESSION_ID)) @@ -268,7 +275,7 @@ class DefaultPushServiceTest { ), pushClientSecretStore = pushClientSecretStore, ) - defaultPushService.onSessionDeleted(A_SESSION_ID.value) + defaultPushService.onSessionDeleted(A_SESSION_ID.value, false) assertThat(userPushStore.getPushProviderName()).isNull() assertThat(pushClientSecretStore.getSecret(A_SESSION_ID)).isNull() } @@ -336,6 +343,281 @@ class DefaultPushServiceTest { } } + @Test + fun `ensurePusher - error when account is not verified`() = runTest { + val sessionVerificationService = FakeSessionVerificationService( + initialSessionVerifiedStatus = SessionVerifiedStatus.NotVerified + ) + val pushService = createDefaultPushService() + val result = pushService.ensurePusherIsRegistered( + FakeMatrixClient( + sessionVerificationService = sessionVerificationService, + ) + ) + assertThat(result.exceptionOrNull()!!).isInstanceOf(PusherRegistrationFailure.AccountNotVerified::class.java) + } + + @Test + fun `ensurePusher - case two push providers but first one does not have distributor - second one will be used`() = runTest { + val lambda = lambdaRecorder> { _, _ -> + Result.success(Unit) + } + val sessionVerificationService = FakeSessionVerificationService( + initialSessionVerifiedStatus = SessionVerifiedStatus.Verified + ) + val pushProvider0 = FakePushProvider( + index = 0, + name = "aFakePushProvider0", + distributors = emptyList(), + ) + val distributor = Distributor("aDistributorValue1", "aDistributorName1") + val pushProvider1 = FakePushProvider( + index = 1, + name = "aFakePushProvider1", + distributors = listOf(distributor), + registerWithResult = lambda, + ) + val pushService = createDefaultPushService( + pushProviders = setOf( + pushProvider0, + pushProvider1, + ), + ) + val result = pushService.ensurePusherIsRegistered( + FakeMatrixClient( + sessionVerificationService = sessionVerificationService, + ) + ) + assertThat(result.isSuccess).isTrue() + lambda.assertions().isCalledOnce() + .with( + // MatrixClient + any(), + // First distributor of second push provider + value(distributor), + ) + } + + @Test + fun `ensurePusher - case one push provider but no distributor available`() = runTest { + val lambda = lambdaRecorder> { _, _ -> + Result.success(Unit) + } + val sessionVerificationService = FakeSessionVerificationService( + initialSessionVerifiedStatus = SessionVerifiedStatus.Verified + ) + val pushProvider = FakePushProvider( + index = 0, + name = "aFakePushProvider", + distributors = emptyList(), + registerWithResult = lambda, + ) + val pushService = createDefaultPushService( + pushProviders = setOf(pushProvider), + ) + val result = pushService.ensurePusherIsRegistered( + FakeMatrixClient( + sessionVerificationService = sessionVerificationService, + ) + ) + assertThat(result.exceptionOrNull()).isInstanceOf(PusherRegistrationFailure.NoDistributorsAvailable::class.java) + lambda.assertions().isNeverCalled() + } + + @Test + fun `ensurePusher - ensure default pusher is registered with default provider`() = runTest { + val lambda = lambdaRecorder> { _, _ -> + Result.success(Unit) + } + val sessionVerificationService = FakeSessionVerificationService( + initialSessionVerifiedStatus = SessionVerifiedStatus.Verified + ) + val pushService = createDefaultPushService( + pushProviders = setOf( + FakePushProvider( + index = 0, + name = "aFakePushProvider", + distributors = listOf(Distributor("aDistributorValue0", "aDistributorName0")), + registerWithResult = lambda, + ) + ), + ) + val result = pushService.ensurePusherIsRegistered( + FakeMatrixClient( + sessionVerificationService = sessionVerificationService, + ) + ) + assertThat(result.isSuccess).isTrue() + lambda.assertions() + .isCalledOnce() + .with( + // MatrixClient + any(), + // First distributor + value(pushService.getAvailablePushProviders()[0].getDistributors()[0]), + ) + } + + @Test + fun `ensurePusher - ensure default pusher is registered with default provider - fail to register`() = runTest { + val lambda = lambdaRecorder> { _, _ -> + Result.failure(AN_EXCEPTION) + } + val sessionVerificationService = FakeSessionVerificationService( + initialSessionVerifiedStatus = SessionVerifiedStatus.Verified + ) + val pushService = createDefaultPushService( + pushProviders = setOf( + FakePushProvider( + index = 0, + name = "aFakePushProvider", + distributors = listOf(Distributor("aDistributorValue0", "aDistributorName0")), + registerWithResult = lambda, + ) + ), + ) + val result = pushService.ensurePusherIsRegistered( + FakeMatrixClient( + sessionVerificationService = sessionVerificationService, + ) + ) + assertThat(result.isFailure).isTrue() + lambda.assertions() + .isCalledOnce() + .with( + // MatrixClient + any(), + // First distributor + value(pushService.getAvailablePushProviders()[0].getDistributors()[0]), + ) + } + + @Test + fun `ensurePusher - if current push provider does not have distributors, nothing happen`() = runTest { + val lambda = lambdaRecorder> { _, _ -> + Result.success(Unit) + } + val sessionVerificationService = FakeSessionVerificationService( + initialSessionVerifiedStatus = SessionVerifiedStatus.Verified + ) + val pushProvider = FakePushProvider( + index = 0, + name = "aFakePushProvider0", + distributors = emptyList(), + registerWithResult = lambda, + ) + val pushService = createDefaultPushService( + pushProviders = setOf(pushProvider), + getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider = pushProvider.name), + ) + val result = pushService.ensurePusherIsRegistered( + FakeMatrixClient( + sessionVerificationService = sessionVerificationService, + ) + ) + assertThat(result.exceptionOrNull()) + .isInstanceOf(PusherRegistrationFailure.NoDistributorsAvailable::class.java) + lambda.assertions() + .isNeverCalled() + } + + @Test + fun `ensurePusher - ensure current provider is registered with current distributor`() = runTest { + val lambda = lambdaRecorder> { _, _ -> + Result.success(Unit) + } + val sessionVerificationService = FakeSessionVerificationService( + initialSessionVerifiedStatus = SessionVerifiedStatus.Verified + ) + val distributor = Distributor("aDistributorValue1", "aDistributorName1") + val pushProvider = FakePushProvider( + index = 0, + name = "aFakePushProvider0", + distributors = listOf( + Distributor("aDistributorValue0", "aDistributorName0"), + distributor, + ), + currentDistributor = { distributor }, + registerWithResult = lambda, + ) + val pushService = createDefaultPushService( + pushProviders = setOf(pushProvider), + getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider = pushProvider.name), + ) + val result = pushService.ensurePusherIsRegistered( + FakeMatrixClient( + sessionVerificationService = sessionVerificationService, + ) + ) + assertThat(result.isSuccess).isTrue() + lambda.assertions() + .isCalledOnce() + .with( + // MatrixClient + any(), + // Current distributor + value(distributor), + ) + } + + @Test + fun `ensurePusher - case no push provider available provider`() = runTest { + val lambda = lambdaRecorder> { _, _ -> + Result.success(Unit) + } + val sessionVerificationService = FakeSessionVerificationService(SessionVerifiedStatus.Verified) + val pushService = createDefaultPushService( + pushProviders = emptySet(), + ) + val result = pushService.ensurePusherIsRegistered( + FakeMatrixClient( + sessionVerificationService = sessionVerificationService, + ) + ) + assertThat(result.exceptionOrNull()) + .isInstanceOf(PusherRegistrationFailure.NoProvidersAvailable::class.java) + lambda.assertions() + .isNeverCalled() + } + + @Test + fun `ensurePusher - if current push provider does not have current distributor, the first one is used`() = runTest { + val lambda = lambdaRecorder> { _, _ -> + Result.success(Unit) + } + val sessionVerificationService = FakeSessionVerificationService( + initialSessionVerifiedStatus = SessionVerifiedStatus.Verified + ) + val pushProvider = FakePushProvider( + index = 0, + name = "aFakePushProvider0", + distributors = listOf( + Distributor("aDistributorValue0", "aDistributorName0"), + Distributor("aDistributorValue1", "aDistributorName1"), + ), + currentDistributor = { null }, + registerWithResult = lambda, + ) + val pushService = createDefaultPushService( + pushProviders = setOf(pushProvider), + getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider = pushProvider.name), + ) + val result = pushService.ensurePusherIsRegistered( + FakeMatrixClient( + sessionVerificationService = sessionVerificationService, + ) + ) + assertThat(result.isSuccess).isTrue() + lambda.assertions() + .isCalledOnce() + .with( + // MatrixClient + any(), + // First distributor + value(pushService.getAvailablePushProviders()[0].getDistributors()[0]), + ) + } + private fun createDefaultPushService( testPush: TestPush = FakeTestPush(), userPushStoreFactory: UserPushStoreFactory = FakeUserPushStoreFactory(), @@ -345,6 +627,7 @@ class DefaultPushServiceTest { pushClientSecretStore: PushClientSecretStore = InMemoryPushClientSecretStore(), pushDataStore: PushDataStore = InMemoryPushDataStore(), mutableBatteryOptimizationStore: MutableBatteryOptimizationStore = FakeMutableBatteryOptimizationStore(), + serviceUnregisteredHandler: ServiceUnregisteredHandler = FakeServiceUnregisteredHandler(), ): DefaultPushService { return DefaultPushService( testPush = testPush, @@ -355,6 +638,7 @@ class DefaultPushServiceTest { pushClientSecretStore = pushClientSecretStore, pushDataStore = pushDataStore, mutableBatteryOptimizationStore = mutableBatteryOptimizationStore, + serviceUnregisteredHandler = serviceUnregisteredHandler, ) } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/DefaultPusherSubscriberTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/DefaultPusherSubscriberTest.kt index 63ffdef7f0..dd2cb24841 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/DefaultPusherSubscriberTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/DefaultPusherSubscriberTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/AndroidBatteryOptimizationTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/AndroidBatteryOptimizationTest.kt index e3a28550ca..dd1dd7b74d 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/AndroidBatteryOptimizationTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/AndroidBatteryOptimizationTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenterTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenterTest.kt index d5dd7c48d4..d3b2c8da2b 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenterTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/BatteryOptimizationPresenterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/FakeBatteryOptimization.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/FakeBatteryOptimization.kt index 0adb3d2520..ae3bfed638 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/FakeBatteryOptimization.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/battery/FakeBatteryOptimization.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/history/FakePushHistoryService.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/history/FakePushHistoryService.kt index 50a06756dc..aac8f8f26d 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/history/FakePushHistoryService.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/history/FakePushHistoryService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultActiveNotificationsProviderTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultActiveNotificationsProviderTest.kt index 0f65047a14..573ec37471 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultActiveNotificationsProviderTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultActiveNotificationsProviderTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,6 +17,7 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_ID_2 import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_SESSION_ID_2 +import io.element.android.libraries.matrix.test.A_THREAD_ID import io.element.android.libraries.push.api.notifications.NotificationIdProvider import io.mockk.every import io.mockk.mockk @@ -43,7 +45,7 @@ class DefaultActiveNotificationsProviderTest { @Test fun `getMembershipNotificationsForSession returns only membership notifications for that session id`() { val activeNotifications = listOf( - aStatusBarNotification(id = notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID), groupId = A_SESSION_ID.value,), + aStatusBarNotification(id = notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID), groupId = A_SESSION_ID.value), aStatusBarNotification(id = notificationIdProvider.getSummaryNotificationId(A_SESSION_ID_2), groupId = A_SESSION_ID_2.value), aStatusBarNotification( id = notificationIdProvider.getRoomInvitationNotificationId(A_SESSION_ID_2), @@ -80,8 +82,35 @@ class DefaultActiveNotificationsProviderTest { ) val activeNotificationsProvider = createActiveNotificationsProvider(activeNotifications = activeNotifications) - assertThat(activeNotificationsProvider.getMessageNotificationsForRoom(A_SESSION_ID, A_ROOM_ID)).hasSize(1) - assertThat(activeNotificationsProvider.getMessageNotificationsForRoom(A_SESSION_ID_2, A_ROOM_ID_2)).isEmpty() + assertThat(activeNotificationsProvider.getMessageNotificationsForRoom(A_SESSION_ID, A_ROOM_ID, null)).hasSize(1) + assertThat(activeNotificationsProvider.getMessageNotificationsForRoom(A_SESSION_ID_2, A_ROOM_ID_2, null)).isEmpty() + } + + @Test + fun `getMessageNotificationsForRoom with thread id returns only message notifications for a thread using those session and room ids`() { + val activeNotifications = listOf( + aStatusBarNotification( + id = notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID), + groupId = A_SESSION_ID.value, + tag = "$A_ROOM_ID|$A_THREAD_ID", + ), + aStatusBarNotification(id = notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), groupId = A_SESSION_ID.value, tag = A_ROOM_ID.value), + aStatusBarNotification( + id = notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID_2), + groupId = A_SESSION_ID_2.value, + tag = "$A_ROOM_ID|$A_THREAD_ID", + ), + aStatusBarNotification(id = notificationIdProvider.getSummaryNotificationId(A_SESSION_ID_2), groupId = A_SESSION_ID_2.value, tag = A_ROOM_ID.value), + aStatusBarNotification( + id = notificationIdProvider.getRoomInvitationNotificationId(A_SESSION_ID_2), + groupId = A_SESSION_ID_2.value, + tag = "$A_ROOM_ID|$A_THREAD_ID", + ), + ) + val activeNotificationsProvider = createActiveNotificationsProvider(activeNotifications = activeNotifications) + + assertThat(activeNotificationsProvider.getMessageNotificationsForRoom(A_SESSION_ID, A_ROOM_ID, A_THREAD_ID)).hasSize(1) + assertThat(activeNotificationsProvider.getMessageNotificationsForRoom(A_SESSION_ID_2, A_ROOM_ID_2, A_THREAD_ID)).isEmpty() } @Test diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultBaseRoomGroupMessageCreatorTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultBaseRoomGroupMessageCreatorTest.kt index ba4e1657a7..542608254a 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultBaseRoomGroupMessageCreatorTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultBaseRoomGroupMessageCreatorTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,11 +19,13 @@ import io.element.android.libraries.matrix.test.A_TIMESTAMP import io.element.android.libraries.matrix.ui.components.aMatrixUser import io.element.android.libraries.matrix.ui.media.AVATAR_THUMBNAIL_SIZE_IN_PIXEL import io.element.android.libraries.matrix.ui.media.MediaRequestData +import io.element.android.libraries.matrix.ui.media.test.FakeImageLoader +import io.element.android.libraries.matrix.ui.media.test.FakeInitialsAvatarBitmapGenerator import io.element.android.libraries.push.impl.notifications.factories.MARK_AS_READ_ACTION_TITLE import io.element.android.libraries.push.impl.notifications.factories.QUICK_REPLY_ACTION_TITLE +import io.element.android.libraries.push.impl.notifications.factories.aNotificationAccountParams import io.element.android.libraries.push.impl.notifications.factories.createNotificationCreator import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent -import io.element.android.libraries.push.test.notifications.FakeImageLoader import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider import io.element.android.services.toolbox.impl.strings.AndroidStringProvider import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider @@ -43,21 +46,22 @@ class DefaultBaseRoomGroupMessageCreatorTest { val sut = createRoomGroupMessageCreator() val fakeImageLoader = FakeImageLoader() val result = sut.createRoomMessage( - currentUser = aMatrixUser(), + notificationAccountParams = aNotificationAccountParams(), events = listOf( aNotifiableMessageEvent(timestamp = A_TIMESTAMP).copy( imageUriString = "aUri", ) ), roomId = A_ROOM_ID, - imageLoader = fakeImageLoader.getImageLoader(), + imageLoader = fakeImageLoader, existingNotification = null, + threadId = null, ) assertThat(result.number).isEqualTo(1) @Suppress("DEPRECATION") assertThat(result.priority).isEqualTo(NotificationCompat.PRIORITY_LOW) assertThat(result.`when`).isEqualTo(A_TIMESTAMP) - assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) + assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty() } @Test @@ -65,19 +69,20 @@ class DefaultBaseRoomGroupMessageCreatorTest { val sut = createRoomGroupMessageCreator() val fakeImageLoader = FakeImageLoader() val result = sut.createRoomMessage( - currentUser = aMatrixUser(), + notificationAccountParams = aNotificationAccountParams(), events = listOf( aNotifiableMessageEvent(timestamp = A_TIMESTAMP).copy( noisy = true, ) ), roomId = A_ROOM_ID, - imageLoader = fakeImageLoader.getImageLoader(), + imageLoader = fakeImageLoader, existingNotification = null, + threadId = null, ) @Suppress("DEPRECATION") assertThat(result.priority).isEqualTo(NotificationCompat.PRIORITY_DEFAULT) - assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) + assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty() } @Test @@ -125,9 +130,11 @@ class DefaultBaseRoomGroupMessageCreatorTest { sdkIntProvider = FakeBuildVersionSdkIntProvider(api) ) val result = sut.createRoomMessage( - currentUser = aMatrixUser( - // Some user avatar - avatarUrl = A_USER_AVATAR_1, + notificationAccountParams = aNotificationAccountParams( + user = aMatrixUser( + // Some user avatar + avatarUrl = A_USER_AVATAR_1, + ) ), events = listOf( aNotifiableMessageEvent(timestamp = A_TIMESTAMP).copy( @@ -136,11 +143,12 @@ class DefaultBaseRoomGroupMessageCreatorTest { ) ), roomId = A_ROOM_ID, - imageLoader = fakeImageLoader.getImageLoader(), + imageLoader = fakeImageLoader, existingNotification = null, + threadId = null, ) assertThat(result.number).isEqualTo(1) - assertThat(fakeImageLoader.getCoilRequests()).containsExactlyElementsIn(expectedCoilRequests) + assertThat(fakeImageLoader.getExecutedRequestsData()).containsExactlyElementsIn(expectedCoilRequests) } @Test @@ -148,14 +156,15 @@ class DefaultBaseRoomGroupMessageCreatorTest { val sut = createRoomGroupMessageCreator() val fakeImageLoader = FakeImageLoader() val result = sut.createRoomMessage( - currentUser = aMatrixUser(), + notificationAccountParams = aNotificationAccountParams(), events = listOf( aNotifiableMessageEvent(timestamp = A_TIMESTAMP), aNotifiableMessageEvent(timestamp = A_TIMESTAMP + 10), ), roomId = A_ROOM_ID, - imageLoader = fakeImageLoader.getImageLoader(), + imageLoader = fakeImageLoader, existingNotification = null, + threadId = null, ) assertThat(result.number).isEqualTo(2) assertThat(result.`when`).isEqualTo(A_TIMESTAMP + 10) @@ -166,7 +175,7 @@ class DefaultBaseRoomGroupMessageCreatorTest { QUICK_REPLY_ACTION_TITLE.takeIf { NotificationConfig.SHOW_QUICK_REPLY_ACTION }, ) ) - assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) + assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty() } @Test @@ -174,7 +183,7 @@ class DefaultBaseRoomGroupMessageCreatorTest { val sut = createRoomGroupMessageCreator() val fakeImageLoader = FakeImageLoader() val result = sut.createRoomMessage( - currentUser = aMatrixUser(), + notificationAccountParams = aNotificationAccountParams(), events = listOf( aNotifiableMessageEvent(timestamp = A_TIMESTAMP).copy( outGoingMessage = true, @@ -182,8 +191,9 @@ class DefaultBaseRoomGroupMessageCreatorTest { ), ), roomId = A_ROOM_ID, - imageLoader = fakeImageLoader.getImageLoader(), + imageLoader = fakeImageLoader, existingNotification = null, + threadId = null, ) val actionTitles = result.actions?.map { it.title } assertThat(actionTitles).isEqualTo( @@ -191,7 +201,7 @@ class DefaultBaseRoomGroupMessageCreatorTest { MARK_AS_READ_ACTION_TITLE.takeIf { NotificationConfig.SHOW_MARK_AS_READ_ACTION } ) ) - assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) + assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty() } @Test @@ -199,19 +209,20 @@ class DefaultBaseRoomGroupMessageCreatorTest { val sut = createRoomGroupMessageCreator() val fakeImageLoader = FakeImageLoader() val result = sut.createRoomMessage( - currentUser = aMatrixUser(), + notificationAccountParams = aNotificationAccountParams(), events = listOf( aNotifiableMessageEvent(timestamp = A_TIMESTAMP).copy( roomIsDm = true, ), ), roomId = A_ROOM_ID, - imageLoader = fakeImageLoader.getImageLoader(), + imageLoader = fakeImageLoader, existingNotification = null, + threadId = null, ) assertThat(result.number).isEqualTo(1) assertThat(result.`when`).isEqualTo(A_TIMESTAMP) - assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) + assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty() } } @@ -222,6 +233,7 @@ fun createRoomGroupMessageCreator( val bitmapLoader = DefaultNotificationBitmapLoader( context = RuntimeEnvironment.getApplication(), sdkIntProvider = sdkIntProvider, + initialsAvatarBitmapGenerator = FakeInitialsAvatarBitmapGenerator(), ) return DefaultRoomGroupMessageCreator( notificationCreator = createNotificationCreator(bitmapLoader = bitmapLoader), diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultCallNotificationEventResolverTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultCallNotificationEventResolverTest.kt index f9097752f1..f406bccbb4 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultCallNotificationEventResolverTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultCallNotificationEventResolverTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt index 25e6b92b16..58f97e55cb 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,6 +10,7 @@ package io.element.android.libraries.push.impl.notifications import android.content.Context import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.exception.NotificationResolverException import io.element.android.libraries.matrix.api.media.MediaSource @@ -36,6 +38,7 @@ import io.element.android.libraries.matrix.test.A_REDACTION_REASON import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_NAME import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.A_SPACE_NAME import io.element.android.libraries.matrix.test.A_TIMESTAMP import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.A_USER_NAME_2 @@ -44,6 +47,7 @@ import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.notification.FakeNotificationService import io.element.android.libraries.matrix.test.notification.aNotificationData import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser +import io.element.android.libraries.push.api.push.NotificationEventRequest import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationMediaRepo import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent @@ -471,6 +475,45 @@ class DefaultNotifiableEventResolverTest { assertThat(result.getEvent(request)).isEqualTo(Result.success(expectedResult)) } + @Test + fun `resolve invite space`() = runTest { + val sut = createDefaultNotifiableEventResolver( + notificationResult = Result.success( + mapOf( + AN_EVENT_ID to Result.success(aNotificationData( + content = NotificationContent.Invite( + senderId = A_USER_ID_2, + ), + roomDisplayName = A_SPACE_NAME, + isDirect = false, + isSpace = true, + )) + ) + ) + ) + val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, "firebase") + val result = sut.resolveEvents(A_SESSION_ID, listOf(request)) + val expectedResult = ResolvedPushEvent.Event( + InviteNotifiableEvent( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + eventId = AN_EVENT_ID, + editedEventId = null, + canBeReplaced = true, + roomName = A_SPACE_NAME, + noisy = false, + title = null, + description = "Bob invited you to join the space", + type = null, + timestamp = A_TIMESTAMP, + soundName = null, + isRedacted = false, + isUpdated = false, + ) + ) + assertThat(result.getEvent(request)).isEqualTo(Result.success(expectedResult)) + } + @Test fun `resolve invite direct`() = runTest { val sut = createDefaultNotifiableEventResolver( @@ -853,7 +896,8 @@ class DefaultNotifiableEventResolverTest { fallbackNotificationFactory = FallbackNotificationFactory( clock = FakeSystemClock(), stringProvider = FakeStringProvider(defaultResult = "You have new messages.") - ) + ), + featureFlagService = FakeFeatureFlagService(), ) } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt index 07702602e8..58bb86e683 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt @@ -1,16 +1,18 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.notifications import android.app.Notification -import androidx.core.app.NotificationManagerCompat +import androidx.compose.ui.graphics.Color import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.matrix.test.AN_AVATAR_URL +import io.element.android.features.enterprise.api.EnterpriseService +import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID @@ -19,13 +21,17 @@ import io.element.android.libraries.matrix.test.A_THREAD_ID import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.ui.components.aMatrixUser +import io.element.android.libraries.matrix.ui.media.test.FakeImageLoaderHolder import io.element.android.libraries.push.api.notifications.NotificationIdProvider +import io.element.android.libraries.push.impl.notifications.factories.aNotificationAccountParams import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator +import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent -import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder +import io.element.android.libraries.sessionstorage.api.SessionStore +import io.element.android.libraries.sessionstorage.test.InMemorySessionStore import io.element.android.services.appnavstate.api.AppNavigationState import io.element.android.services.appnavstate.api.AppNavigationStateService import io.element.android.services.appnavstate.api.NavigationState @@ -37,25 +43,19 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import io.mockk.every import io.mockk.mockk -import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.RuntimeEnvironment @OptIn(ExperimentalCoroutinesApi::class) -@RunWith(RobolectricTestRunner::class) class DefaultNotificationDrawerManagerTest { @Test fun `clearAllEvents should have no effect when queue is empty`() = runTest { val defaultNotificationDrawerManager = createDefaultNotificationDrawerManager() defaultNotificationDrawerManager.clearAllEvents(A_SESSION_ID) - defaultNotificationDrawerManager.destroy() } @Test @@ -63,8 +63,8 @@ class DefaultNotificationDrawerManagerTest { // For now just call all the API. Later, add more valuable tests. val matrixUser = aMatrixUser(id = A_SESSION_ID.value, displayName = "alice", avatarUrl = "mxc://data") val mockRoomGroupMessageCreator = FakeRoomGroupMessageCreator( - createRoomMessageResult = lambdaRecorder { user, _, roomId, _, existingNotification -> - assertThat(user).isEqualTo(matrixUser) + createRoomMessageResult = lambdaRecorder { notificationAccountParams, _, roomId, _, _, existingNotification -> + assertThat(notificationAccountParams.user).isEqualTo(matrixUser) assertThat(roomId).isEqualTo(A_ROOM_ID) assertThat(existingNotification).isNull() Notification() @@ -87,7 +87,6 @@ class DefaultNotificationDrawerManagerTest { defaultNotificationDrawerManager.onNotifiableEventReceived(aNotifiableMessageEvent()) // Add the same Event again (will be ignored) defaultNotificationDrawerManager.onNotifiableEventReceived(aNotifiableMessageEvent()) - defaultNotificationDrawerManager.destroy() } @Test @@ -100,7 +99,7 @@ class DefaultNotificationDrawerManagerTest { ) ) val appNavigationStateService = FakeAppNavigationStateService(appNavigationState = appNavigationStateFlow) - val defaultNotificationDrawerManager = createDefaultNotificationDrawerManager( + createDefaultNotificationDrawerManager( appNavigationStateService = appNavigationStateService ) appNavigationStateFlow.emit(AppNavigationState(aNavigationState(), isInForeground = true)) @@ -116,17 +115,22 @@ class DefaultNotificationDrawerManagerTest { // Like a user sign out appNavigationStateFlow.emit(AppNavigationState(aNavigationState(), isInForeground = true)) runCurrent() - defaultNotificationDrawerManager.destroy() } @Test - fun `when MatrixClient has no cached user name a fallback one is used to render the notification`() = runTest { - val matrixClient = FakeMatrixClient(userDisplayName = null) + fun `when MatrixClient has no cached user name and avatar, the profile is loaded to render the notification`() = runTest { + val matrixClient = FakeMatrixClient( + userDisplayName = null, + userAvatarUrl = null, + ) val matrixClientProvider = FakeMatrixClientProvider(getClient = { Result.success(matrixClient) }) val messageCreator = FakeRoomGroupMessageCreator() val defaultNotificationDrawerManager = createDefaultNotificationDrawerManager( matrixClientProvider = matrixClientProvider, roomGroupMessageCreator = messageCreator, + enterpriseService = FakeEnterpriseService( + initialBrandColor = Color.Red, + ) ) // Gets a display name from MatrixClient.getUserProfile matrixClient.givenGetProfileResult(A_SESSION_ID, Result.success(aMatrixUser(id = A_SESSION_ID.value, displayName = "alice"))) @@ -143,20 +147,41 @@ class DefaultNotificationDrawerManagerTest { messageCreator.createRoomMessageResult.assertions() .isCalledExactly(3) .withSequence( - listOf(value(aMatrixUser(id = A_SESSION_ID.value, displayName = "alice")), any(), any(), any(), any()), - listOf(value(aMatrixUser(id = A_SESSION_ID.value, displayName = A_SESSION_ID.value)), any(), any(), any(), any()), - listOf(value(aMatrixUser(id = A_SESSION_ID.value, displayName = A_SESSION_ID.value, avatarUrl = AN_AVATAR_URL)), any(), any(), any(), any()), + listOf( + value(aNotificationAccountParams(user = aMatrixUser(id = A_SESSION_ID.value, displayName = "alice"))), + any(), + any(), + any(), + any(), + any(), + ), + listOf( + value(aNotificationAccountParams(user = aMatrixUser(id = A_SESSION_ID.value, displayName = ""))), + any(), + any(), + any(), + any(), + any(), + ), + listOf( + value(aNotificationAccountParams(user = aMatrixUser(id = A_SESSION_ID.value, displayName = null, avatarUrl = null))), + any(), + any(), + any(), + any(), + any(), + ), ) - - defaultNotificationDrawerManager.destroy() } @Test fun `clearSummaryNotificationIfNeeded will run after clearing all other notifications`() = runTest { - val notificationManager = mockk { - every { cancel(any(), any()) } returns Unit - } + val cancelNotificationResult = lambdaRecorder { _, _ -> } + val notificationDisplayer = FakeNotificationDisplayer( + cancelNotificationResult = cancelNotificationResult, + ) val summaryId = NotificationIdProvider.getSummaryNotificationId(A_SESSION_ID) + val roomMessageId = NotificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID) val activeNotificationsProvider = FakeActiveNotificationsProvider( getSummaryNotificationResult = { mockk { @@ -166,7 +191,7 @@ class DefaultNotificationDrawerManagerTest { countResult = { 1 }, ) val defaultNotificationDrawerManager = createDefaultNotificationDrawerManager( - notificationManager = notificationManager, + notificationDisplayer = notificationDisplayer, activeNotificationsProvider = activeNotificationsProvider, ) @@ -174,24 +199,26 @@ class DefaultNotificationDrawerManagerTest { defaultNotificationDrawerManager.clearAllMessagesEvents(A_SESSION_ID) // Verify we asked to cancel the notification with summaryId - verify { notificationManager.cancel(null, summaryId) } - - defaultNotificationDrawerManager.destroy() + cancelNotificationResult.assertions().isCalledExactly(2).withSequence( + listOf(value(null), value(roomMessageId)), + listOf(value(null), value(summaryId)), + ) } private fun TestScope.createDefaultNotificationDrawerManager( - notificationManager: NotificationManagerCompat = NotificationManagerCompat.from(RuntimeEnvironment.getApplication()), + notificationDisplayer: NotificationDisplayer = FakeNotificationDisplayer(), appNavigationStateService: AppNavigationStateService = FakeAppNavigationStateService(), roomGroupMessageCreator: RoomGroupMessageCreator = FakeRoomGroupMessageCreator(), summaryGroupMessageCreator: SummaryGroupMessageCreator = FakeSummaryGroupMessageCreator(), activeNotificationsProvider: FakeActiveNotificationsProvider = FakeActiveNotificationsProvider(), matrixClientProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(), + sessionStore: SessionStore = InMemorySessionStore(), + enterpriseService: EnterpriseService = FakeEnterpriseService(), ): DefaultNotificationDrawerManager { - val context = RuntimeEnvironment.getApplication() return DefaultNotificationDrawerManager( - notificationManager = notificationManager, + notificationDisplayer = notificationDisplayer, notificationRenderer = NotificationRenderer( - notificationDisplayer = DefaultNotificationDisplayer(context, NotificationManagerCompat.from(context)), + notificationDisplayer = FakeNotificationDisplayer(), notificationDataFactory = DefaultNotificationDataFactory( notificationCreator = FakeNotificationCreator(), roomGroupMessageCreator = roomGroupMessageCreator, @@ -199,9 +226,11 @@ class DefaultNotificationDrawerManagerTest { activeNotificationsProvider = activeNotificationsProvider, stringProvider = FakeStringProvider(), ), + enterpriseService = enterpriseService, + sessionStore = sessionStore, ), appNavigationStateService = appNavigationStateService, - coroutineScope = this, + coroutineScope = backgroundScope, matrixClientProvider = matrixClientProvider, imageLoaderHolder = FakeImageLoaderHolder(), activeNotificationsProvider = activeNotificationsProvider, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandlerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandlerTest.kt index 25e00f7977..1a47200e12 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandlerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultOnMissedCallNotificationHandlerTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,23 +16,19 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.notification.FakeNotificationService import io.element.android.libraries.matrix.test.notification.aNotificationData +import io.element.android.libraries.matrix.ui.media.test.FakeImageLoaderHolder import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDataFactory import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.push.test.notifications.FakeCallNotificationEventResolver -import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder import io.element.android.services.appnavstate.test.FakeAppNavigationStateService import io.element.android.tests.testutils.lambda.lambdaRecorder -import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -@RunWith(RobolectricTestRunner::class) class DefaultOnMissedCallNotificationHandlerTest { @OptIn(ExperimentalCoroutinesApi::class) @Test @@ -51,9 +48,8 @@ class DefaultOnMissedCallNotificationHandlerTest { val defaultOnMissedCallNotificationHandler = DefaultOnMissedCallNotificationHandler( matrixClientProvider = matrixClientProvider, defaultNotificationDrawerManager = DefaultNotificationDrawerManager( - notificationManager = mockk(relaxed = true), - notificationRenderer = NotificationRenderer( - notificationDisplayer = FakeNotificationDisplayer(), + notificationDisplayer = FakeNotificationDisplayer(), + notificationRenderer = createNotificationRenderer( notificationDataFactory = dataFactory, ), appNavigationStateService = FakeAppNavigationStateService(), diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultSummaryGroupMessageCreatorTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultSummaryGroupMessageCreatorTest.kt index aae742fa6d..ba17fd0683 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultSummaryGroupMessageCreatorTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultSummaryGroupMessageCreatorTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,7 @@ import android.app.Notification import androidx.core.app.NotificationCompat import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.test.A_ROOM_ID -import io.element.android.libraries.matrix.ui.components.aMatrixUser +import io.element.android.libraries.push.impl.notifications.factories.aNotificationAccountParams import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator import io.element.android.services.toolbox.test.strings.FakeStringProvider import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP @@ -33,7 +34,7 @@ class DefaultSummaryGroupMessageCreatorTest { ) val result = summaryCreator.createSummaryNotification( - currentUser = aMatrixUser(), + notificationAccountParams = aNotificationAccountParams(), roomNotifications = listOf( RoomNotification( notification = Notification(), @@ -42,6 +43,7 @@ class DefaultSummaryGroupMessageCreatorTest { messageCount = 1, latestTimestamp = A_FAKE_TIMESTAMP + 10, shouldBing = true, + threadId = null, ) ), invitationNotifications = emptyList(), @@ -51,7 +53,7 @@ class DefaultSummaryGroupMessageCreatorTest { notificationCreator.createSummaryListNotificationResult.assertions() .isCalledOnce() - .with(any(), nonNull(), any(), any()) + .with(any(), any(), nonNull(), any(), any()) // Set from the events included @Suppress("DEPRECATION") diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/FakeNotifiableEventResolver.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/FakeNotifiableEventResolver.kt index d38bc098a0..17ba7448ec 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/FakeNotifiableEventResolver.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/FakeNotifiableEventResolver.kt @@ -1,13 +1,15 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.notifications import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.push.api.push.NotificationEventRequest import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent import io.element.android.tests.testutils.lambda.lambdaError diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/FakeReplyMessageExtractor.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/FakeReplyMessageExtractor.kt index 010da2d1a4..c0098a3947 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/FakeReplyMessageExtractor.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/FakeReplyMessageExtractor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt index 7a6f6b0118..a52eb16b07 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -39,6 +40,7 @@ import io.element.android.libraries.push.impl.push.FakeOnNotifiableEventReceived import io.element.android.libraries.push.impl.push.OnNotifiableEventReceived import io.element.android.libraries.push.test.notifications.FakeNotificationCleaner import io.element.android.services.appnavstate.api.ActiveRoomsHolder +import io.element.android.services.appnavstate.impl.DefaultActiveRoomsHolder import io.element.android.services.toolbox.api.strings.StringProvider import io.element.android.services.toolbox.api.systemclock.SystemClock import io.element.android.services.toolbox.test.strings.FakeStringProvider @@ -222,10 +224,11 @@ class NotificationBroadcastReceiverHandlerTest { ) val clearMessagesForRoomLambda = lambdaRecorder { _, _ -> } val markAsReadResult = lambdaRecorder> { Result.success(Unit) } + val timeline = FakeTimeline(markAsReadResult = markAsReadResult) val joinedRoom = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - markAsReadResult = markAsReadResult, - ), + baseRoom = FakeBaseRoom(), + liveTimeline = timeline, + createTimelineResult = { Result.success(timeline) }, ) val fakeNotificationCleaner = FakeNotificationCleaner( clearMessagesForRoomLambda = clearMessagesForRoomLambda, @@ -480,7 +483,7 @@ class NotificationBroadcastReceiverHandlerTest { onNotifiableEventReceived: OnNotifiableEventReceived = FakeOnNotifiableEventReceived(), stringProvider: StringProvider = FakeStringProvider(), replyMessageExtractor: ReplyMessageExtractor = FakeReplyMessageExtractor(), - activeRoomsHolder: ActiveRoomsHolder = ActiveRoomsHolder(), + activeRoomsHolder: ActiveRoomsHolder = DefaultActiveRoomsHolder(), ): NotificationBroadcastReceiverHandler { return NotificationBroadcastReceiverHandler( appCoroutineScope = this, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactoryTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactoryTest.kt index 30c433a513..7d9d1e6550 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactoryTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDataFactoryTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2021-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2021-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,6 +14,8 @@ import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.ui.media.test.FakeImageLoader +import io.element.android.libraries.push.impl.notifications.factories.aNotificationAccountParams import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator @@ -20,7 +23,6 @@ import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGrou import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent -import io.element.android.libraries.push.test.notifications.FakeImageLoader import io.element.android.services.toolbox.test.strings.FakeStringProvider import kotlinx.coroutines.test.runTest import org.junit.Test @@ -50,16 +52,18 @@ class NotificationDataFactoryTest { @Test fun `given a room invitation when mapping to notification then it's added`() = testWith(notificationDataFactory) { - val expectedNotification = notificationCreator.createRoomInvitationNotificationResult(AN_INVITATION_EVENT) + val expectedNotification = notificationCreator.createRoomInvitationNotificationResult( + aNotificationAccountParams(), + AN_INVITATION_EVENT, + ) val roomInvitation = listOf(AN_INVITATION_EVENT) - - val result = toNotifications(roomInvitation) + val result = toNotifications(roomInvitation, aNotificationAccountParams()) assertThat(result).isEqualTo( listOf( OneShotNotification( notification = expectedNotification, - key = A_ROOM_ID.value, + tag = A_ROOM_ID.value, summaryLine = AN_INVITATION_EVENT.description, isNoisy = AN_INVITATION_EVENT.noisy, timestamp = AN_INVITATION_EVENT.timestamp @@ -70,20 +74,18 @@ class NotificationDataFactoryTest { @Test fun `given a simple event when mapping to notification then it's added`() = testWith(notificationDataFactory) { - val expectedNotification = notificationCreator.createRoomInvitationNotificationResult(AN_INVITATION_EVENT) - val roomInvitation = listOf(A_SIMPLE_EVENT) - - val result = toNotifications(roomInvitation) - - assertThat(result).isEqualTo( - listOf( - OneShotNotification( - notification = expectedNotification, - key = AN_EVENT_ID.value, - summaryLine = A_SIMPLE_EVENT.description, - isNoisy = A_SIMPLE_EVENT.noisy, - timestamp = AN_INVITATION_EVENT.timestamp - ) + val expectedNotification = notificationCreator.createRoomInvitationNotificationResult( + aNotificationAccountParams(), + AN_INVITATION_EVENT, + ) + val result = toNotifications(listOf(A_SIMPLE_EVENT), aNotificationAccountParams()) + assertThat(result).containsExactly( + OneShotNotification( + notification = expectedNotification, + tag = AN_EVENT_ID.value, + summaryLine = A_SIMPLE_EVENT.description, + isNoisy = A_SIMPLE_EVENT.noisy, + timestamp = AN_INVITATION_EVENT.timestamp ) ) } @@ -93,45 +95,49 @@ class NotificationDataFactoryTest { val events = listOf(A_MESSAGE_EVENT) val expectedNotification = RoomNotification( notification = fakeRoomGroupMessageCreator.createRoomMessage( - MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), - events, - A_ROOM_ID, - FakeImageLoader().getImageLoader(), - null, + notificationAccountParams = aNotificationAccountParams( + user = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), + ), + events = events, + roomId = A_ROOM_ID, + threadId = null, + imageLoader = FakeImageLoader(), + existingNotification = null, ), roomId = A_ROOM_ID, summaryLine = "A room name: Bob Hello world!", messageCount = events.size, latestTimestamp = events.maxOf { it.timestamp }, - shouldBing = events.any { it.noisy } + shouldBing = events.any { it.noisy }, + threadId = null, ) - val roomWithMessage = listOf(A_MESSAGE_EVENT) - val fakeImageLoader = FakeImageLoader() val result = toNotifications( - messages = roomWithMessage, - currentUser = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), - imageLoader = fakeImageLoader.getImageLoader(), + messages = listOf(A_MESSAGE_EVENT), + notificationAccountParams = aNotificationAccountParams( + user = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), + ), + imageLoader = fakeImageLoader, ) assertThat(result.size).isEqualTo(1) assertThat(result.first().isDataEqualTo(expectedNotification)).isTrue() - assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) + assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty() } @Test fun `given a room with only redacted events when mapping to notification then is Empty`() = testWith(notificationDataFactory) { - val redactedRoom = listOf(A_MESSAGE_EVENT.copy(isRedacted = true)) - + val redactedRoom = A_MESSAGE_EVENT.copy(isRedacted = true) val fakeImageLoader = FakeImageLoader() val result = toNotifications( - messages = redactedRoom, - currentUser = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), - imageLoader = fakeImageLoader.getImageLoader(), + messages = listOf(redactedRoom), + notificationAccountParams = aNotificationAccountParams( + user = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), + ), + imageLoader = fakeImageLoader, ) - assertThat(result).isEmpty() - assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) + assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty() } @Test @@ -145,29 +151,35 @@ class NotificationDataFactoryTest { val withRedactedRemoved = listOf(A_MESSAGE_EVENT.copy(eventId = EventId("\$not-redacted"))) val expectedNotification = RoomNotification( notification = fakeRoomGroupMessageCreator.createRoomMessage( - MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), - withRedactedRemoved, - A_ROOM_ID, - FakeImageLoader().getImageLoader(), - null, + notificationAccountParams = aNotificationAccountParams( + user = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), + ), + events = withRedactedRemoved, + roomId = A_ROOM_ID, + threadId = null, + imageLoader = FakeImageLoader(), + existingNotification = null, ), roomId = A_ROOM_ID, summaryLine = "A room name: Bob Hello world!", messageCount = withRedactedRemoved.size, latestTimestamp = withRedactedRemoved.maxOf { it.timestamp }, - shouldBing = withRedactedRemoved.any { it.noisy } + shouldBing = withRedactedRemoved.any { it.noisy }, + threadId = null, ) val fakeImageLoader = FakeImageLoader() val result = toNotifications( messages = roomWithRedactedMessage, - currentUser = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), - imageLoader = fakeImageLoader.getImageLoader(), + notificationAccountParams = aNotificationAccountParams( + user = MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), + ), + imageLoader = fakeImageLoader, ) assertThat(result.size).isEqualTo(1) assertThat(result.first().isDataEqualTo(expectedNotification)).isTrue() - assertThat(fakeImageLoader.getCoilRequests().size).isEqualTo(0) + assertThat(fakeImageLoader.getExecutedRequestsData()).isEmpty() } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationIdProviderTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationIdProviderTest.kt index 60d5dad2ac..bf9b06fa6e 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationIdProviderTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationIdProviderTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt index 8912693bc4..51d491f444 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt @@ -1,19 +1,24 @@ /* - * Copyright 2021-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2021-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.notifications +import io.element.android.features.enterprise.api.EnterpriseService +import io.element.android.features.enterprise.test.FakeEnterpriseService import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.ui.media.test.FakeImageLoader import io.element.android.libraries.push.api.notifications.NotificationIdProvider import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator +import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDataFactory import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator @@ -22,7 +27,8 @@ import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiable import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent -import io.element.android.libraries.push.test.notifications.FakeImageLoader +import io.element.android.libraries.sessionstorage.api.SessionStore +import io.element.android.libraries.sessionstorage.test.InMemorySessionStore import io.element.android.services.toolbox.test.strings.FakeStringProvider import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value @@ -37,7 +43,7 @@ private const val USE_COMPLETE_NOTIFICATION_FORMAT = true private val A_SUMMARY_NOTIFICATION = SummaryNotification.Update(A_NOTIFICATION) private val ONE_SHOT_NOTIFICATION = - OneShotNotification(notification = A_NOTIFICATION, key = "ignored", summaryLine = "ignored", isNoisy = false, timestamp = -1) + OneShotNotification(notification = A_NOTIFICATION, tag = "ignored", summaryLine = "ignored", isNoisy = false, timestamp = -1) @RunWith(RobolectricTestRunner::class) class NotificationRendererTest { @@ -55,7 +61,7 @@ class NotificationRendererTest { ) private val notificationIdProvider = NotificationIdProvider - private val notificationRenderer = NotificationRenderer( + private val notificationRenderer = createNotificationRenderer( notificationDisplayer = notificationDisplayer, notificationDataFactory = notificationDataFactory, ) @@ -69,11 +75,11 @@ class NotificationRendererTest { @Test fun `given a room message group notification is added when rendering then show the message notification and update summary`() = runTest { - roomGroupMessageCreator.createRoomMessageResult = lambdaRecorder { _, _, _, _, _ -> A_NOTIFICATION } + roomGroupMessageCreator.createRoomMessageResult = lambdaRecorder { _, _, _, _, _, _ -> A_NOTIFICATION } renderEventsAsNotifications(listOf(aNotifiableMessageEvent())) - notificationDisplayer.showNotificationMessageResult.assertions().isCalledExactly(2).withSequence( + notificationDisplayer.showNotificationResult.assertions().isCalledExactly(2).withSequence( listOf(value(A_ROOM_ID.value), value(notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID)), value(A_NOTIFICATION)), listOf(value(null), value(notificationIdProvider.getSummaryNotificationId(A_SESSION_ID)), value(A_SUMMARY_NOTIFICATION.notification)) ) @@ -81,11 +87,11 @@ class NotificationRendererTest { @Test fun `given a simple notification is added when rendering then show the simple notification and update summary`() = runTest { - notificationCreator.createSimpleNotificationResult = lambdaRecorder { _ -> ONE_SHOT_NOTIFICATION.copy(key = AN_EVENT_ID.value).notification } + notificationCreator.createSimpleNotificationResult = lambdaRecorder { _, _ -> ONE_SHOT_NOTIFICATION.copy(tag = AN_EVENT_ID.value).notification } renderEventsAsNotifications(listOf(aSimpleNotifiableEvent(eventId = AN_EVENT_ID))) - notificationDisplayer.showNotificationMessageResult.assertions().isCalledExactly(2).withSequence( + notificationDisplayer.showNotificationResult.assertions().isCalledExactly(2).withSequence( listOf(value(AN_EVENT_ID.value), value(notificationIdProvider.getRoomEventNotificationId(A_SESSION_ID)), value(A_NOTIFICATION)), listOf(value(null), value(notificationIdProvider.getSummaryNotificationId(A_SESSION_ID)), value(A_SUMMARY_NOTIFICATION.notification)) ) @@ -93,11 +99,11 @@ class NotificationRendererTest { @Test fun `given an invitation notification is added when rendering then show the invitation notification and update summary`() = runTest { - notificationCreator.createRoomInvitationNotificationResult = lambdaRecorder { _ -> ONE_SHOT_NOTIFICATION.copy(key = AN_EVENT_ID.value).notification } + notificationCreator.createRoomInvitationNotificationResult = lambdaRecorder { _, _ -> ONE_SHOT_NOTIFICATION.copy(tag = AN_EVENT_ID.value).notification } renderEventsAsNotifications(listOf(anInviteNotifiableEvent())) - notificationDisplayer.showNotificationMessageResult.assertions().isCalledExactly(2).withSequence( + notificationDisplayer.showNotificationResult.assertions().isCalledExactly(2).withSequence( listOf(value(A_ROOM_ID.value), value(notificationIdProvider.getRoomInvitationNotificationId(A_SESSION_ID)), value(A_NOTIFICATION)), listOf(value(null), value(notificationIdProvider.getSummaryNotificationId(A_SESSION_ID)), value(A_SUMMARY_NOTIFICATION.notification)) ) @@ -108,7 +114,19 @@ class NotificationRendererTest { MatrixUser(A_SESSION_ID, MY_USER_DISPLAY_NAME, MY_USER_AVATAR_URL), useCompleteNotificationFormat = USE_COMPLETE_NOTIFICATION_FORMAT, eventsToProcess = events, - imageLoader = FakeImageLoader().getImageLoader(), + imageLoader = FakeImageLoader(), ) } } + +fun createNotificationRenderer( + notificationDisplayer: NotificationDisplayer = FakeNotificationDisplayer(), + notificationDataFactory: NotificationDataFactory = FakeNotificationDataFactory(), + enterpriseService: EnterpriseService = FakeEnterpriseService(), + sessionStore: SessionStore = InMemorySessionStore(), +) = NotificationRenderer( + notificationDisplayer = notificationDisplayer, + notificationDataFactory = notificationDataFactory, + enterpriseService = enterpriseService, + sessionStore = sessionStore, +) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/channels/FakeNotificationChannels.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/channels/FakeNotificationChannels.kt index 89ce153f10..6caf02b41b 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/channels/FakeNotificationChannels.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/channels/FakeNotificationChannels.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/channels/NotificationChannelsTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/channels/NotificationChannelsTest.kt index b93bbcaf15..e6b28831bd 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/channels/NotificationChannelsTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/channels/NotificationChannelsTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationServiceTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationServiceTest.kt index d05966f9a6..d4d7713c2d 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationServiceTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/conversations/DefaultNotificationConversationServiceTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -20,9 +21,9 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID_2 import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_SESSION_ID_2 import io.element.android.libraries.matrix.test.FakeMatrixClientProvider +import io.element.android.libraries.matrix.ui.media.test.FakeImageLoaderHolder import io.element.android.libraries.push.impl.notifications.factories.FakeIntentProvider import io.element.android.libraries.push.impl.notifications.shortcut.createShortcutId -import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader import io.element.android.libraries.sessionstorage.test.observer.FakeSessionObserver import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt index 7786fb261e..0504ae433b 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,11 +17,14 @@ import com.google.common.truth.Truth.assertThat import io.element.android.appconfig.NotificationConfig import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.A_COLOR_INT import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_THREAD_ID import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.ui.components.aMatrixUser +import io.element.android.libraries.matrix.ui.media.test.FakeImageLoader +import io.element.android.libraries.matrix.ui.media.test.FakeInitialsAvatarBitmapGenerator import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader import io.element.android.libraries.push.impl.notifications.DefaultNotificationBitmapLoader import io.element.android.libraries.push.impl.notifications.NotificationActionIds @@ -31,10 +35,10 @@ import io.element.android.libraries.push.impl.notifications.factories.action.Acc import io.element.android.libraries.push.impl.notifications.factories.action.MarkAsReadActionFactory import io.element.android.libraries.push.impl.notifications.factories.action.QuickReplyActionFactory import io.element.android.libraries.push.impl.notifications.factories.action.RejectInvitationActionFactory +import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent -import io.element.android.libraries.push.test.notifications.FakeImageLoader import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider import io.element.android.services.toolbox.test.strings.FakeStringProvider import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP @@ -50,17 +54,35 @@ class DefaultNotificationCreatorTest { @Test fun `test createDiagnosticNotification`() { val sut = createNotificationCreator() - val result = sut.createDiagnosticNotification() + val result = sut.createDiagnosticNotification( + color = A_COLOR_INT, + ) result.commonAssertions( expectedGroup = null, expectedCategory = NotificationCompat.CATEGORY_STATUS, ) } + @Test + fun `test createUnregistrationNotification`() { + val sut = createNotificationCreator() + val matrixUser = aMatrixUser() + val result = sut.createUnregistrationNotification( + notificationAccountParams = aNotificationAccountParams( + user = matrixUser, + ), + ) + result.commonAssertions( + expectedGroup = matrixUser.userId.value, + expectedCategory = NotificationCompat.CATEGORY_ERROR, + ) + } + @Test fun `test createFallbackNotification`() { val sut = createNotificationCreator() val result = sut.createFallbackNotification( + notificationAccountParams = aNotificationAccountParams(), FallbackNotifiableEvent( sessionId = A_SESSION_ID, roomId = A_ROOM_ID, @@ -72,7 +94,7 @@ class DefaultNotificationCreatorTest { isUpdated = false, timestamp = A_FAKE_TIMESTAMP, cause = null, - ) + ), ) result.commonAssertions( expectedCategory = null, @@ -83,6 +105,7 @@ class DefaultNotificationCreatorTest { fun `test createSimpleEventNotification`() { val sut = createNotificationCreator() val result = sut.createSimpleEventNotification( + notificationAccountParams = aNotificationAccountParams(), SimpleNotifiableEvent( sessionId = A_SESSION_ID, roomId = A_ROOM_ID, @@ -97,7 +120,7 @@ class DefaultNotificationCreatorTest { canBeReplaced = false, isRedacted = false, isUpdated = false, - ) + ), ) result.commonAssertions( expectedCategory = null, @@ -108,6 +131,7 @@ class DefaultNotificationCreatorTest { fun `test createSimpleEventNotification noisy`() { val sut = createNotificationCreator() val result = sut.createSimpleEventNotification( + notificationAccountParams = aNotificationAccountParams(), SimpleNotifiableEvent( sessionId = A_SESSION_ID, roomId = A_ROOM_ID, @@ -122,7 +146,7 @@ class DefaultNotificationCreatorTest { canBeReplaced = false, isRedacted = false, isUpdated = false, - ) + ), ) result.commonAssertions( expectedCategory = null, @@ -133,6 +157,7 @@ class DefaultNotificationCreatorTest { fun `test createRoomInvitationNotification`() { val sut = createNotificationCreator() val result = sut.createRoomInvitationNotification( + notificationAccountParams = aNotificationAccountParams(), InviteNotifiableEvent( sessionId = A_SESSION_ID, roomId = A_ROOM_ID, @@ -148,7 +173,7 @@ class DefaultNotificationCreatorTest { isRedacted = false, isUpdated = false, roomName = "roomName", - ) + ), ) result.commonAssertions( expectedCategory = null, @@ -166,6 +191,7 @@ class DefaultNotificationCreatorTest { fun `test createRoomInvitationNotification noisy`() { val sut = createNotificationCreator() val result = sut.createRoomInvitationNotification( + notificationAccountParams = aNotificationAccountParams(), InviteNotifiableEvent( sessionId = A_SESSION_ID, roomId = A_ROOM_ID, @@ -181,7 +207,7 @@ class DefaultNotificationCreatorTest { isRedacted = false, isUpdated = false, roomName = "roomName", - ) + ), ) result.commonAssertions( expectedCategory = null, @@ -193,7 +219,7 @@ class DefaultNotificationCreatorTest { val sut = createNotificationCreator() val matrixUser = aMatrixUser() val result = sut.createSummaryListNotification( - currentUser = matrixUser, + notificationAccountParams = aNotificationAccountParams(user = matrixUser), compatSummary = "compatSummary", noisy = false, lastMessageTimestamp = 123_456L, @@ -208,7 +234,7 @@ class DefaultNotificationCreatorTest { val sut = createNotificationCreator() val matrixUser = aMatrixUser() val result = sut.createSummaryListNotification( - currentUser = matrixUser, + notificationAccountParams = aNotificationAccountParams(user = matrixUser), compatSummary = "compatSummary", noisy = true, lastMessageTimestamp = 123_456L, @@ -221,8 +247,8 @@ class DefaultNotificationCreatorTest { @Test fun `test createMessagesListNotification`() = runTest { val sut = createNotificationCreator() - aMatrixUser() val result = sut.createMessagesListNotification( + notificationAccountParams = aNotificationAccountParams(), roomInfo = RoomEventGroupInfo( sessionId = A_SESSION_ID, roomId = A_ROOM_ID, @@ -236,10 +262,9 @@ class DefaultNotificationCreatorTest { largeIcon = null, lastMessageTimestamp = 123_456L, tickerText = "tickerText", - currentUser = aMatrixUser(), existingNotification = null, - imageLoader = FakeImageLoader().getImageLoader(), - events = emptyList(), + imageLoader = FakeImageLoader(), + events = listOf(aNotifiableMessageEvent()), ) result.commonAssertions() } @@ -247,8 +272,8 @@ class DefaultNotificationCreatorTest { @Test fun `test createMessagesListNotification should bing and thread`() = runTest { val sut = createNotificationCreator() - aMatrixUser() val result = sut.createMessagesListNotification( + notificationAccountParams = aNotificationAccountParams(), roomInfo = RoomEventGroupInfo( sessionId = A_SESSION_ID, roomId = A_ROOM_ID, @@ -262,16 +287,15 @@ class DefaultNotificationCreatorTest { largeIcon = null, lastMessageTimestamp = 123_456L, tickerText = "tickerText", - currentUser = aMatrixUser(), existingNotification = null, - imageLoader = FakeImageLoader().getImageLoader(), - events = emptyList(), + imageLoader = FakeImageLoader(), + events = listOf(aNotifiableMessageEvent()), ) result.commonAssertions() } private fun Notification.commonAssertions( - expectedGroup: String? = A_SESSION_ID.value, + expectedGroup: String? = aMatrixUser().userId.value, expectedCategory: String? = NotificationCompat.CATEGORY_MESSAGE, ) { assertThat(contentIntent).isNotNull() @@ -289,7 +313,11 @@ fun createNotificationCreator( context: Context = RuntimeEnvironment.getApplication(), buildMeta: BuildMeta = aBuildMeta(), notificationChannels: NotificationChannels = createNotificationChannels(), - bitmapLoader: NotificationBitmapLoader = DefaultNotificationBitmapLoader(context, FakeBuildVersionSdkIntProvider(Build.VERSION_CODES.R)), + bitmapLoader: NotificationBitmapLoader = DefaultNotificationBitmapLoader( + context = context, + sdkIntProvider = FakeBuildVersionSdkIntProvider(Build.VERSION_CODES.R), + initialsAvatarBitmapGenerator = FakeInitialsAvatarBitmapGenerator(), + ), ): NotificationCreator { return DefaultNotificationCreator( context = context, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/FakeIntentProvider.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/FakeIntentProvider.kt index 29aade1753..62ab850ae7 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/FakeIntentProvider.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/FakeIntentProvider.kt @@ -1,18 +1,27 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.notifications.factories import android.content.Intent +import android.os.Bundle +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.intent.IntentProvider class FakeIntentProvider : IntentProvider { - override fun getViewRoomIntent(sessionId: SessionId, roomId: RoomId?, threadId: ThreadId?) = Intent(Intent.ACTION_VIEW) + override fun getViewRoomIntent( + sessionId: SessionId, + roomId: RoomId?, + threadId: ThreadId?, + eventId: EventId?, + extras: Bundle?, + ) = Intent(Intent.ACTION_VIEW) } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationAccountParams.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationAccountParams.kt new file mode 100644 index 0000000000..d0c724d259 --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationAccountParams.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.impl.notifications.factories + +import androidx.annotation.ColorInt +import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.test.A_COLOR_INT +import io.element.android.libraries.matrix.ui.components.aMatrixUser + +fun aNotificationAccountParams( + user: MatrixUser = aMatrixUser(), + @ColorInt color: Int = A_COLOR_INT, + showSessionId: Boolean = false, +) = NotificationAccountParams( + user = user, + color = color, + showSessionId = showSessionId, +) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeActiveNotificationsProvider.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeActiveNotificationsProvider.kt index 0e93ba3506..ae3edce437 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeActiveNotificationsProvider.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeActiveNotificationsProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,18 +11,24 @@ package io.element.android.libraries.push.impl.notifications.fake import android.service.notification.StatusBarNotification 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.notifications.ActiveNotificationsProvider class FakeActiveNotificationsProvider( - private val getMessageNotificationsForRoomResult: (SessionId, RoomId) -> List = { _, _ -> emptyList() }, + private val getMessageNotificationsForRoomResult: (SessionId, RoomId, ThreadId?) -> List = { _, _, _ -> emptyList() }, + private val getAllMessageNotificationsForRoomResult: (SessionId, RoomId) -> List = { _, _ -> emptyList() }, private val getNotificationsForSessionResult: (SessionId) -> List = { emptyList() }, private val getMembershipNotificationForSessionResult: (SessionId) -> List = { emptyList() }, private val getMembershipNotificationForRoomResult: (SessionId, RoomId) -> List = { _, _ -> emptyList() }, private val getSummaryNotificationResult: (SessionId) -> StatusBarNotification? = { null }, private val countResult: (SessionId) -> Int = { 0 }, ) : ActiveNotificationsProvider { - override fun getMessageNotificationsForRoom(sessionId: SessionId, roomId: RoomId): List { - return getMessageNotificationsForRoomResult(sessionId, roomId) + override fun getMessageNotificationsForRoom(sessionId: SessionId, roomId: RoomId, threadId: ThreadId?): List { + return getMessageNotificationsForRoomResult(sessionId, roomId, threadId) + } + + override fun getAllMessageNotificationsForRoom(sessionId: SessionId, roomId: RoomId): List { + return getAllMessageNotificationsForRoomResult(sessionId, roomId) } override fun getNotificationsForSession(sessionId: SessionId): List { diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationCreator.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationCreator.kt index 3cbed6e8bc..d5e4ad9695 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationCreator.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationCreator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,70 +10,93 @@ package io.element.android.libraries.push.impl.notifications.fake import android.app.Notification import android.graphics.Bitmap +import androidx.annotation.ColorInt import coil3.ImageLoader import io.element.android.libraries.matrix.api.core.ThreadId -import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.push.impl.notifications.RoomEventGroupInfo +import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator import io.element.android.libraries.push.impl.notifications.fixtures.A_NOTIFICATION import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent -import io.element.android.tests.testutils.lambda.LambdaFourParamsRecorder +import io.element.android.tests.testutils.lambda.LambdaFiveParamsRecorder import io.element.android.tests.testutils.lambda.LambdaListAnyParamsRecorder -import io.element.android.tests.testutils.lambda.LambdaNoParamRecorder import io.element.android.tests.testutils.lambda.LambdaOneParamRecorder +import io.element.android.tests.testutils.lambda.LambdaTwoParamsRecorder import io.element.android.tests.testutils.lambda.lambdaAnyRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder class FakeNotificationCreator( var createMessagesListNotificationResult: LambdaListAnyParamsRecorder = lambdaAnyRecorder { A_NOTIFICATION }, - var createRoomInvitationNotificationResult: LambdaOneParamRecorder = lambdaRecorder { _ -> A_NOTIFICATION }, - var createSimpleNotificationResult: LambdaOneParamRecorder = lambdaRecorder { _ -> A_NOTIFICATION }, - var createFallbackNotificationResult: LambdaOneParamRecorder = lambdaRecorder { _ -> A_NOTIFICATION }, - var createSummaryListNotificationResult: LambdaFourParamsRecorder = - lambdaRecorder { _, _, _, _ -> A_NOTIFICATION }, - var createDiagnosticNotificationResult: LambdaNoParamRecorder = lambdaRecorder { -> A_NOTIFICATION }, + var createRoomInvitationNotificationResult: LambdaTwoParamsRecorder = + lambdaRecorder { _, _ -> A_NOTIFICATION }, + var createSimpleNotificationResult: LambdaTwoParamsRecorder = + lambdaRecorder { _, _ -> A_NOTIFICATION }, + var createFallbackNotificationResult: LambdaTwoParamsRecorder = + lambdaRecorder { _, _ -> A_NOTIFICATION }, + var createSummaryListNotificationResult: LambdaFiveParamsRecorder< + NotificationAccountParams, String, Boolean, Long, NotificationAccountParams, Notification + > = lambdaRecorder { _, _, _, _, _ -> A_NOTIFICATION }, + var createDiagnosticNotificationResult: LambdaOneParamRecorder = + lambdaRecorder { _ -> A_NOTIFICATION }, + val createUnregistrationNotificationResult: LambdaOneParamRecorder = + lambdaRecorder { _ -> A_NOTIFICATION }, ) : NotificationCreator { override suspend fun createMessagesListNotification( + notificationAccountParams: NotificationAccountParams, roomInfo: RoomEventGroupInfo, threadId: ThreadId?, largeIcon: Bitmap?, lastMessageTimestamp: Long, tickerText: String, - currentUser: MatrixUser, existingNotification: Notification?, imageLoader: ImageLoader, - events: List + events: List, ): Notification { return createMessagesListNotificationResult( - listOf(roomInfo, threadId, largeIcon, lastMessageTimestamp, tickerText, currentUser, existingNotification, imageLoader, events) + listOf(notificationAccountParams, roomInfo, threadId, largeIcon, lastMessageTimestamp, tickerText, existingNotification, imageLoader, events) ) } - override fun createRoomInvitationNotification(inviteNotifiableEvent: InviteNotifiableEvent): Notification { - return createRoomInvitationNotificationResult(inviteNotifiableEvent) + override fun createRoomInvitationNotification( + notificationAccountParams: NotificationAccountParams, + inviteNotifiableEvent: InviteNotifiableEvent, + ): Notification { + return createRoomInvitationNotificationResult(notificationAccountParams, inviteNotifiableEvent) } - override fun createSimpleEventNotification(simpleNotifiableEvent: SimpleNotifiableEvent): Notification { - return createSimpleNotificationResult(simpleNotifiableEvent) + override fun createSimpleEventNotification( + notificationAccountParams: NotificationAccountParams, + simpleNotifiableEvent: SimpleNotifiableEvent, + ): Notification { + return createSimpleNotificationResult(notificationAccountParams, simpleNotifiableEvent) } - override fun createFallbackNotification(fallbackNotifiableEvent: FallbackNotifiableEvent): Notification { - return createFallbackNotificationResult(fallbackNotifiableEvent) + override fun createFallbackNotification( + notificationAccountParams: NotificationAccountParams, + fallbackNotifiableEvent: FallbackNotifiableEvent, + ): Notification { + return createFallbackNotificationResult(notificationAccountParams, fallbackNotifiableEvent) } override fun createSummaryListNotification( - currentUser: MatrixUser, + notificationAccountParams: NotificationAccountParams, compatSummary: String, noisy: Boolean, - lastMessageTimestamp: Long + lastMessageTimestamp: Long, ): Notification { - return createSummaryListNotificationResult(currentUser, compatSummary, noisy, lastMessageTimestamp) + return createSummaryListNotificationResult(notificationAccountParams, compatSummary, noisy, lastMessageTimestamp, notificationAccountParams) } - override fun createDiagnosticNotification(): Notification { - return createDiagnosticNotificationResult() + override fun createDiagnosticNotification( + @ColorInt color: Int, + ): Notification { + return createDiagnosticNotificationResult(color) + } + + override fun createUnregistrationNotification(notificationAccountParams: NotificationAccountParams): Notification { + return createUnregistrationNotificationResult(notificationAccountParams) } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDataFactory.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDataFactory.kt index fed6e3c7a3..009513b640 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDataFactory.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDataFactory.kt @@ -1,18 +1,19 @@ /* - * Copyright 2021-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2021-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.notifications.fake import coil3.ImageLoader -import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.push.impl.notifications.NotificationDataFactory import io.element.android.libraries.push.impl.notifications.OneShotNotification import io.element.android.libraries.push.impl.notifications.RoomNotification import io.element.android.libraries.push.impl.notifications.SummaryNotification +import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams import io.element.android.libraries.push.impl.notifications.fixtures.A_NOTIFICATION import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent @@ -24,56 +25,70 @@ import io.element.android.tests.testutils.lambda.LambdaThreeParamsRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder class FakeNotificationDataFactory( - var messageEventToNotificationsResult: LambdaThreeParamsRecorder, MatrixUser, ImageLoader, List> = - lambdaRecorder { _, _, _ -> emptyList() }, + var messageEventToNotificationsResult: LambdaThreeParamsRecorder< + List, ImageLoader, NotificationAccountParams, List + > = lambdaRecorder { _, _, _ -> emptyList() }, var summaryToNotificationsResult: LambdaFiveParamsRecorder< - MatrixUser, List, List, List, List, + NotificationAccountParams, SummaryNotification - > = lambdaRecorder { _, _, _, _, _ -> SummaryNotification.Update(A_NOTIFICATION) }, + > = lambdaRecorder { _, _, _, _, _ -> SummaryNotification.Update(A_NOTIFICATION) }, var inviteToNotificationsResult: LambdaOneParamRecorder, List> = lambdaRecorder { _ -> emptyList() }, var simpleEventToNotificationsResult: LambdaOneParamRecorder, List> = lambdaRecorder { _ -> emptyList() }, var fallbackEventToNotificationsResult: LambdaOneParamRecorder, List> = lambdaRecorder { _ -> emptyList() }, ) : NotificationDataFactory { - override suspend fun toNotifications(messages: List, currentUser: MatrixUser, imageLoader: ImageLoader): List { - return messageEventToNotificationsResult(messages, currentUser, imageLoader) + override suspend fun toNotifications( + messages: List, + imageLoader: ImageLoader, + notificationAccountParams: NotificationAccountParams, + ): List { + return messageEventToNotificationsResult(messages, imageLoader, notificationAccountParams) } @JvmName("toNotificationInvites") @Suppress("INAPPLICABLE_JVM_NAME") - override fun toNotifications(invites: List): List { + override fun toNotifications( + invites: List, + notificationAccountParams: NotificationAccountParams, + ): List { return inviteToNotificationsResult(invites) } @JvmName("toNotificationSimpleEvents") @Suppress("INAPPLICABLE_JVM_NAME") - override fun toNotifications(simpleEvents: List): List { + override fun toNotifications( + simpleEvents: List, + notificationAccountParams: NotificationAccountParams, + ): List { return simpleEventToNotificationsResult(simpleEvents) } @JvmName("toNotificationFallbackEvents") @Suppress("INAPPLICABLE_JVM_NAME") - override fun toNotifications(fallback: List): List { + override fun toNotifications( + fallback: List, + notificationAccountParams: NotificationAccountParams, + ): List { return fallbackEventToNotificationsResult(fallback) } override fun createSummaryNotification( - currentUser: MatrixUser, roomNotifications: List, invitationNotifications: List, simpleNotifications: List, fallbackNotifications: List, + notificationAccountParams: NotificationAccountParams, ): SummaryNotification { return summaryToNotificationsResult( - currentUser, roomNotifications, invitationNotifications, simpleNotifications, fallbackNotifications, + notificationAccountParams, ) } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt index cd3d047e2e..fd4af70a72 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2021-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2021-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -19,17 +20,18 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value class FakeNotificationDisplayer( - var showNotificationMessageResult: LambdaThreeParamsRecorder = lambdaRecorder { _, _, _ -> true }, - var cancelNotificationMessageResult: LambdaTwoParamsRecorder = lambdaRecorder { _, _ -> }, + var showNotificationResult: LambdaThreeParamsRecorder = lambdaRecorder { _, _, _ -> true }, + var cancelNotificationResult: LambdaTwoParamsRecorder = lambdaRecorder { _, _ -> }, var displayDiagnosticNotificationResult: LambdaOneParamRecorder = lambdaRecorder { _ -> true }, var dismissDiagnosticNotificationResult: LambdaNoParamRecorder = lambdaRecorder { -> }, + var displayUnregistrationNotificationResult: LambdaOneParamRecorder = lambdaRecorder { _ -> true }, ) : NotificationDisplayer { - override fun showNotificationMessage(tag: String?, id: Int, notification: Notification): Boolean { - return showNotificationMessageResult(tag, id, notification) + override fun showNotification(tag: String?, id: Int, notification: Notification): Boolean { + return showNotificationResult(tag, id, notification) } - override fun cancelNotificationMessage(tag: String?, id: Int) { - return cancelNotificationMessageResult(tag, id) + override fun cancelNotification(tag: String?, id: Int) { + return cancelNotificationResult(tag, id) } override fun displayDiagnosticNotification(notification: Notification): Boolean { @@ -40,8 +42,12 @@ class FakeNotificationDisplayer( return dismissDiagnosticNotificationResult() } + override fun displayUnregistrationNotification(notification: Notification): Boolean { + return displayUnregistrationNotificationResult(notification) + } + fun verifySummaryCancelled(times: Int = 1) { - cancelNotificationMessageResult.assertions().isCalledExactly(times).withSequence( + cancelNotificationResult.assertions().isCalledExactly(times).withSequence( listOf(value(null), value(NotificationIdProvider.getSummaryNotificationId(A_SESSION_ID))) ) } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationMediaRepo.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationMediaRepo.kt index 66c64fb83e..ecf4a15129 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationMediaRepo.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationMediaRepo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt index c531735a1e..c4b9513fa3 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2021-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2021-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,24 +11,29 @@ package io.element.android.libraries.push.impl.notifications.fake import android.app.Notification import coil3.ImageLoader import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.push.impl.notifications.RoomGroupMessageCreator +import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams import io.element.android.libraries.push.impl.notifications.fixtures.A_NOTIFICATION import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent -import io.element.android.tests.testutils.lambda.LambdaFiveParamsRecorder +import io.element.android.tests.testutils.lambda.LambdaSixParamsRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder +// We just can't make the param types fit +@Suppress("MaxLineLength", "ktlint:standard:max-line-length", "ktlint:standard:parameter-wrapping") class FakeRoomGroupMessageCreator( - var createRoomMessageResult: LambdaFiveParamsRecorder, RoomId, ImageLoader, Notification?, Notification> = - lambdaRecorder { _, _, _, _, _, -> A_NOTIFICATION } + var createRoomMessageResult: LambdaSixParamsRecorder< + NotificationAccountParams, List, RoomId, ThreadId?, ImageLoader, Notification?, Notification + > = lambdaRecorder { _, _, _, _, _, _ -> A_NOTIFICATION } ) : RoomGroupMessageCreator { override suspend fun createRoomMessage( - currentUser: MatrixUser, + notificationAccountParams: NotificationAccountParams, events: List, roomId: RoomId, + threadId: ThreadId?, imageLoader: ImageLoader, - existingNotification: Notification? + existingNotification: Notification?, ): Notification { - return createRoomMessageResult(currentUser, events, roomId, imageLoader, existingNotification) + return createRoomMessageResult(notificationAccountParams, events, roomId, threadId, imageLoader, existingNotification) } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt index ed3ca3027e..9db6853408 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt @@ -1,36 +1,36 @@ /* - * Copyright 2021-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2021-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.notifications.fake import android.app.Notification -import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.push.impl.notifications.OneShotNotification import io.element.android.libraries.push.impl.notifications.RoomNotification import io.element.android.libraries.push.impl.notifications.SummaryGroupMessageCreator +import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams import io.element.android.libraries.push.impl.notifications.fixtures.A_NOTIFICATION import io.element.android.tests.testutils.lambda.LambdaFiveParamsRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder class FakeSummaryGroupMessageCreator( var createSummaryNotificationResult: LambdaFiveParamsRecorder< - MatrixUser, List, List, List, List, Notification - > = + NotificationAccountParams, List, List, List, List, Notification> = lambdaRecorder { _, _, _, _, _ -> A_NOTIFICATION } ) : SummaryGroupMessageCreator { override fun createSummaryNotification( - currentUser: MatrixUser, + notificationAccountParams: NotificationAccountParams, roomNotifications: List, invitationNotifications: List, simpleNotifications: List, fallbackNotifications: List, ): Notification { return createSummaryNotificationResult( - currentUser, + notificationAccountParams, roomNotifications, invitationNotifications, simpleNotifications, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt index 0d466d90fd..edd0c2ba17 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotifiableEventFixture.kt @@ -1,7 +1,8 @@ /* - * Copyright 2021-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2021-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotificationEventRequestFixture.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotificationEventRequestFixture.kt new file mode 100644 index 0000000000..c450287fbd --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotificationEventRequestFixture.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.impl.notifications.fixtures + +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.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.push.api.push.NotificationEventRequest + +fun aNotificationEventRequest( + sessionId: SessionId = A_SESSION_ID, + roomId: RoomId = A_ROOM_ID, + eventId: EventId = AN_EVENT_ID, + providerInfo: String = "providerInfo", +) = NotificationEventRequest( + sessionId = sessionId, + roomId = roomId, + eventId = eventId, + providerInfo = providerInfo, +) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotificationFixture.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotificationFixture.kt index 48c9ec4316..5ae1156b36 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotificationFixture.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fixtures/NotificationFixture.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultOnRedactedEventReceivedTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultOnRedactedEventReceivedTest.kt index 1b08f7b14c..2bab4c14c2 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultOnRedactedEventReceivedTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultOnRedactedEventReceivedTest.kt @@ -1,27 +1,37 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.push +import android.app.Notification import android.service.notification.StatusBarNotification +import androidx.core.app.NotificationCompat +import androidx.core.app.Person import androidx.test.platform.app.InstrumentationRegistry +import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.AN_EVENT_ID_2 import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.A_THREAD_ID +import io.element.android.libraries.matrix.test.A_USER_ID +import io.element.android.libraries.matrix.test.A_USER_NAME +import io.element.android.libraries.push.impl.notifications.factories.DefaultNotificationCreator import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent import io.element.android.services.toolbox.test.strings.FakeStringProvider import io.element.android.tests.testutils.lambda.lambdaError +import io.element.android.tests.testutils.lambda.lambdaRecorder import io.mockk.every import io.mockk.mockk -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -29,43 +39,113 @@ import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) class DefaultOnRedactedEventReceivedTest { + private val fakePerson = Person.Builder().setName(A_USER_NAME).setKey(A_USER_ID.value).build() + private val fakeMessage = NotificationCompat.MessagingStyle.Message("A message", 0L, fakePerson).also { + it.extras.putString(DefaultNotificationCreator.MESSAGE_EVENT_ID, AN_EVENT_ID.value) + } + private val fakeNotification = NotificationCompat.Builder(InstrumentationRegistry.getInstrumentation().targetContext, "aChannel") + .setStyle( + NotificationCompat.MessagingStyle(fakePerson) + .addMessage(fakeMessage) + ) + .setGroup(A_SESSION_ID.value) + .build() + + private val fakeIncorrectMessage = NotificationCompat.MessagingStyle.Message("The wrong message", 0L, fakePerson).also { + it.extras.putString(DefaultNotificationCreator.MESSAGE_EVENT_ID, AN_EVENT_ID_2.value) + } + private val fakeIncorrectNotification = NotificationCompat.Builder(InstrumentationRegistry.getInstrumentation().targetContext, "aChannel") + .setGroup(A_SESSION_ID.value) + .setStyle( + NotificationCompat.MessagingStyle(fakePerson) + .addMessage(fakeIncorrectMessage) + ) + .build() + @Test fun `when no notifications are found, nothing happen`() = runTest { + val showNotificationLambda = lambdaRecorder { _, _, _ -> true } val sut = createDefaultOnRedactedEventReceived( - getMessageNotificationsForRoomResult = { _, _ -> emptyList() } + getAllMessageNotificationsForRoomResult = { _, _ -> emptyList() }, + displayer = FakeNotificationDisplayer(showNotificationLambda), ) sut.onRedactedEventsReceived(listOf(ResolvedPushEvent.Redaction(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, null))) + showNotificationLambda.assertions().isNeverCalled() } @Test fun `when a notification is found, try to retrieve the message`() = runTest { + val showNotificationLambda = lambdaRecorder { tag, id, _ -> + assertThat(tag).isEqualTo(A_ROOM_ID.value) + assertThat(id).isEqualTo(1) + true + } val sut = createDefaultOnRedactedEventReceived( - getMessageNotificationsForRoomResult = { _, _ -> + getAllMessageNotificationsForRoomResult = { _, _ -> listOf( mockk { - every { notification } returns mockk {} + every { id } returns 1 + every { notification } returns fakeNotification + every { tag } returns A_ROOM_ID.value + }, + mockk { + every { id } returns 2 + every { notification } returns fakeIncorrectNotification + every { tag } returns A_ROOM_ID.value } ) - } + }, + displayer = FakeNotificationDisplayer(showNotificationLambda), ) sut.onRedactedEventsReceived(listOf(ResolvedPushEvent.Redaction(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, null))) + showNotificationLambda.assertions().isCalledOnce() } - private fun TestScope.createDefaultOnRedactedEventReceived( - getMessageNotificationsForRoomResult: (SessionId, RoomId) -> List = { _, _ -> lambdaError() }, + @Test + fun `when thread notifications are found, try to retrieve the message`() = runTest { + val showNotificationLambda = lambdaRecorder { tag, id, _ -> + assertThat(tag).isEqualTo("$A_ROOM_ID|$A_THREAD_ID") + assertThat(id).isEqualTo(1) + true + } + val sut = createDefaultOnRedactedEventReceived( + getAllMessageNotificationsForRoomResult = { _, _ -> + listOf( + mockk { + every { id } returns 1 + every { notification } returns fakeNotification + every { tag } returns "$A_ROOM_ID|$A_THREAD_ID" + }, + mockk { + every { id } returns 2 + every { notification } returns fakeIncorrectNotification + every { tag } returns A_ROOM_ID.value + } + ) + }, + displayer = FakeNotificationDisplayer(showNotificationResult = showNotificationLambda), + ) + sut.onRedactedEventsReceived(listOf(ResolvedPushEvent.Redaction(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, null))) + + showNotificationLambda.assertions().isCalledOnce() + } + + private fun createDefaultOnRedactedEventReceived( + getAllMessageNotificationsForRoomResult: (SessionId, RoomId) -> List = { _, _ -> lambdaError() }, + displayer: FakeNotificationDisplayer = FakeNotificationDisplayer(), ): DefaultOnRedactedEventReceived { val context = InstrumentationRegistry.getInstrumentation().context return DefaultOnRedactedEventReceived( activeNotificationsProvider = FakeActiveNotificationsProvider( - getMessageNotificationsForRoomResult = getMessageNotificationsForRoomResult, + getMessageNotificationsForRoomResult = { _, _, _ -> lambdaError() }, + getAllMessageNotificationsForRoomResult = getAllMessageNotificationsForRoomResult, getNotificationsForSessionResult = { lambdaError() }, getMembershipNotificationForSessionResult = { lambdaError() }, getMembershipNotificationForRoomResult = { _, _ -> lambdaError() }, getSummaryNotificationResult = { lambdaError() }, countResult = { lambdaError() }, ), - notificationDisplayer = FakeNotificationDisplayer(), - coroutineScope = this, + notificationDisplayer = displayer, context = context, stringProvider = FakeStringProvider(), ) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt index e482385b3d..7c95034985 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,10 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.call.api.CallType import io.element.android.features.call.test.FakeElementCallEntryPoint +import io.element.android.libraries.androidutils.json.DefaultJsonProvider import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService 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 @@ -28,12 +32,13 @@ import io.element.android.libraries.matrix.test.A_SECRET import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.core.aBuildMeta +import io.element.android.libraries.push.api.push.NotificationEventRequest +import io.element.android.libraries.push.api.push.SyncOnNotifiableEvent import io.element.android.libraries.push.impl.history.FakePushHistoryService import io.element.android.libraries.push.impl.history.PushHistoryService +import io.element.android.libraries.push.impl.notifications.DefaultNotificationResolverQueue import io.element.android.libraries.push.impl.notifications.FakeNotifiableEventResolver import io.element.android.libraries.push.impl.notifications.FallbackNotificationFactory -import io.element.android.libraries.push.impl.notifications.NotificationEventRequest -import io.element.android.libraries.push.impl.notifications.NotificationResolverQueue import io.element.android.libraries.push.impl.notifications.channels.FakeNotificationChannels import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableCallEvent import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent @@ -42,12 +47,16 @@ import io.element.android.libraries.push.impl.notifications.model.NotifiableEven import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent import io.element.android.libraries.push.impl.test.DefaultTestPush import io.element.android.libraries.push.impl.troubleshoot.DiagnosticPushHandler +import io.element.android.libraries.push.impl.workmanager.SyncNotificationsWorkerDataConverter import io.element.android.libraries.pushproviders.api.PushData import io.element.android.libraries.pushstore.api.UserPushStore import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStore import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory import io.element.android.libraries.pushstore.test.userpushstore.clientsecret.FakePushClientSecret +import io.element.android.libraries.workmanager.api.WorkManagerRequest +import io.element.android.libraries.workmanager.test.FakeWorkManagerScheduler +import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider import io.element.android.services.toolbox.test.strings.FakeStringProvider import io.element.android.services.toolbox.test.systemclock.FakeSystemClock import io.element.android.tests.testutils.lambda.any @@ -131,6 +140,45 @@ class DefaultPushHandlerTest { .isCalledOnce() } + @Test + fun `when classical PushData is received and the workmanager flag is enabled, the work is scheduled`() = runTest { + val aNotifiableMessageEvent = aNotifiableMessageEvent() + val notifiableEventResult = + lambdaRecorder, Result>>> { _, _ -> + val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, A_PUSHER_INFO) + Result.success(mapOf(request to Result.success(ResolvedPushEvent.Event(aNotifiableMessageEvent)))) + } + val incrementPushCounterResult = lambdaRecorder {} + val aPushData = PushData( + eventId = AN_EVENT_ID, + roomId = A_ROOM_ID, + unread = 0, + clientSecret = A_SECRET, + ) + + val featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SyncNotificationsWithWorkManager.key to true)) + val submitWorkLambda = lambdaRecorder {} + val workManagerScheduler = FakeWorkManagerScheduler(submitLambda = submitWorkLambda) + + val defaultPushHandler = createDefaultPushHandler( + notifiableEventsResult = notifiableEventResult, + pushClientSecret = FakePushClientSecret( + getUserIdFromSecretResult = { A_USER_ID } + ), + incrementPushCounterResult = incrementPushCounterResult, + featureFlagService = featureFlagService, + workManagerScheduler = workManagerScheduler, + ) + defaultPushHandler.handle(aPushData, A_PUSHER_INFO) + + advanceTimeBy(300.milliseconds) + + submitWorkLambda.assertions().isCalledOnce() + + incrementPushCounterResult.assertions() + .isCalledOnce() + } + @Test fun `when classical PushData is received, but notifications are disabled, nothing happen`() = runTest { @@ -644,6 +692,9 @@ class DefaultPushHandlerTest { elementCallEntryPoint: FakeElementCallEntryPoint = FakeElementCallEntryPoint(), notificationChannels: FakeNotificationChannels = FakeNotificationChannels(), pushHistoryService: PushHistoryService = FakePushHistoryService(), + syncOnNotifiableEvent: SyncOnNotifiableEvent = SyncOnNotifiableEvent {}, + featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.SyncNotificationsWithWorkManager.key to false)), + workManagerScheduler: FakeWorkManagerScheduler = FakeWorkManagerScheduler(), ): DefaultPushHandler { return DefaultPushHandler( onNotifiableEventReceived = FakeOnNotifiableEventReceived(onNotifiableEventsReceived), @@ -661,12 +712,22 @@ class DefaultPushHandlerTest { elementCallEntryPoint = elementCallEntryPoint, notificationChannels = notificationChannels, pushHistoryService = pushHistoryService, - resolverQueue = NotificationResolverQueue(notifiableEventResolver = FakeNotifiableEventResolver(notifiableEventsResult), backgroundScope), + // We don't use a fake here so we can perform tests that are a bit more end to end + resolverQueue = DefaultNotificationResolverQueue( + notifiableEventResolver = FakeNotifiableEventResolver(notifiableEventsResult), + appCoroutineScope = backgroundScope, + workManagerScheduler = workManagerScheduler, + featureFlagService = featureFlagService, + workerDataConverter = SyncNotificationsWorkerDataConverter(DefaultJsonProvider()), + buildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(33), + ), appCoroutineScope = backgroundScope, fallbackNotificationFactory = FallbackNotificationFactory( clock = FakeSystemClock(), stringProvider = FakeStringProvider(), - ) + ), + syncOnNotifiableEvent = syncOnNotifiableEvent, + featureFlagService = featureFlagService, ) } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/FakeMutableBatteryOptimizationStore.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/FakeMutableBatteryOptimizationStore.kt index d4d3992f1e..f5de845350 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/FakeMutableBatteryOptimizationStore.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/FakeMutableBatteryOptimizationStore.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/FakeOnNotifiableEventReceived.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/FakeOnNotifiableEventReceived.kt index 055b1f322e..6686577dbb 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/FakeOnNotifiableEventReceived.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/FakeOnNotifiableEventReceived.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/FakeOnRedactedEventReceived.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/FakeOnRedactedEventReceived.kt index b5a3731830..e295298c63 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/FakeOnRedactedEventReceived.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/FakeOnRedactedEventReceived.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,7 @@ import io.element.android.tests.testutils.lambda.lambdaError class FakeOnRedactedEventReceived( private val onRedactedEventsReceivedResult: (List) -> Unit = { lambdaError() }, ) : OnRedactedEventReceived { - override fun onRedactedEventsReceived(redactions: List) { + override suspend fun onRedactedEventsReceived(redactions: List) { onRedactedEventsReceivedResult(redactions) } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/SyncOnNotifiableEventTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/SyncOnNotifiableEventTest.kt index e1c4b33acf..6c88e7bf12 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/SyncOnNotifiableEventTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/SyncOnNotifiableEventTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,22 +19,17 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom -import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.sync.FakeSyncService -import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableCallEvent -import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent +import io.element.android.libraries.push.api.push.SyncOnNotifiableEvent +import io.element.android.libraries.push.impl.notifications.fixtures.aNotificationEventRequest import io.element.android.services.appnavstate.test.FakeAppForegroundStateService import io.element.android.tests.testutils.lambda.assert import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.testCoroutineDispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runTest import org.junit.Test -import java.util.concurrent.atomic.AtomicBoolean -import kotlin.time.Duration.Companion.seconds class SyncOnNotifiableEventTest { private val startSyncLambda = lambdaRecorder> { Result.success(Unit) } @@ -57,60 +53,19 @@ class SyncOnNotifiableEventTest { givenGetRoomResult(A_ROOM_ID, room) } - private val notifiableEvent = aNotifiableMessageEvent() - private val incomingCallNotifiableEvent = aNotifiableCallEvent() + private val notificationRequest = aNotificationEventRequest() @Test fun `when feature flag is disabled, nothing happens`() = runTest { val sut = createSyncOnNotifiableEvent(client = client, isSyncOnPushEnabled = false) - sut(listOf(notifiableEvent)) + sut(listOf(notificationRequest)) assert(startSyncLambda).isNeverCalled() assert(stopSyncLambda).isNeverCalled() assert(subscribeToSyncLambda).isNeverCalled() } - @OptIn(ExperimentalCoroutinesApi::class) - @Test - fun `when feature flag is enabled, a ringing call waits until the room is in 'in-call' state`() = runTest { - val appForegroundStateService = FakeAppForegroundStateService( - initialForegroundValue = false, - ) - val sut = createSyncOnNotifiableEvent(client = client, appForegroundStateService = appForegroundStateService, isSyncOnPushEnabled = true) - - val unlocked = AtomicBoolean(false) - launch { - advanceTimeBy(1.seconds) - unlocked.set(true) - room.givenRoomInfo(aRoomInfo(hasRoomCall = true)) - } - sut(listOf(incomingCallNotifiableEvent)) - - // The process was completed before the timeout - assertThat(unlocked.get()).isTrue() - } - - @OptIn(ExperimentalCoroutinesApi::class) - @Test - fun `when feature flag is enabled, a ringing call waits until the room is in 'in-call' state or timeouts`() = runTest { - val appForegroundStateService = FakeAppForegroundStateService( - initialForegroundValue = false, - ) - val sut = createSyncOnNotifiableEvent(client = client, appForegroundStateService = appForegroundStateService, isSyncOnPushEnabled = true) - - val unlocked = AtomicBoolean(false) - launch { - advanceTimeBy(120.seconds) - unlocked.set(true) - room.givenRoomInfo(aRoomInfo(hasRoomCall = true)) - } - sut(listOf(incomingCallNotifiableEvent)) - - // Didn't unlock before the timeout - assertThat(unlocked.get()).isFalse() - } - @Test fun `when feature flag is enabled and app is in background, sync is started and stopped`() = runTest { val appForegroundStateService = FakeAppForegroundStateService( @@ -120,7 +75,7 @@ class SyncOnNotifiableEventTest { appForegroundStateService.isSyncingNotificationEvent.test { syncService.emitSyncState(SyncState.Running) - sut(listOf(notifiableEvent)) + sut(listOf(notificationRequest)) // It's initially false assertThat(awaitItem()).isFalse() @@ -141,8 +96,8 @@ class SyncOnNotifiableEventTest { val sut = createSyncOnNotifiableEvent(client = client, appForegroundStateService = appForegroundStateService, isSyncOnPushEnabled = true) appForegroundStateService.isSyncingNotificationEvent.test { - launch { sut(listOf(notifiableEvent)) } - launch { sut(listOf(notifiableEvent)) } + launch { sut(listOf(notificationRequest)) } + launch { sut(listOf(notificationRequest)) } // It's initially false assertThat(awaitItem()).isFalse() @@ -168,7 +123,7 @@ class SyncOnNotifiableEventTest { ) ) val matrixClientProvider = FakeMatrixClientProvider { Result.success(client) } - return SyncOnNotifiableEvent( + return DefaultSyncOnNotifiableEvent( matrixClientProvider = matrixClientProvider, featureFlagService = featureFlagService, appForegroundStateService = appForegroundStateService, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/pushgateway/DefaultPushGatewayNotifyRequestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/pushgateway/DefaultPushGatewayNotifyRequestTest.kt index 223f2f4d8a..47dc027b43 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/pushgateway/DefaultPushGatewayNotifyRequestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/pushgateway/DefaultPushGatewayNotifyRequestTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/pushgateway/FakePushGatewayApiFactory.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/pushgateway/FakePushGatewayApiFactory.kt index 563b6d1c37..1b1958a8a3 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/pushgateway/FakePushGatewayApiFactory.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/pushgateway/FakePushGatewayApiFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/store/InMemoryPushDataStore.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/store/InMemoryPushDataStore.kt index 41b0546f33..591e533262 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/store/InMemoryPushDataStore.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/store/InMemoryPushDataStore.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/test/DefaultTestPushTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/test/DefaultTestPushTest.kt index c3231097a0..9ab0f16b62 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/test/DefaultTestPushTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/test/DefaultTestPushTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,7 @@ package io.element.android.libraries.push.impl.test import io.element.android.appconfig.PushConfig import io.element.android.libraries.push.impl.pushgateway.PushGatewayNotifyRequest -import io.element.android.libraries.pushproviders.test.aCurrentUserPushConfig +import io.element.android.libraries.pushproviders.test.aSessionPushConfig import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import kotlinx.coroutines.test.runTest @@ -24,7 +25,7 @@ class DefaultTestPushTest { executeResult = executeResult, ) ) - val aConfig = aCurrentUserPushConfig() + val aConfig = aSessionPushConfig() defaultTestPush.execute(aConfig) executeResult.assertions() .isCalledOnce() diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/test/FakePushGatewayNotifyRequest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/test/FakePushGatewayNotifyRequest.kt index 97d3172262..173b05e1c9 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/test/FakePushGatewayNotifyRequest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/test/FakePushGatewayNotifyRequest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/test/FakeTestPush.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/test/FakeTestPush.kt index f09832cc3b..9050601f4a 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/test/FakeTestPush.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/test/FakeTestPush.kt @@ -1,19 +1,20 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.test -import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig +import io.element.android.libraries.pushproviders.api.Config import io.element.android.tests.testutils.lambda.lambdaError class FakeTestPush( - private val executeResult: (CurrentUserPushConfig) -> Unit = { lambdaError() } + private val executeResult: (Config) -> Unit = { lambdaError() } ) : TestPush { - override suspend fun execute(config: CurrentUserPushConfig) { + override suspend fun execute(config: Config) { executeResult(config) } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTestTest.kt index a11c6b147e..c49bff6c38 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTestTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTestTest.kt index e38baa7321..c662361f44 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/IgnoredUsersTestTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -39,7 +40,7 @@ class IgnoredUsersTestTest { ) val openIgnoredUsersResult = lambdaRecorder {} val navigator = object : NotificationTroubleshootNavigator { - override fun openIgnoredUsers() = openIgnoredUsersResult() + override fun navigateToBlockedUsers() = openIgnoredUsersResult() } sut.quickFix( coroutineScope = backgroundScope, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt index eecb3801fd..06dbe383ea 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt @@ -1,13 +1,16 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.troubleshoot import com.google.common.truth.Truth.assertThat +import io.element.android.features.enterprise.test.FakeEnterpriseService +import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState @@ -64,10 +67,12 @@ class NotificationTestTest { private fun createNotificationTest(): NotificationTest { return NotificationTest( + sessionId = A_SESSION_ID, notificationCreator = notificationCreator, notificationDisplayer = fakeNotificationDisplayer, notificationClickHandler = notificationClickHandler, stringProvider = FakeStringProvider(), + enterpriseService = FakeEnterpriseService(), ) } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt index f06b6a9e1b..f83fb660ce 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt @@ -1,21 +1,27 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.impl.troubleshoot import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_FAILURE_REASON +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.push.api.PushService import io.element.android.libraries.push.api.gateway.PushGatewayFailure import io.element.android.libraries.push.test.FakePushService import io.element.android.libraries.pushproviders.test.FakePushProvider import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState import io.element.android.libraries.troubleshoot.test.FakeNotificationTroubleshootNavigator import io.element.android.libraries.troubleshoot.test.runAndTestState +import io.element.android.services.toolbox.api.strings.StringProvider +import io.element.android.services.toolbox.api.systemclock.SystemClock import io.element.android.services.toolbox.test.strings.FakeStringProvider import io.element.android.services.toolbox.test.systemclock.FakeSystemClock import io.element.android.tests.testutils.lambda.lambdaRecorder @@ -25,13 +31,7 @@ import org.junit.Test class PushLoopbackTestTest { @Test fun `test PushLoopbackTest timeout - push is not received`() = runTest { - val diagnosticPushHandler = DiagnosticPushHandler() - val sut = PushLoopbackTest( - pushService = FakePushService(), - diagnosticPushHandler = diagnosticPushHandler, - clock = FakeSystemClock(), - stringProvider = FakeStringProvider(), - ) + val sut = createPushLoopbackTest() sut.runAndTestState { assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) @@ -42,16 +42,12 @@ class PushLoopbackTestTest { @Test fun `test PushLoopbackTest PusherRejected error`() = runTest { - val diagnosticPushHandler = DiagnosticPushHandler() - val sut = PushLoopbackTest( + val sut = createPushLoopbackTest( pushService = FakePushService( testPushBlock = { throw PushGatewayFailure.PusherRejected() } ), - diagnosticPushHandler = diagnosticPushHandler, - clock = FakeSystemClock(), - stringProvider = FakeStringProvider(), ) sut.runAndTestState { assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) @@ -65,9 +61,8 @@ class PushLoopbackTestTest { @Test fun `test PushLoopbackTest PusherRejected error with quick fix`() = runTest { - val diagnosticPushHandler = DiagnosticPushHandler() val rotateTokenLambda = lambdaRecorder> { Result.success(Unit) } - val sut = PushLoopbackTest( + val sut = createPushLoopbackTest( pushService = FakePushService( testPushBlock = { throw PushGatewayFailure.PusherRejected() @@ -79,9 +74,6 @@ class PushLoopbackTestTest { ) } ), - diagnosticPushHandler = diagnosticPushHandler, - clock = FakeSystemClock(), - stringProvider = FakeStringProvider(), ) sut.runAndTestState { assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) @@ -97,14 +89,10 @@ class PushLoopbackTestTest { @Test fun `test PushLoopbackTest setup error`() = runTest { - val diagnosticPushHandler = DiagnosticPushHandler() - val sut = PushLoopbackTest( + val sut = createPushLoopbackTest( pushService = FakePushService( testPushBlock = { false } ), - diagnosticPushHandler = diagnosticPushHandler, - clock = FakeSystemClock(), - stringProvider = FakeStringProvider(), ) sut.runAndTestState { assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) @@ -116,16 +104,12 @@ class PushLoopbackTestTest { @Test fun `test PushLoopbackTest other error`() = runTest { - val diagnosticPushHandler = DiagnosticPushHandler() - val sut = PushLoopbackTest( + val sut = createPushLoopbackTest( pushService = FakePushService( testPushBlock = { throw AN_EXCEPTION } ), - diagnosticPushHandler = diagnosticPushHandler, - clock = FakeSystemClock(), - stringProvider = FakeStringProvider(), ) sut.runAndTestState { assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) @@ -139,14 +123,12 @@ class PushLoopbackTestTest { @Test fun `test PushLoopbackTest push is received`() = runTest { val diagnosticPushHandler = DiagnosticPushHandler() - val sut = PushLoopbackTest( + val sut = createPushLoopbackTest( pushService = FakePushService(testPushBlock = { diagnosticPushHandler.handlePush() true }), diagnosticPushHandler = diagnosticPushHandler, - clock = FakeSystemClock(), - stringProvider = FakeStringProvider(), ) sut.runAndTestState { assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) @@ -156,3 +138,17 @@ class PushLoopbackTestTest { } } } + +private fun createPushLoopbackTest( + sessionId: SessionId = A_SESSION_ID, + pushService: PushService = FakePushService(), + diagnosticPushHandler: DiagnosticPushHandler = DiagnosticPushHandler(), + clock: SystemClock = FakeSystemClock(), + stringProvider: StringProvider = FakeStringProvider(), +) = PushLoopbackTest( + sessionId = sessionId, + pushService = pushService, + diagnosticPushHandler = diagnosticPushHandler, + clock = clock, + stringProvider = stringProvider +) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt index b274f0bdab..ce94d389ba 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/unregistration/DefaultServiceUnregisteredHandlerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/unregistration/DefaultServiceUnregisteredHandlerTest.kt new file mode 100644 index 0000000000..bda82a3c3e --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/unregistration/DefaultServiceUnregisteredHandlerTest.kt @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.impl.unregistration + +import android.app.Notification +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import io.element.android.appconfig.NotificationConfig +import io.element.android.features.enterprise.api.EnterpriseService +import io.element.android.features.enterprise.test.FakeEnterpriseService +import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.A_SESSION_ID_2 +import io.element.android.libraries.push.impl.notifications.NotificationDisplayer +import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams +import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator +import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator +import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer +import io.element.android.libraries.push.impl.notifications.fixtures.A_NOTIFICATION +import io.element.android.libraries.sessionstorage.api.SessionStore +import io.element.android.libraries.sessionstorage.test.InMemorySessionStore +import io.element.android.libraries.sessionstorage.test.aSessionData +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class DefaultServiceUnregisteredHandlerTest { + @Test + fun `handle will create a notification and render it`() = runTest { + val notification = A_NOTIFICATION + val createUnregistrationNotificationResult = lambdaRecorder { notification } + val displayUnregistrationNotificationResult = lambdaRecorder { true } + val sut = createDefaultServiceUnregisteredHandler( + notificationCreator = FakeNotificationCreator( + createUnregistrationNotificationResult = createUnregistrationNotificationResult, + ), + notificationDisplayer = FakeNotificationDisplayer( + displayUnregistrationNotificationResult = displayUnregistrationNotificationResult, + ) + ) + sut.handle(A_SESSION_ID) + createUnregistrationNotificationResult.assertions().isCalledOnce().with( + value( + NotificationAccountParams( + MatrixUser( + userId = A_SESSION_ID, + displayName = null, + avatarUrl = null, + ), + color = NotificationConfig.NOTIFICATION_ACCENT_COLOR, + showSessionId = false, + ) + ) + ) + displayUnregistrationNotificationResult.assertions().isCalledOnce().with( + value(notification) + ) + } + + @Test + fun `handle will create a notification and render it - custom color and multi accounts`() = runTest { + val notification = A_NOTIFICATION + val createUnregistrationNotificationResult = lambdaRecorder { notification } + val displayUnregistrationNotificationResult = lambdaRecorder { true } + val sut = createDefaultServiceUnregisteredHandler( + enterpriseService = FakeEnterpriseService( + initialBrandColor = Color.Red, + ), + notificationCreator = FakeNotificationCreator( + createUnregistrationNotificationResult = createUnregistrationNotificationResult, + ), + notificationDisplayer = FakeNotificationDisplayer( + displayUnregistrationNotificationResult = displayUnregistrationNotificationResult, + ), + sessionStore = InMemorySessionStore( + initialList = listOf( + aSessionData(sessionId = A_SESSION_ID.value), + aSessionData(sessionId = A_SESSION_ID_2.value), + ) + ) + ) + sut.handle(A_SESSION_ID) + createUnregistrationNotificationResult.assertions().isCalledOnce().with( + value( + NotificationAccountParams( + MatrixUser( + userId = A_SESSION_ID, + displayName = null, + avatarUrl = null, + ), + color = Color.Red.toArgb(), + showSessionId = true, + ) + ) + ) + displayUnregistrationNotificationResult.assertions().isCalledOnce().with( + value(notification) + ) + } + + private fun createDefaultServiceUnregisteredHandler( + enterpriseService: EnterpriseService = FakeEnterpriseService(), + notificationCreator: NotificationCreator = FakeNotificationCreator(), + notificationDisplayer: NotificationDisplayer = FakeNotificationDisplayer(), + sessionStore: SessionStore = InMemorySessionStore(), + ) = DefaultServiceUnregisteredHandler( + enterpriseService = enterpriseService, + notificationCreator = notificationCreator, + notificationDisplayer = notificationDisplayer, + sessionStore = sessionStore, + ) +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/unregistration/FakeServiceUnregisteredHandler.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/unregistration/FakeServiceUnregisteredHandler.kt new file mode 100644 index 0000000000..0ae4190973 --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/unregistration/FakeServiceUnregisteredHandler.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.impl.unregistration + +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeServiceUnregisteredHandler( + private val handleResult: (UserId) -> Unit = { lambdaError() }, +) : ServiceUnregisteredHandler { + override suspend fun handle(userId: UserId) { + handleResult(userId) + } +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/FetchNotificationWorkerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/FetchNotificationWorkerTest.kt new file mode 100644 index 0000000000..d40ef17b53 --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/FetchNotificationWorkerTest.kt @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.impl.workmanager + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import androidx.work.Data +import androidx.work.ListenableWorker +import androidx.work.WorkerParameters +import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor +import androidx.work.workDataOf +import com.google.common.truth.Truth.assertThat +import com.google.common.util.concurrent.ListenableFuture +import io.element.android.features.networkmonitor.api.NetworkStatus +import io.element.android.features.networkmonitor.test.FakeNetworkMonitor +import io.element.android.libraries.androidutils.json.DefaultJsonProvider +import io.element.android.libraries.push.api.push.SyncOnNotifiableEvent +import io.element.android.libraries.push.impl.notifications.FakeNotifiableEventResolver +import io.element.android.libraries.push.impl.notifications.NotificationResolverQueue +import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent +import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent +import io.element.android.libraries.push.test.notifications.FakeNotificationResolverQueue +import io.element.android.libraries.workmanager.api.WorkManagerRequest +import io.element.android.libraries.workmanager.api.di.MetroWorkerFactory +import io.element.android.libraries.workmanager.test.FakeWorkManagerScheduler +import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import java.util.UUID +import java.util.concurrent.Executor +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import kotlin.time.Duration.Companion.seconds + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class FetchNotificationWorkerTest { + @Test + fun `test - success`() = runTest { + var synced = false + val syncOnNotifiableEventLambda = SyncOnNotifiableEvent { synced = true } + + val queue = FakeNotificationResolverQueue( + processingLambda = { Result.success(ResolvedPushEvent.Event(aNotifiableMessageEvent())) } + ) + val worker = createWorker( + input = """ + [ + { + "session_id": "@alice:matrix.org", + "room_id": "!roomid:matrix.org", + "event_id": "$1436ebk:matrix.org", + "provider_info": "some_info" + } + ] + """.trimIndent(), + queue = queue, + syncOnNotifiableEvent = syncOnNotifiableEventLambda, + ) + + val result = worker.doWork() + + // The process finished successfully + assertThat(result).isEqualTo(ListenableWorker.Result.success()) + + // A result was emitted + assertThat(queue.results.replayCache).isNotEmpty() + + // An opportunistic sync was triggered + assertThat(synced).isTrue() + } + + @Test + fun `test - invalid input fails the work`() = runTest { + val worker = createWorker( + input = """ + [ + { + "session_id": "!alice:matrix.org", + "room_id": "!roomid:matrix.org", + "event_id": "$1436ebk:matrix.org", + "provider_info": "some_info" + } + ] + """.trimIndent(), + ) + + val result = worker.doWork() + + // The process failed + assertThat(result).isEqualTo(ListenableWorker.Result.failure()) + } + + @Test + fun `test - no network connectivity fails the work`() = runTest { + val networkMonitor = FakeNetworkMonitor(initialStatus = NetworkStatus.Disconnected) + val worker = createWorker( + input = """ + [ + { + "session_id": "@alice:matrix.org", + "room_id": "!roomid:matrix.org", + "event_id": "$1436ebk:matrix.org", + "provider_info": "some_info" + } + ] + """.trimIndent(), + networkMonitor = networkMonitor, + ) + + val result = worker.doWork() + + advanceTimeBy(10.seconds) + + // The process failed due to a timeout in getting the network connectivity, a retry is scheduled + assertThat(result).isEqualTo(ListenableWorker.Result.retry()) + } + + @Test + fun `test - failing to resolve events re-schedules the work`() = runTest { + val submitWorkerLambda = lambdaRecorder {} + val scheduler = FakeWorkManagerScheduler(submitLambda = submitWorkerLambda) + + val resolver = FakeNotifiableEventResolver( + resolveEventsResult = { _, _ -> Result.failure(Exception("Failed to resolve events")) } + ) + + val worker = createWorker( + input = """ + [ + { + "session_id": "@alice:matrix.org", + "room_id": "!roomid:matrix.org", + "event_id": "$1436ebk:matrix.org", + "provider_info": "some_info" + } + ] + """.trimIndent(), + eventResolver = resolver, + workManagerScheduler = scheduler, + ) + + val result = worker.doWork() + + // The process was considered successful, but a retry was scheduled due to the failure to resolve events + assertThat(result).isEqualTo(ListenableWorker.Result.success()) + submitWorkerLambda.assertions().isCalledOnce() + } + + private fun TestScope.createWorker( + input: String, + networkMonitor: FakeNetworkMonitor = FakeNetworkMonitor(), + eventResolver: FakeNotifiableEventResolver = FakeNotifiableEventResolver(resolveEventsResult = { _, _ -> Result.success(emptyMap()) }), + queue: NotificationResolverQueue = FakeNotificationResolverQueue( + processingLambda = { Result.success(ResolvedPushEvent.Event(aNotifiableMessageEvent())) } + ), + workManagerScheduler: FakeWorkManagerScheduler = FakeWorkManagerScheduler(), + syncOnNotifiableEvent: SyncOnNotifiableEvent = SyncOnNotifiableEvent {}, + ) = FetchNotificationsWorker( + workerParams = createWorkerParams(workDataOf("requests" to input)), + context = InstrumentationRegistry.getInstrumentation().context, + networkMonitor = networkMonitor, + eventResolver = eventResolver, + queue = queue, + workManagerScheduler = workManagerScheduler, + syncOnNotifiableEvent = syncOnNotifiableEvent, + coroutineDispatchers = testCoroutineDispatchers(), + workerDataConverter = SyncNotificationsWorkerDataConverter(DefaultJsonProvider()), + buildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(33), + ) + + private fun TestScope.createWorkerParams( + inputData: Data = Data.EMPTY, + ): WorkerParameters = WorkerParameters( + UUID.randomUUID(), + inputData, + emptySet(), + WorkerParameters.RuntimeExtras(), + 0, + 0, + Executors.newSingleThreadExecutor(), + backgroundScope.coroutineContext, + WorkManagerTaskExecutor(Executors.newSingleThreadExecutor()), + MetroWorkerFactory(emptyMap()), + { context, id, data -> FakeListenableFuture() }, + { context, id, foregroundInfo -> FakeListenableFuture() }, + ) +} + +class FakeListenableFuture : ListenableFuture { + override fun addListener(listener: Runnable, executor: Executor) = Unit + override fun cancel(mayInterruptIfRunning: Boolean): Boolean = true + override fun get(): T? = null + override fun get(timeout: Long, unit: TimeUnit?): T? = null + override fun isCancelled(): Boolean = false + override fun isDone(): Boolean = false +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationWorkManagerRequestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationWorkManagerRequestTest.kt new file mode 100644 index 0000000000..1f8d646e2b --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/SyncNotificationWorkManagerRequestTest.kt @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.impl.workmanager + +import androidx.work.OneTimeWorkRequest +import androidx.work.hasKeyWithValueOfType +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.androidutils.json.DefaultJsonProvider +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.push.api.push.NotificationEventRequest +import io.element.android.libraries.push.impl.notifications.fixtures.aNotificationEventRequest +import io.element.android.libraries.workmanager.api.WorkManagerRequestType +import io.element.android.libraries.workmanager.api.workManagerTag +import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider +import kotlinx.coroutines.test.runTest +import org.junit.Test +import kotlin.collections.first + +class SyncNotificationWorkManagerRequestTest { + @Test + fun `build - success API 33`() = runTest { + val request = createSyncNotificationWorkManagerRequest( + sessionId = A_SESSION_ID, + notificationEventRequests = listOf(aNotificationEventRequest()), + sdkVersion = 33, + ) + + val result = request.build() + assertThat(result.isSuccess).isTrue() + result.getOrNull()!!.first().run { + assertThat(this).isInstanceOf(OneTimeWorkRequest::class.java) + assertThat(workSpec.input.hasKeyWithValueOfType("requests")).isTrue() + // True in API 33+ + assertThat(workSpec.expedited).isTrue() + assertThat(workSpec.traceTag).isEqualTo(workManagerTag(A_SESSION_ID, WorkManagerRequestType.NOTIFICATION_SYNC)) + } + } + + @Test + fun `build - success API 32 and lower`() = runTest { + val request = createSyncNotificationWorkManagerRequest( + sessionId = A_SESSION_ID, + notificationEventRequests = listOf(aNotificationEventRequest()), + sdkVersion = 32, + ) + + val result = request.build() + assertThat(result.isSuccess).isTrue() + result.getOrNull()!!.first().run { + assertThat(this).isInstanceOf(OneTimeWorkRequest::class.java) + assertThat(workSpec.input.hasKeyWithValueOfType("requests")).isTrue() + // False before API 33 + assertThat(workSpec.expedited).isFalse() + assertThat(workSpec.traceTag).isEqualTo(workManagerTag(A_SESSION_ID, WorkManagerRequestType.NOTIFICATION_SYNC)) + } + } + + @Test + fun `build - empty list of requests fails`() = runTest { + val request = createSyncNotificationWorkManagerRequest( + sessionId = A_SESSION_ID, + notificationEventRequests = emptyList() + ) + + val result = request.build() + assertThat(result.isFailure).isTrue() + } + + @Test + fun `build - invalid serialization`() = runTest { + val request = createSyncNotificationWorkManagerRequest( + sessionId = A_SESSION_ID, + notificationEventRequests = listOf(aNotificationEventRequest()), + workerDataConverter = SyncNotificationsWorkerDataConverter({ error("error during serialization") }) + ) + val result = request.build() + assertThat(result.isFailure).isTrue() + } +} + +private fun createSyncNotificationWorkManagerRequest( + sessionId: SessionId, + notificationEventRequests: List, + workerDataConverter: SyncNotificationsWorkerDataConverter = SyncNotificationsWorkerDataConverter(DefaultJsonProvider()), + sdkVersion: Int = 33, +) = SyncNotificationWorkManagerRequest( + sessionId = sessionId, + notificationEventRequests = notificationEventRequests, + workerDataConverter = workerDataConverter, + buildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(sdkVersion), +) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/WorkerDataConverterTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/WorkerDataConverterTest.kt new file mode 100644 index 0000000000..85b55e0d62 --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/workmanager/WorkerDataConverterTest.kt @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.impl.workmanager + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.androidutils.json.DefaultJsonProvider +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.AN_EVENT_ID_2 +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_ROOM_ID_2 +import io.element.android.libraries.matrix.test.A_ROOM_ID_3 +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.A_SESSION_ID_2 +import io.element.android.libraries.push.api.push.NotificationEventRequest +import org.junit.Test + +class WorkerDataConverterTest { + @Test + fun `ensure identity when serializing - deserializing an empty list`() { + testIdentity(emptyList()) + } + + @Test + fun `ensure identity when serializing - deserializing a list`() { + testIdentity( + listOf( + NotificationEventRequest( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + eventId = AN_EVENT_ID, + providerInfo = "info1", + ), + NotificationEventRequest( + sessionId = A_SESSION_ID_2, + roomId = A_ROOM_ID_2, + eventId = AN_EVENT_ID_2, + providerInfo = "info2", + ), + ) + ) + } + + @Test + fun `serializing lots of data leads to several work data generated - one room - 100 events should be split in 5 chunks`() { + val data = List(100) { + NotificationEventRequest( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + eventId = EventId(AN_EVENT_ID.value + it), + providerInfo = "info$it", + ) + } + val sut = SyncNotificationsWorkerDataConverter(DefaultJsonProvider()) + val serialized = sut.serialize(data) + assertThat(serialized.getOrNull()?.size).isGreaterThan(1) + assertThat(serialized.getOrNull()?.size).isEqualTo(100 / SyncNotificationsWorkerDataConverter.CHUNK_SIZE) + // All the items are present + val deserialized = serialized.getOrNull()?.flatMap { sut.deserialize(it)!! } + assertThat(deserialized).containsExactlyElementsIn(data) + } + + @Test + fun `serializing lots of data leads to several work data generated - one room - 101 events should be split in 6 chunks`() { + val data = List(101) { + NotificationEventRequest( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + eventId = EventId(AN_EVENT_ID.value + it), + providerInfo = "info$it", + ) + } + val sut = SyncNotificationsWorkerDataConverter(DefaultJsonProvider()) + val serialized = sut.serialize(data) + assertThat(serialized.getOrNull()?.size).isGreaterThan(1) + assertThat(serialized.getOrNull()?.size).isEqualTo(100 / SyncNotificationsWorkerDataConverter.CHUNK_SIZE + 1) + // All the items are present + val deserialized = serialized.getOrNull()?.flatMap { sut.deserialize(it)!! } + assertThat(deserialized).containsExactlyElementsIn(data) + } + + @Test + fun `serializing lots of data leads to several work data generated - 3 rooms - 25 events should be split in 2 chunks and room not mixed`() { + val data1 = List(15) { + NotificationEventRequest( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + eventId = EventId(AN_EVENT_ID.value + it), + providerInfo = "info".repeat(100) + it, + ) + } + val data2 = List(3) { + NotificationEventRequest( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID_2, + eventId = EventId(AN_EVENT_ID.value + it), + providerInfo = "info".repeat(100) + it, + ) + } + val data3 = List(7) { + NotificationEventRequest( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID_3, + eventId = EventId(AN_EVENT_ID.value + it), + providerInfo = "info".repeat(100) + it, + ) + } + val data = (data1 + data2 + data3).shuffled() + val sut = SyncNotificationsWorkerDataConverter(DefaultJsonProvider()) + val serialized = sut.serialize(data) + assertThat(serialized.getOrNull()?.size).isEqualTo(2) + // All the items are present + val deserialized = serialized.getOrNull()?.flatMap { sut.deserialize(it)!! } + assertThat(deserialized).containsExactlyElementsIn(data) + // Rooms are not mixed between the chunks + val setsOfRooms = serialized.getOrNull()!! + .map { workData -> sut.deserialize(workData)!! } + .map { + it.map { request -> request.roomId }.toSet() + } + // Ensure that all sets are distinct + assertThat(setsOfRooms.size).isEqualTo(2) + // 3 roomId are present + assertThat(setsOfRooms.flatten().toSet()).containsExactly(A_ROOM_ID, A_ROOM_ID_2, A_ROOM_ID_3) + // No intersection between sets + assertThat(setsOfRooms[0].intersect(setsOfRooms[1])).isEmpty() + } + + private fun testIdentity(data: List) { + val sut = SyncNotificationsWorkerDataConverter(DefaultJsonProvider()) + val serialized = sut.serialize(data).getOrThrow() + val result = sut.deserialize(serialized.first()) + assertThat(result).isEqualTo(data) + } +} diff --git a/libraries/push/test/build.gradle.kts b/libraries/push/test/build.gradle.kts index fa0db0738b..3a0b5532ae 100644 --- a/libraries/push/test/build.gradle.kts +++ b/libraries/push/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,6 +17,7 @@ android { dependencies { api(projects.libraries.push.api) api(projects.libraries.pushproviders.api) + implementation(projects.libraries.designsystem) implementation(projects.libraries.push.impl) implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrixui) diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakeGetCurrentPushProvider.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakeGetCurrentPushProvider.kt index e2fc48aada..f9cae82f8c 100644 --- a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakeGetCurrentPushProvider.kt +++ b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakeGetCurrentPushProvider.kt @@ -1,16 +1,18 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.push.test +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.push.api.GetCurrentPushProvider class FakeGetCurrentPushProvider( private val currentPushProvider: String? ) : GetCurrentPushProvider { - override suspend fun getCurrentPushProvider(): String? = currentPushProvider + override suspend fun getCurrentPushProvider(sessionId: SessionId): String? = currentPushProvider } diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt index 553ac09465..604ff88728 100644 --- a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt +++ b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,6 +10,7 @@ package io.element.android.libraries.push.test import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.push.api.PushService import io.element.android.libraries.push.api.history.PushHistoryItem import io.element.android.libraries.pushproviders.api.Distributor @@ -19,19 +21,21 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow class FakePushService( - private val testPushBlock: suspend () -> Boolean = { true }, + private val testPushBlock: suspend (SessionId) -> Boolean = { true }, private val availablePushProviders: List = emptyList(), - private val registerWithLambda: suspend (MatrixClient, PushProvider, Distributor) -> Result = { _, _, _ -> + private val registerWithLambda: (MatrixClient, PushProvider, Distributor) -> Result = { _, _, _ -> Result.success(Unit) }, - private val currentPushProvider: () -> PushProvider? = { availablePushProviders.firstOrNull() }, + private val currentPushProvider: (SessionId) -> PushProvider? = { availablePushProviders.firstOrNull() }, private val selectPushProviderLambda: suspend (SessionId, PushProvider) -> Unit = { _, _ -> lambdaError() }, private val setIgnoreRegistrationErrorLambda: (SessionId, Boolean) -> Unit = { _, _ -> lambdaError() }, private val resetPushHistoryResult: () -> Unit = { lambdaError() }, private val resetBatteryOptimizationStateResult: () -> Unit = { lambdaError() }, + private val onServiceUnregisteredResult: (UserId) -> Unit = { lambdaError() }, + private val ensurePusherIsRegisteredResult: () -> Result = { lambdaError() }, ) : PushService { - override suspend fun getCurrentPushProvider(): PushProvider? { - return registeredPushProvider ?: currentPushProvider() + override suspend fun getCurrentPushProvider(sessionId: SessionId): PushProvider? { + return registeredPushProvider ?: currentPushProvider(sessionId) } override fun getAvailablePushProviders(): List { @@ -53,6 +57,10 @@ class FakePushService( } } + override suspend fun ensurePusherIsRegistered(matrixClient: MatrixClient): Result { + return ensurePusherIsRegisteredResult() + } + override suspend fun selectPushProvider(sessionId: SessionId, pushProvider: PushProvider) { selectPushProviderLambda(sessionId, pushProvider) } @@ -68,8 +76,8 @@ class FakePushService( setIgnoreRegistrationErrorLambda(sessionId, ignore) } - override suspend fun testPush(): Boolean = simulateLongTask { - testPushBlock() + override suspend fun testPush(sessionId: SessionId): Boolean = simulateLongTask { + testPushBlock(sessionId) } private val pushHistoryItemsFlow = MutableStateFlow>(emptyList()) @@ -97,4 +105,8 @@ class FakePushService( override suspend fun resetBatteryOptimizationState() { resetBatteryOptimizationStateResult() } + + override suspend fun onServiceUnregistered(userId: UserId) { + onServiceUnregisteredResult(userId) + } } diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePusherSubscriber.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePusherSubscriber.kt index 7148409acf..50ed06251c 100644 --- a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePusherSubscriber.kt +++ b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePusherSubscriber.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeCallNotificationEventResolver.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeCallNotificationEventResolver.kt index 947d57ee7e..f923d0c9fe 100644 --- a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeCallNotificationEventResolver.kt +++ b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeCallNotificationEventResolver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeImageLoader.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeImageLoader.kt deleted file mode 100644 index 067376cfbc..0000000000 --- a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeImageLoader.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.push.test.notifications - -import android.graphics.Color -import android.graphics.drawable.ColorDrawable -import coil3.ImageLoader -import coil3.test.FakeImageLoaderEngine -import coil3.test.intercept -import org.robolectric.RuntimeEnvironment - -class FakeImageLoader { - private val coilRequests = mutableListOf() - - private var cache: ImageLoader? = null - - fun getImageLoader(): ImageLoader { - return cache ?: ImageLoader.Builder(RuntimeEnvironment.getApplication()) - .components { - val engine = FakeImageLoaderEngine.Builder() - .intercept( - predicate = { - coilRequests.add(it) - true - }, - drawable = ColorDrawable(Color.BLUE) - ) - .build() - add(engine) - } - .build() - .also { - cache = it - } - } - - fun getCoilRequests(): List { - return coilRequests.toList() - } -} diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeNotificationCleaner.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeNotificationCleaner.kt index be6a31ca75..28a249dc9b 100644 --- a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeNotificationCleaner.kt +++ b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeNotificationCleaner.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,12 +11,14 @@ package io.element.android.libraries.push.test.notifications 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.api.notifications.NotificationCleaner import io.element.android.tests.testutils.lambda.lambdaError class FakeNotificationCleaner( private val clearAllMessagesEventsLambda: (SessionId) -> Unit = { lambdaError() }, private val clearMessagesForRoomLambda: (SessionId, RoomId) -> Unit = { _, _ -> lambdaError() }, + private val clearMessagesForThreadLambda: (SessionId, RoomId, ThreadId) -> Unit = { _, _, _ -> lambdaError() }, private val clearEventLambda: (SessionId, EventId) -> Unit = { _, _ -> lambdaError() }, private val clearMembershipNotificationForSessionLambda: (SessionId) -> Unit = { lambdaError() }, private val clearMembershipNotificationForRoomLambda: (SessionId, RoomId) -> Unit = { _, _ -> lambdaError() } @@ -28,6 +31,10 @@ class FakeNotificationCleaner( clearMessagesForRoomLambda(sessionId, roomId) } + override fun clearMessagesForThread(sessionId: SessionId, roomId: RoomId, threadId: ThreadId) { + clearMessagesForThreadLambda(sessionId, roomId, threadId) + } + override fun clearEvent(sessionId: SessionId, eventId: EventId) { clearEventLambda(sessionId, eventId) } diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeNotificationResolverQueue.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeNotificationResolverQueue.kt new file mode 100644 index 0000000000..d4279ab028 --- /dev/null +++ b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeNotificationResolverQueue.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.push.test.notifications + +import io.element.android.libraries.push.api.push.NotificationEventRequest +import io.element.android.libraries.push.impl.notifications.NotificationResolverQueue +import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent +import kotlinx.coroutines.flow.MutableSharedFlow + +class FakeNotificationResolverQueue( + private val processingLambda: suspend (NotificationEventRequest) -> Result, +) : NotificationResolverQueue { + override val results = MutableSharedFlow, Map>>>(replay = 1) + + override suspend fun enqueue(request: NotificationEventRequest) { + results.emit(listOf(request) to mapOf(request to processingLambda(request))) + } +} diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeOnMissedCallNotificationHandler.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeOnMissedCallNotificationHandler.kt index 10d7ba7c78..5e8063d788 100644 --- a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeOnMissedCallNotificationHandler.kt +++ b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/FakeOnMissedCallNotificationHandler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/conversations/FakeNotificationConversationService.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/conversations/FakeNotificationConversationService.kt index 4076171ca6..0c8d870448 100644 --- a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/conversations/FakeNotificationConversationService.kt +++ b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/conversations/FakeNotificationConversationService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/push/FakeNotificationBitmapLoader.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/push/FakeNotificationBitmapLoader.kt index c44837b230..78af0e56c1 100644 --- a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/push/FakeNotificationBitmapLoader.kt +++ b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/notifications/push/FakeNotificationBitmapLoader.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,17 +11,18 @@ package io.element.android.libraries.push.test.notifications.push import android.graphics.Bitmap import androidx.core.graphics.drawable.IconCompat import coil3.ImageLoader +import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader class FakeNotificationBitmapLoader( - var getRoomBitmapResult: (String?, ImageLoader, Long) -> Bitmap? = { _, _, _ -> null }, - var getUserIconResult: (String?, ImageLoader) -> IconCompat? = { _, _ -> null }, + var getRoomBitmapResult: (AvatarData, ImageLoader, Long) -> Bitmap? = { _, _, _ -> null }, + var getUserIconResult: (AvatarData, ImageLoader) -> IconCompat? = { _, _ -> null }, ) : NotificationBitmapLoader { - override suspend fun getRoomBitmap(path: String?, imageLoader: ImageLoader, targetSize: Long): Bitmap? { - return getRoomBitmapResult(path, imageLoader, targetSize) + override suspend fun getRoomBitmap(avatarData: AvatarData, imageLoader: ImageLoader, targetSize: Long): Bitmap? { + return getRoomBitmapResult(avatarData, imageLoader, targetSize) } - override suspend fun getUserIcon(path: String?, imageLoader: ImageLoader): IconCompat? { - return getUserIconResult(path, imageLoader) + override suspend fun getUserIcon(avatarData: AvatarData, imageLoader: ImageLoader): IconCompat? { + return getUserIconResult(avatarData, imageLoader) } } diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/test/FakePushHandler.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/test/FakePushHandler.kt index 1279d6e5a0..a7e476be9c 100644 --- a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/test/FakePushHandler.kt +++ b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/test/FakePushHandler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/api/build.gradle.kts b/libraries/pushproviders/api/build.gradle.kts index 587b38f617..3581a43952 100644 --- a/libraries/pushproviders/api/build.gradle.kts +++ b/libraries/pushproviders/api/build.gradle.kts @@ -1,11 +1,12 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { - id("io.element.android-library") + id("io.element.android-compose-library") } android { diff --git a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/CurrentUserPushConfig.kt b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/Config.kt similarity index 68% rename from libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/CurrentUserPushConfig.kt rename to libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/Config.kt index 0d3bf0241f..fec79067db 100644 --- a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/CurrentUserPushConfig.kt +++ b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/Config.kt @@ -1,13 +1,14 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.pushproviders.api -data class CurrentUserPushConfig( +data class Config( val url: String, val pushKey: String, ) diff --git a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/Distributor.kt b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/Distributor.kt index d623740444..e7082f8d5b 100644 --- a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/Distributor.kt +++ b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/Distributor.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushData.kt b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushData.kt index 6000f74a95..b8fb5378b5 100644 --- a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushData.kt +++ b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushData.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushHandler.kt b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushHandler.kt index a9dfd76c2c..d3105111c5 100644 --- a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushHandler.kt +++ b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushHandler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushProvider.kt b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushProvider.kt index 38a7135b75..405a83b9b8 100644 --- a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushProvider.kt +++ b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -59,7 +60,7 @@ interface PushProvider { */ suspend fun onSessionDeleted(sessionId: SessionId) - suspend fun getCurrentUserPushConfig(): CurrentUserPushConfig? + suspend fun getPushConfig(sessionId: SessionId): Config? fun canRotateToken(): Boolean diff --git a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PusherSubscriber.kt b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PusherSubscriber.kt index ef0cf9ddef..989170c975 100644 --- a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PusherSubscriber.kt +++ b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PusherSubscriber.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/firebase/build.gradle.kts b/libraries/pushproviders/firebase/build.gradle.kts index 05e60920e5..ee5bd942ff 100644 --- a/libraries/pushproviders/firebase/build.gradle.kts +++ b/libraries/pushproviders/firebase/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/firebase/src/main/AndroidManifest.xml b/libraries/pushproviders/firebase/src/main/AndroidManifest.xml index 946de39546..24bad30d77 100644 --- a/libraries/pushproviders/firebase/src/main/AndroidManifest.xml +++ b/libraries/pushproviders/firebase/src/main/AndroidManifest.xml @@ -1,7 +1,8 @@ diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseConfig.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseConfig.kt index f592a1ae6e..10891d515a 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseConfig.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseGatewayProvider.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseGatewayProvider.kt index a872808e16..1ae27d5d23 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseGatewayProvider.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseGatewayProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.pushproviders.firebase import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.features.enterprise.api.EnterpriseService interface FirebaseGatewayProvider { @@ -17,7 +17,6 @@ interface FirebaseGatewayProvider { } @ContributesBinding(AppScope::class) -@Inject class DefaultFirebaseGatewayProvider( private val enterpriseService: EnterpriseService, ) : FirebaseGatewayProvider { diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseNewTokenHandler.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseNewTokenHandler.kt index 2853d52685..72977afa3b 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseNewTokenHandler.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseNewTokenHandler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.pushproviders.firebase import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.extensions.flatMap import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.matrix.api.MatrixClientProvider @@ -30,7 +30,6 @@ interface FirebaseNewTokenHandler { } @ContributesBinding(AppScope::class) -@Inject class DefaultFirebaseNewTokenHandler( private val pusherSubscriber: PusherSubscriber, private val sessionStore: SessionStore, diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushParser.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushParser.kt index 410fc05014..a5b9bbcd23 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushParser.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushParser.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt index 4f8c99cd3e..0aa345769f 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,7 @@ import dev.zacsweers.metro.Inject import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.SessionId -import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig +import io.element.android.libraries.pushproviders.api.Config import io.element.android.libraries.pushproviders.api.Distributor import io.element.android.libraries.pushproviders.api.PushProvider import io.element.android.libraries.pushproviders.api.PusherSubscriber @@ -74,9 +75,9 @@ class FirebasePushProvider( */ override suspend fun onSessionDeleted(sessionId: SessionId) = Unit - override suspend fun getCurrentUserPushConfig(): CurrentUserPushConfig? { + override suspend fun getPushConfig(sessionId: SessionId): Config? { return firebaseStore.getFcmToken()?.let { fcmToken -> - CurrentUserPushConfig( + Config( url = firebaseGatewayProvider.getFirebaseGateway(), pushKey = fcmToken ) diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseStore.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseStore.kt index 91fec3bb57..1f6a4709cc 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseStore.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,7 +12,6 @@ import android.content.SharedPreferences import androidx.core.content.edit import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.onCompletion @@ -27,7 +27,6 @@ interface FirebaseStore { } @ContributesBinding(AppScope::class) -@Inject class SharedPreferencesFirebaseStore( private val sharedPreferences: SharedPreferences, ) : FirebaseStore { @@ -41,7 +40,7 @@ class SharedPreferencesFirebaseStore( if (k == PREFS_KEY_FCM_TOKEN) { try { flow.value = getFcmToken() - } catch (e: Exception) { + } catch (_: Exception) { flow.value = null } } diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenDeleter.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenDeleter.kt index 4a8be152ad..21fa189121 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenDeleter.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenDeleter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.libraries.pushproviders.firebase import com.google.firebase.messaging.FirebaseMessaging import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import timber.log.Timber import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -24,7 +24,6 @@ interface FirebaseTokenDeleter { } @ContributesBinding(AppScope::class) -@Inject class DefaultFirebaseTokenDeleter( private val isPlayServiceAvailable: IsPlayServiceAvailable, ) : FirebaseTokenDeleter { diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenGetter.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenGetter.kt index 4add5e4f8b..80fd9726d4 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenGetter.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenGetter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.libraries.pushproviders.firebase import com.google.firebase.messaging.FirebaseMessaging import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import timber.log.Timber import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -25,7 +25,6 @@ interface FirebaseTokenGetter { } @ContributesBinding(AppScope::class) -@Inject class DefaultFirebaseTokenGetter( private val isPlayServiceAvailable: IsPlayServiceAvailable, ) : FirebaseTokenGetter { diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenRotator.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenRotator.kt index c54221aa38..78b33cbb08 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenRotator.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenRotator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.pushproviders.firebase import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.extensions.runCatchingExceptions interface FirebaseTokenRotator { @@ -20,7 +20,6 @@ interface FirebaseTokenRotator { * This class delete the Firebase token and generate a new one. */ @ContributesBinding(AppScope::class) -@Inject class DefaultFirebaseTokenRotator( private val firebaseTokenDeleter: FirebaseTokenDeleter, private val firebaseTokenGetter: FirebaseTokenGetter, diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTroubleshooter.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTroubleshooter.kt index 132996ee34..78fce0338d 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTroubleshooter.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTroubleshooter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.pushproviders.firebase import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.extensions.runCatchingExceptions interface FirebaseTroubleshooter { @@ -20,7 +20,6 @@ interface FirebaseTroubleshooter { * This class force retrieving and storage of the Firebase token. */ @ContributesBinding(AppScope::class) -@Inject class DefaultFirebaseTroubleshooter( private val newTokenHandler: FirebaseNewTokenHandler, private val firebaseTokenGetter: FirebaseTokenGetter, diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/IsPlayServiceAvailable.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/IsPlayServiceAvailable.kt index 8e25407a91..828b7bb60a 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/IsPlayServiceAvailable.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/IsPlayServiceAvailable.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,7 +13,6 @@ import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailabilityLight import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.annotations.ApplicationContext import timber.log.Timber @@ -27,7 +27,6 @@ fun IsPlayServiceAvailable.checkAvailableOrThrow() { } @ContributesBinding(AppScope::class) -@Inject class DefaultIsPlayServiceAvailable( @ApplicationContext private val context: Context, ) : IsPlayServiceAvailable { diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/PushDataFirebase.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/PushDataFirebase.kt index fb33ab9c1f..04bdb12cfd 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/PushDataFirebase.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/PushDataFirebase.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingService.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingService.kt index 6d13143ddc..532ee8a4a1 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingService.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingServiceBindings.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingServiceBindings.kt index 0f3e109fef..6de938c5c2 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingServiceBindings.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingServiceBindings.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTest.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTest.kt index c4ffe19db3..69ffd504fd 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTest.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt index a886b5fb77..8bcd44911e 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/firebase/src/main/res/values-hr/translations.xml b/libraries/pushproviders/firebase/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..026949086d --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/res/values-hr/translations.xml @@ -0,0 +1,11 @@ + + + "Provjerite je li Firebase dostupan." + "Firebase nije dostupan." + "Firebase je dostupan." + "Provjeri Firebase" + "Provjerite je li Firebaseov token dostupan." + "Firebaseov token nije poznat." + "Firebaseov token: %1$s." + "Provjeri Firebaseov token" + diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/DefaultFirebaseNewTokenHandlerTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/DefaultFirebaseNewTokenHandlerTest.kt index 70cd24e6c8..4db9ea667f 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/DefaultFirebaseNewTokenHandlerTest.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/DefaultFirebaseNewTokenHandlerTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseGatewayProvider.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseGatewayProvider.kt index 3540562a3d..cdd50e9af3 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseGatewayProvider.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseGatewayProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseNewTokenHandler.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseNewTokenHandler.kt index 87c7495693..2fa9508103 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseNewTokenHandler.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseNewTokenHandler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseTokenRotator.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseTokenRotator.kt index 568ef30ae6..d504aa32ae 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseTokenRotator.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseTokenRotator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseTroubleshooter.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseTroubleshooter.kt index ceabcd0c58..6563c7d398 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseTroubleshooter.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseTroubleshooter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeIsPlayServiceAvailable.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeIsPlayServiceAvailable.kt index 1f5cfc72e6..226e57a795 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeIsPlayServiceAvailable.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeIsPlayServiceAvailable.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushParserTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushParserTest.kt index 49ed5bc6d4..d4a8ef6349 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushParserTest.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushParserTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProviderTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProviderTest.kt index 53ed52be07..312689da6e 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProviderTest.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProviderTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,7 @@ import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.push.test.FakePusherSubscriber -import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig +import io.element.android.libraries.pushproviders.api.Config import io.element.android.libraries.pushproviders.api.Distributor import io.element.android.libraries.pushproviders.api.PusherSubscriber import io.element.android.tests.testutils.lambda.lambdaRecorder @@ -152,7 +153,7 @@ class FirebasePushProviderTest { token = null ) ) - val result = firebasePushProvider.getCurrentUserPushConfig() + val result = firebasePushProvider.getPushConfig(A_SESSION_ID) assertThat(result).isNull() } @@ -163,8 +164,8 @@ class FirebasePushProviderTest { token = "aToken" ), ) - val result = firebasePushProvider.getCurrentUserPushConfig() - assertThat(result).isEqualTo(CurrentUserPushConfig(A_FIREBASE_GATEWAY, "aToken")) + val result = firebasePushProvider.getPushConfig(A_SESSION_ID) + assertThat(result).isEqualTo(Config(A_FIREBASE_GATEWAY, "aToken")) } @Test diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/InMemoryFirebaseStore.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/InMemoryFirebaseStore.kt index e64f3338eb..3e6292f445 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/InMemoryFirebaseStore.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/InMemoryFirebaseStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingServiceTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingServiceTest.kt index 6b8c19934f..1140d6f45e 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingServiceTest.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/VectorFirebaseMessagingServiceTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTestTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTestTest.kt index ce617799f5..22bc762a32 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTestTest.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTestTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt index 7e6981ef57..8f9721a17d 100644 --- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/test/build.gradle.kts b/libraries/pushproviders/test/build.gradle.kts index 90b88c1313..61143e7ffc 100644 --- a/libraries/pushproviders/test/build.gradle.kts +++ b/libraries/pushproviders/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/pushproviders/test/src/main/kotlin/io/element/android/libraries/pushproviders/test/FakePushProvider.kt b/libraries/pushproviders/test/src/main/kotlin/io/element/android/libraries/pushproviders/test/FakePushProvider.kt index afb4d833d4..86cb4a468f 100644 --- a/libraries/pushproviders/test/src/main/kotlin/io/element/android/libraries/pushproviders/test/FakePushProvider.kt +++ b/libraries/pushproviders/test/src/main/kotlin/io/element/android/libraries/pushproviders/test/FakePushProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,7 @@ package io.element.android.libraries.pushproviders.test import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.SessionId -import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig +import io.element.android.libraries.pushproviders.api.Config import io.element.android.libraries.pushproviders.api.Distributor import io.element.android.libraries.pushproviders.api.PushProvider import io.element.android.tests.testutils.lambda.lambdaError @@ -21,7 +22,7 @@ class FakePushProvider( private val distributors: List = listOf(Distributor("aDistributorValue", "aDistributorName")), private val currentDistributorValue: () -> String? = { lambdaError() }, private val currentDistributor: () -> Distributor? = { distributors.firstOrNull() }, - private val currentUserPushConfig: CurrentUserPushConfig? = null, + private val config: Config? = null, private val registerWithResult: (MatrixClient, Distributor) -> Result = { _, _ -> lambdaError() }, private val unregisterWithResult: (MatrixClient) -> Result = { lambdaError() }, private val onSessionDeletedLambda: (SessionId) -> Unit = { lambdaError() }, @@ -50,8 +51,8 @@ class FakePushProvider( onSessionDeletedLambda(sessionId) } - override suspend fun getCurrentUserPushConfig(): CurrentUserPushConfig? { - return currentUserPushConfig + override suspend fun getPushConfig(sessionId: SessionId): Config? { + return config } override fun canRotateToken(): Boolean { diff --git a/libraries/pushproviders/test/src/main/kotlin/io/element/android/libraries/pushproviders/test/Fixtures.kt b/libraries/pushproviders/test/src/main/kotlin/io/element/android/libraries/pushproviders/test/Fixtures.kt index cd3b95d672..0e8dcb91fa 100644 --- a/libraries/pushproviders/test/src/main/kotlin/io/element/android/libraries/pushproviders/test/Fixtures.kt +++ b/libraries/pushproviders/test/src/main/kotlin/io/element/android/libraries/pushproviders/test/Fixtures.kt @@ -1,18 +1,19 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.pushproviders.test -import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig +import io.element.android.libraries.pushproviders.api.Config -fun aCurrentUserPushConfig( +fun aSessionPushConfig( url: String = "aUrl", pushKey: String = "aPushKey", -) = CurrentUserPushConfig( +) = Config( url = url, pushKey = pushKey, ) diff --git a/libraries/pushproviders/unifiedpush/build.gradle.kts b/libraries/pushproviders/unifiedpush/build.gradle.kts index e416ccf8bf..1b24bc4b1f 100644 --- a/libraries/pushproviders/unifiedpush/build.gradle.kts +++ b/libraries/pushproviders/unifiedpush/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { @@ -23,6 +24,7 @@ dependencies { implementation(projects.libraries.androidutils) implementation(projects.libraries.core) implementation(projects.libraries.matrix.api) + implementation(projects.libraries.push.api) implementation(projects.libraries.uiStrings) api(projects.libraries.troubleshoot.api) @@ -30,7 +32,6 @@ dependencies { implementation(projects.libraries.pushproviders.api) implementation(projects.libraries.architecture) implementation(projects.libraries.core) - implementation(projects.services.appnavstate.api) implementation(projects.services.toolbox.api) implementation(projects.libraries.network) @@ -53,5 +54,4 @@ dependencies { testImplementation(projects.libraries.pushstore.test) testImplementation(projects.libraries.troubleshoot.test) testImplementation(projects.services.toolbox.test) - testImplementation(projects.services.appnavstate.test) } diff --git a/libraries/pushproviders/unifiedpush/src/main/AndroidManifest.xml b/libraries/pushproviders/unifiedpush/src/main/AndroidManifest.xml index e85f18e3c0..5845883c5b 100644 --- a/libraries/pushproviders/unifiedpush/src/main/AndroidManifest.xml +++ b/libraries/pushproviders/unifiedpush/src/main/AndroidManifest.xml @@ -1,7 +1,8 @@ ( + HttpURLConnection.HTTP_UNAUTHORIZED, + HttpURLConnection.HTTP_FORBIDDEN, + HttpURLConnection.HTTP_NOT_FOUND, + HttpURLConnection.HTTP_BAD_METHOD, + HttpURLConnection.HTTP_NOT_ACCEPTABLE + ) + } } diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushGatewayUrlResolver.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushGatewayUrlResolver.kt index 48f8153787..78a569a211 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushGatewayUrlResolver.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushGatewayUrlResolver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.pushproviders.unifiedpush import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject interface UnifiedPushGatewayUrlResolver { fun resolve( @@ -19,7 +19,6 @@ interface UnifiedPushGatewayUrlResolver { } @ContributesBinding(AppScope::class) -@Inject class DefaultUnifiedPushGatewayUrlResolver( private val unifiedPushStore: UnifiedPushStore, private val defaultPushGatewayHttpUrlProvider: DefaultPushGatewayHttpUrlProvider, diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt index a899c0787f..d7de923a26 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.pushproviders.unifiedpush import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.extensions.flatMap import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.matrix.api.MatrixClientProvider @@ -28,7 +28,6 @@ interface UnifiedPushNewGatewayHandler { } @ContributesBinding(AppScope::class) -@Inject class DefaultUnifiedPushNewGatewayHandler( private val pusherSubscriber: PusherSubscriber, private val userPushStoreFactory: UserPushStoreFactory, @@ -40,7 +39,7 @@ class DefaultUnifiedPushNewGatewayHandler( val userId = pushClientSecret.getUserIdFromSecret(clientSecret) ?: return Result.failure( IllegalStateException("Unable to retrieve session") ).also { - Timber.w("Unable to retrieve session") + Timber.tag(loggerTag.value).w("Unable to retrieve session") } val userDataStore = userPushStoreFactory.getOrCreate(userId) return if (userDataStore.getPushProviderName() == UnifiedPushConfig.NAME) { @@ -49,6 +48,9 @@ class DefaultUnifiedPushNewGatewayHandler( .flatMap { client -> pusherSubscriber.registerPusher(client, endpoint, pushGateway) } + .onFailure { + Timber.tag(loggerTag.value).w(it, "Unable to register pusher") + } } else { Timber.tag(loggerTag.value).d("This session is not using UnifiedPush pusher") Result.failure( diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParser.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParser.kt index f1290636c3..0cc9560c70 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParser.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParser.kt @@ -1,22 +1,23 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.pushproviders.unifiedpush import dev.zacsweers.metro.Inject +import io.element.android.libraries.androidutils.json.JsonProvider import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.pushproviders.api.PushData -import kotlinx.serialization.json.Json @Inject -class UnifiedPushParser { - private val json by lazy { Json { ignoreUnknownKeys = true } } - +class UnifiedPushParser( + private val json: JsonProvider, +) { fun parse(message: ByteArray, clientSecret: String): PushData? { - return tryOrNull { json.decodeFromString(String(message)) }?.toPushData(clientSecret) + return tryOrNull { json().decodeFromString(String(message)) }?.toPushData(clientSecret) } } diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt index 92d7c9ebc3..7c65e8e2e4 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,7 +13,7 @@ import dev.zacsweers.metro.ContributesIntoSet import dev.zacsweers.metro.Inject import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.SessionId -import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig +import io.element.android.libraries.pushproviders.api.Config import io.element.android.libraries.pushproviders.api.Distributor import io.element.android.libraries.pushproviders.api.PushProvider import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret @@ -25,7 +26,7 @@ class UnifiedPushProvider( private val unRegisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase, private val pushClientSecret: PushClientSecret, private val unifiedPushStore: UnifiedPushStore, - private val unifiedPushCurrentUserPushConfigProvider: UnifiedPushCurrentUserPushConfigProvider, + private val unifiedPushSessionPushConfigProvider: UnifiedPushSessionPushConfigProvider, ) : PushProvider { override val index = UnifiedPushConfig.INDEX override val name = UnifiedPushConfig.NAME @@ -62,8 +63,8 @@ class UnifiedPushProvider( unRegisterUnifiedPushUseCase.cleanup(clientSecret) } - override suspend fun getCurrentUserPushConfig(): CurrentUserPushConfig? { - return unifiedPushCurrentUserPushConfigProvider.provide() + override suspend fun getPushConfig(sessionId: SessionId): Config? { + return unifiedPushSessionPushConfigProvider.provide(sessionId) } override fun canRotateToken(): Boolean = false diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushRemovedGatewayHandler.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushRemovedGatewayHandler.kt new file mode 100644 index 0000000000..7baaaab1bd --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushRemovedGatewayHandler.kt @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.pushproviders.unifiedpush + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.SingleIn +import io.element.android.libraries.androidutils.throttler.FirstThrottler +import io.element.android.libraries.core.extensions.flatMap +import io.element.android.libraries.core.log.logger.LoggerTag +import io.element.android.libraries.di.annotations.AppCoroutineScope +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.MatrixClientProvider +import io.element.android.libraries.push.api.PushService +import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret +import kotlinx.coroutines.CoroutineScope +import timber.log.Timber + +private val loggerTag = LoggerTag("UnifiedPushRemovedGatewayHandler", LoggerTag.PushLoggerTag) + +/** + * Handle endpoint removal received from UnifiedPush. Will try to register again. + */ +fun interface UnifiedPushRemovedGatewayHandler { + suspend fun handle(clientSecret: String): Result +} + +@Inject +@SingleIn(AppScope::class) +class UnifiedPushRemovedGatewayThrottler( + @AppCoroutineScope + private val appCoroutineScope: CoroutineScope, +) { + private val firstThrottler = FirstThrottler( + minimumInterval = 60_000, + coroutineScope = appCoroutineScope, + ) + + fun canRegisterAgain(): Boolean { + return firstThrottler.canHandle() + } +} + +@ContributesBinding(AppScope::class) +class DefaultUnifiedPushRemovedGatewayHandler( + private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase, + private val pushClientSecret: PushClientSecret, + private val matrixClientProvider: MatrixClientProvider, + private val pushService: PushService, + private val unifiedPushRemovedGatewayThrottler: UnifiedPushRemovedGatewayThrottler, +) : UnifiedPushRemovedGatewayHandler { + /** + * The application has been informed by the UnifiedPush distributor that the topic has been deleted. + * So this code aim to unregister the pusher from the homeserver, register a new topic on the + * UnifiedPush application then register a new pusher to the homeserver. + * No registration will happen if the topic deletion has already occurred in the last minute. + */ + override suspend fun handle(clientSecret: String): Result { + val sessionId = pushClientSecret.getUserIdFromSecret(clientSecret) ?: return Result.failure( + IllegalStateException("Unable to retrieve session") + ).also { + Timber.tag(loggerTag.value).w("Unable to retrieve session") + } + return matrixClientProvider + .getOrRestore(sessionId) + .onFailure { + // Silently ignore this error (do not invoke onServiceUnregistered) + Timber.tag(loggerTag.value).w(it, "Fails to restore client") + } + .flatMap { client -> + client.rotateRegistration(clientSecret = clientSecret) + .onFailure { + Timber.tag(loggerTag.value).w(it, "Issue during pusher unregistration / re registration") + // Let the user know + pushService.onServiceUnregistered(sessionId) + } + } + } + + /** + * Unregister the pusher for the session. Then register again if possible. + */ + private suspend fun MatrixClient.rotateRegistration(clientSecret: String): Result { + val unregisterResult = unregisterUnifiedPushUseCase.unregister( + matrixClient = this, + clientSecret = clientSecret, + unregisterUnifiedPush = false, + ).onFailure { + Timber.tag(loggerTag.value).w(it, "Unable to unregister pusher") + } + return unregisterResult.flatMap { + registerAgain() + } + } + + /** + * Attempt to register again, if possible i.e. the current configuration is known and the + * deletion of data in the UnifiedPush application has not already occurred in the last minute. + */ + private suspend fun MatrixClient.registerAgain(): Result { + return if (unifiedPushRemovedGatewayThrottler.canRegisterAgain()) { + val pushProvider = pushService.getCurrentPushProvider(sessionId) + val distributor = pushProvider?.getCurrentDistributor(sessionId) + if (pushProvider != null && distributor != null) { + pushService.registerWith( + matrixClient = this, + pushProvider = pushProvider, + distributor = distributor, + ).onFailure { + Timber.tag(loggerTag.value).w(it, "Unable to register with current data") + } + } else { + Result.failure(IllegalStateException("Unable to register again")) + } + } else { + Timber.tag(loggerTag.value).w("Second removal in less than 1 minute, do not register again") + Result.failure(IllegalStateException("Too many requests to register again")) + } + } +} diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushSessionPushConfigProvider.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushSessionPushConfigProvider.kt new file mode 100644 index 0000000000..20ac923184 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushSessionPushConfigProvider.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.pushproviders.unifiedpush + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.pushproviders.api.Config +import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret + +interface UnifiedPushSessionPushConfigProvider { + suspend fun provide(sessionId: SessionId): Config? +} + +@ContributesBinding(AppScope::class) +class DefaultUnifiedPushPushConfigProvider( + private val pushClientSecret: PushClientSecret, + private val unifiedPushStore: UnifiedPushStore, +) : UnifiedPushSessionPushConfigProvider { + override suspend fun provide(sessionId: SessionId): Config? { + val clientSecret = pushClientSecret.getSecretForUser(sessionId) + val url = unifiedPushStore.getPushGateway(clientSecret) ?: return null + val pushKey = unifiedPushStore.getEndpoint(clientSecret) ?: return null + return Config( + url = url, + pushKey = pushKey, + ) + } +} diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushStore.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushStore.kt index 96c92b09d8..009c863d5c 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushStore.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,7 +13,6 @@ import android.content.SharedPreferences import androidx.core.content.edit import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.matrix.api.core.UserId @@ -26,7 +26,6 @@ interface UnifiedPushStore { } @ContributesBinding(AppScope::class) -@Inject class SharedPreferencesUnifiedPushStore( @ApplicationContext val context: Context, private val sharedPreferences: SharedPreferences, 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 429c23d747..2e0d6ea413 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.libraries.pushproviders.unifiedpush import android.content.Context import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.pushproviders.api.PusherSubscriber @@ -19,24 +19,34 @@ import timber.log.Timber interface UnregisterUnifiedPushUseCase { /** - * Unregister the app from the homeserver, then from UnifiedPush. + * Unregister the app from the homeserver, then from UnifiedPush if [unregisterUnifiedPush] is true. */ - suspend fun unregister(matrixClient: MatrixClient, clientSecret: String): Result + suspend fun unregister( + matrixClient: MatrixClient, + clientSecret: String, + unregisterUnifiedPush: Boolean = true, + ): Result /** * Cleanup any remaining data for the given client secret and unregister the app from UnifiedPush. */ - fun cleanup(clientSecret: String) + fun cleanup( + clientSecret: String, + unregisterUnifiedPush: Boolean = true, + ) } @ContributesBinding(AppScope::class) -@Inject class DefaultUnregisterUnifiedPushUseCase( @ApplicationContext private val context: Context, private val unifiedPushStore: UnifiedPushStore, private val pusherSubscriber: PusherSubscriber, ) : UnregisterUnifiedPushUseCase { - override suspend fun unregister(matrixClient: MatrixClient, clientSecret: String): Result { + override suspend fun unregister( + matrixClient: MatrixClient, + clientSecret: String, + unregisterUnifiedPush: Boolean, + ): Result { val endpoint = unifiedPushStore.getEndpoint(clientSecret) val gateway = unifiedPushStore.getPushGateway(clientSecret) if (endpoint == null || gateway == null) { @@ -47,13 +57,15 @@ class DefaultUnregisterUnifiedPushUseCase( } return pusherSubscriber.unregisterPusher(matrixClient, endpoint, gateway) .onSuccess { - cleanup(clientSecret) + cleanup(clientSecret, unregisterUnifiedPush) } } - override fun cleanup(clientSecret: String) { + override fun cleanup(clientSecret: String, unregisterUnifiedPush: Boolean) { unifiedPushStore.storeUpEndpoint(clientSecret, null) unifiedPushStore.storePushGateway(clientSecret, null) - UnifiedPush.unregister(context, clientSecret) + if (unregisterUnifiedPush) { + UnifiedPush.unregister(context, clientSecret) + } } } diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiver.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiver.kt index 97ef0b27f1..05f6969fc5 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiver.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -34,7 +35,9 @@ class VectorUnifiedPushMessagingReceiver : MessagingReceiver() { @Inject lateinit var unifiedPushGatewayResolver: UnifiedPushGatewayResolver @Inject lateinit var unifiedPushGatewayUrlResolver: UnifiedPushGatewayUrlResolver @Inject lateinit var newGatewayHandler: UnifiedPushNewGatewayHandler + @Inject lateinit var removedGatewayHandler: UnifiedPushRemovedGatewayHandler @Inject lateinit var endpointRegistrationHandler: EndpointRegistrationHandler + @AppCoroutineScope @Inject lateinit var coroutineScope: CoroutineScope @@ -103,30 +106,23 @@ class VectorUnifiedPushMessagingReceiver : MessagingReceiver() { */ override fun onRegistrationFailed(context: Context, reason: FailedReason, instance: String) { Timber.tag(loggerTag.value).e("onRegistrationFailed for $instance, reason: $reason") - /* - Toast.makeText(context, "Push service registration failed", Toast.LENGTH_SHORT).show() - val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME - pushDataStore.setFdroidSyncBackgroundMode(mode) - guardServiceStarter.start() - */ + coroutineScope.launch { + endpointRegistrationHandler.registrationDone( + RegistrationResult( + clientSecret = instance, + result = Result.failure(Exception("Registration failed. Reason: $reason")), + ) + ) + } } /** * Called when this application is unregistered from receiving push messages. */ override fun onUnregistered(context: Context, instance: String) { - Timber.tag(loggerTag.value).w("UnifiedPush: Unregistered") - /* - val mode = BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME - pushDataStore.setFdroidSyncBackgroundMode(mode) - guardServiceStarter.start() - runBlocking { - try { - pushersManager.unregisterPusher(unifiedPushHelper.getEndpointOrToken().orEmpty()) - } catch (e: Exception) { - Timber.tag(loggerTag.value).d("Probably unregistering a non existing pusher") - } + Timber.tag(loggerTag.value).w("onUnregistered $instance") + coroutineScope.launch { + removedGatewayHandler.handle(instance) } - */ } } diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiverBindings.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiverBindings.kt index bbec3f272c..1d227b3b83 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiverBindings.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiverBindings.kt @@ -1,21 +1,17 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.pushproviders.unifiedpush import dev.zacsweers.metro.AppScope -import dev.zacsweers.metro.Binds import dev.zacsweers.metro.ContributesTo -import org.unifiedpush.android.connector.MessagingReceiver @ContributesTo(AppScope::class) interface VectorUnifiedPushMessagingReceiverBindings { fun inject(receiver: VectorUnifiedPushMessagingReceiver) - - @Binds - fun bindsMessagingReceiver(vectorUnifiedPushMessagingReceiver: VectorUnifiedPushMessagingReceiver): MessagingReceiver } diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/DiscoveryResponse.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/DiscoveryResponse.kt index 2984b47125..ecdfad09e8 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/DiscoveryResponse.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/DiscoveryResponse.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/DiscoveryUnifiedPush.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/DiscoveryUnifiedPush.kt index 0b7f48adca..023dd992dc 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/DiscoveryUnifiedPush.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/DiscoveryUnifiedPush.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/UnifiedPushApi.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/UnifiedPushApi.kt index c6fad7ecba..ca99f47f31 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/UnifiedPushApi.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/network/UnifiedPushApi.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/registration/EndpointRegistrationHandler.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/registration/EndpointRegistrationHandler.kt index c6a1353a2f..b11484952e 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/registration/EndpointRegistrationHandler.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/registration/EndpointRegistrationHandler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/OpenDistributorWebPageAction.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/OpenDistributorWebPageAction.kt index 5f4eb4538c..7f805ad560 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/OpenDistributorWebPageAction.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/OpenDistributorWebPageAction.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.libraries.pushproviders.unifiedpush.troubleshoot import android.content.Context import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.androidutils.system.openUrlInExternalApp import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushConfig @@ -20,7 +20,6 @@ interface OpenDistributorWebPageAction { } @ContributesBinding(AppScope::class) -@Inject class DefaultOpenDistributorWebPageAction( @ApplicationContext private val context: Context, ) : OpenDistributorWebPageAction { diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushMatrixGatewayTest.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushMatrixGatewayTest.kt index 7a22e92222..7a50077915 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushMatrixGatewayTest.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushMatrixGatewayTest.kt @@ -1,19 +1,21 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.pushproviders.unifiedpush.troubleshoot -import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet import dev.zacsweers.metro.Inject import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushApiFactory import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushConfig -import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushCurrentUserPushConfigProvider +import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushSessionPushConfigProvider import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState @@ -22,12 +24,13 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -@ContributesIntoSet(AppScope::class) +@ContributesIntoSet(SessionScope::class) @Inject class UnifiedPushMatrixGatewayTest( + private val sessionId: SessionId, private val unifiedPushApiFactory: UnifiedPushApiFactory, private val coroutineDispatchers: CoroutineDispatchers, - private val unifiedPushCurrentUserPushConfigProvider: UnifiedPushCurrentUserPushConfigProvider, + private val unifiedPushSessionPushConfigProvider: UnifiedPushSessionPushConfigProvider, ) : NotificationTroubleshootTest { override val order = 450 private val delegate = NotificationTroubleshootTestDelegate( @@ -44,7 +47,7 @@ class UnifiedPushMatrixGatewayTest( override suspend fun run(coroutineScope: CoroutineScope) { delegate.start() - val config = unifiedPushCurrentUserPushConfigProvider.provide() + val config = unifiedPushSessionPushConfigProvider.provide(sessionId) if (config == null) { delegate.updateState( description = "No current push provider", diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTest.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTest.kt index bb2c88bcdd..6d6d3b68af 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTest.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/unifiedpush/src/main/res/values-hr/translations.xml b/libraries/pushproviders/unifiedpush/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..a77fcce68c --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/res/values-hr/translations.xml @@ -0,0 +1,11 @@ + + + "Provjerite jesu li UnifiedPush distributeri dostupni." + "Nisu pronađeni distributeri push obavijesti." + + "Pronađen je %1$d distributer: %2$s." + "Pronađena su %1$d distributera: %2$s." + "Pronađeno je %1$d distributera: %2$s." + + "Provjeri UnifiedPush" + diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultRegisterUnifiedPushUseCaseTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultRegisterUnifiedPushUseCaseTest.kt index ba84c5896f..1e77df79a2 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultRegisterUnifiedPushUseCaseTest.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultRegisterUnifiedPushUseCaseTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,16 +15,23 @@ import io.element.android.libraries.matrix.test.A_SECRET import io.element.android.libraries.pushproviders.api.Distributor import io.element.android.libraries.pushproviders.unifiedpush.registration.EndpointRegistrationHandler import io.element.android.libraries.pushproviders.unifiedpush.registration.RegistrationResult +import io.element.android.tests.testutils.fake.FakeAndroidKeyStore import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) class DefaultRegisterUnifiedPushUseCaseTest { + @Before + fun setup() { + FakeAndroidKeyStore.setup + } + @Test fun `test registration successful`() = runTest { val endpointRegistrationHandler = EndpointRegistrationHandler() diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnifiedPushCurrentUserPushConfigProviderTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnifiedPushCurrentUserPushConfigProviderTest.kt index deabff4488..7a8ec16001 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnifiedPushCurrentUserPushConfigProviderTest.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnifiedPushCurrentUserPushConfigProviderTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,36 +11,16 @@ package io.element.android.libraries.pushproviders.unifiedpush import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.test.A_SECRET import io.element.android.libraries.matrix.test.A_SESSION_ID -import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig +import io.element.android.libraries.pushproviders.api.Config import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret import io.element.android.libraries.pushstore.test.userpushstore.clientsecret.FakePushClientSecret -import io.element.android.services.appnavstate.api.AppNavigationState -import io.element.android.services.appnavstate.api.AppNavigationStateService -import io.element.android.services.appnavstate.api.NavigationState -import io.element.android.services.appnavstate.test.FakeAppNavigationStateService -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import org.junit.Test class DefaultUnifiedPushCurrentUserPushConfigProviderTest { - @Test - fun `getCurrentUserPushConfig no session`() = runTest { - val sut = createDefaultUnifiedPushCurrentUserPushConfigProvider() - val result = sut.provide() - assertThat(result).isNull() - } - @Test fun `getCurrentUserPushConfig no push gateway`() = runTest { val sut = createDefaultUnifiedPushCurrentUserPushConfigProvider( - appNavigationStateService = FakeAppNavigationStateService( - appNavigationState = MutableStateFlow( - AppNavigationState( - navigationState = NavigationState.Session(owner = "owner", sessionId = A_SESSION_ID), - isInForeground = true - ) - ) - ), pushClientSecret = FakePushClientSecret( getSecretForUserResult = { A_SECRET } ), @@ -47,21 +28,13 @@ class DefaultUnifiedPushCurrentUserPushConfigProviderTest { getPushGatewayResult = { null } ), ) - val result = sut.provide() + val result = sut.provide(A_SESSION_ID) assertThat(result).isNull() } @Test fun `getCurrentUserPushConfig no push key`() = runTest { val sut = createDefaultUnifiedPushCurrentUserPushConfigProvider( - appNavigationStateService = FakeAppNavigationStateService( - appNavigationState = MutableStateFlow( - AppNavigationState( - navigationState = NavigationState.Session(owner = "owner", sessionId = A_SESSION_ID), - isInForeground = true - ) - ) - ), pushClientSecret = FakePushClientSecret( getSecretForUserResult = { A_SECRET } ), @@ -70,21 +43,13 @@ class DefaultUnifiedPushCurrentUserPushConfigProviderTest { getEndpointResult = { null } ), ) - val result = sut.provide() + val result = sut.provide(A_SESSION_ID) assertThat(result).isNull() } @Test fun `getCurrentUserPushConfig ok`() = runTest { val sut = createDefaultUnifiedPushCurrentUserPushConfigProvider( - appNavigationStateService = FakeAppNavigationStateService( - appNavigationState = MutableStateFlow( - AppNavigationState( - navigationState = NavigationState.Session(owner = "owner", sessionId = A_SESSION_ID), - isInForeground = true - ) - ) - ), pushClientSecret = FakePushClientSecret( getSecretForUserResult = { A_SECRET } ), @@ -93,19 +58,17 @@ class DefaultUnifiedPushCurrentUserPushConfigProviderTest { getEndpointResult = { "aEndpoint" } ), ) - val result = sut.provide() - assertThat(result).isEqualTo(CurrentUserPushConfig("aPushGateway", "aEndpoint")) + val result = sut.provide(A_SESSION_ID) + assertThat(result).isEqualTo(Config("aPushGateway", "aEndpoint")) } private fun createDefaultUnifiedPushCurrentUserPushConfigProvider( pushClientSecret: PushClientSecret = FakePushClientSecret(), unifiedPushStore: UnifiedPushStore = FakeUnifiedPushStore(), - appNavigationStateService: AppNavigationStateService = FakeAppNavigationStateService(), - ): DefaultUnifiedPushCurrentUserPushConfigProvider { - return DefaultUnifiedPushCurrentUserPushConfigProvider( + ): DefaultUnifiedPushPushConfigProvider { + return DefaultUnifiedPushPushConfigProvider( pushClientSecret = pushClientSecret, unifiedPushStore = unifiedPushStore, - appNavigationStateService = appNavigationStateService, ) } } diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnifiedPushGatewayResolverTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnifiedPushGatewayResolverTest.kt index c36449f838..642f14438f 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnifiedPushGatewayResolverTest.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnifiedPushGatewayResolverTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -118,7 +119,7 @@ class DefaultUnifiedPushGatewayResolverTest { } @Test - fun `when a custom url is forbidden (403), Error is returned`() = runTest { + fun `when a custom url is forbidden (403), NoMatrixGateway is returned`() = runTest { val unifiedPushApiFactory = FakeUnifiedPushApiFactory( discoveryResponse = { throw HttpException(Response.error(HttpURLConnection.HTTP_FORBIDDEN, "".toResponseBody())) @@ -129,6 +130,36 @@ class DefaultUnifiedPushGatewayResolverTest { ) val result = sut.getGateway("http://custom.url") assertThat(unifiedPushApiFactory.baseUrlParameter).isEqualTo("http://custom.url") + assertThat(result).isEqualTo(UnifiedPushGatewayResolverResult.NoMatrixGateway) + } + + @Test + fun `when a custom url is not acceptable (406), NoMatrixGateway is returned`() = runTest { + val unifiedPushApiFactory = FakeUnifiedPushApiFactory( + discoveryResponse = { + throw HttpException(Response.error(HttpURLConnection.HTTP_NOT_ACCEPTABLE, "".toResponseBody())) + } + ) + val sut = createDefaultUnifiedPushGatewayResolver( + unifiedPushApiFactory = unifiedPushApiFactory + ) + val result = sut.getGateway("http://custom.url") + assertThat(unifiedPushApiFactory.baseUrlParameter).isEqualTo("http://custom.url") + assertThat(result).isEqualTo(UnifiedPushGatewayResolverResult.NoMatrixGateway) + } + + @Test + fun `when a custom url is internal error (500), Error is returned`() = runTest { + val unifiedPushApiFactory = FakeUnifiedPushApiFactory( + discoveryResponse = { + throw HttpException(Response.error(HttpURLConnection.HTTP_INTERNAL_ERROR, "".toResponseBody())) + } + ) + val sut = createDefaultUnifiedPushGatewayResolver( + unifiedPushApiFactory = unifiedPushApiFactory + ) + val result = sut.getGateway("http://custom.url") + assertThat(unifiedPushApiFactory.baseUrlParameter).isEqualTo("http://custom.url") assertThat(result).isEqualTo(UnifiedPushGatewayResolverResult.Error("http://custom.url/_matrix/push/v1/notify")) } diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnifiedPushGatewayUrlResolverTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnifiedPushGatewayUrlResolverTest.kt index 27008d96ec..3f437bdafa 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnifiedPushGatewayUrlResolverTest.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnifiedPushGatewayUrlResolverTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnifiedPushNewGatewayHandlerTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnifiedPushNewGatewayHandlerTest.kt index 1e429d7b2d..d5f2aa8026 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnifiedPushNewGatewayHandlerTest.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnifiedPushNewGatewayHandlerTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnifiedPushRemovedGatewayHandlerTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnifiedPushRemovedGatewayHandlerTest.kt new file mode 100644 index 0000000000..2fcaa3934b --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnifiedPushRemovedGatewayHandlerTest.kt @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.pushproviders.unifiedpush + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.MatrixClientProvider +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.test.AN_EXCEPTION +import io.element.android.libraries.matrix.test.A_SECRET +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.FakeMatrixClientProvider +import io.element.android.libraries.push.api.PushService +import io.element.android.libraries.push.test.FakePushService +import io.element.android.libraries.pushproviders.api.Distributor +import io.element.android.libraries.pushproviders.api.PushProvider +import io.element.android.libraries.pushproviders.test.FakePushProvider +import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret +import io.element.android.libraries.pushstore.test.userpushstore.clientsecret.FakePushClientSecret +import io.element.android.tests.testutils.lambda.any +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runTest +import org.junit.Test +import kotlin.time.Duration.Companion.seconds + +class DefaultUnifiedPushRemovedGatewayHandlerTest { + @Test + fun `handle returns error if the secret is unknown`() = runTest { + val sut = createDefaultUnifiedPushRemovedGatewayHandler( + pushClientSecret = FakePushClientSecret( + getUserIdFromSecretResult = { null }, + ), + ) + val result = sut.handle(A_SECRET) + assertThat(result.isFailure).isTrue() + } + + @Test + fun `handle returns error if cannot restore the client`() = runTest { + val sut = createDefaultUnifiedPushRemovedGatewayHandler( + pushClientSecret = FakePushClientSecret( + getUserIdFromSecretResult = { A_SESSION_ID }, + ), + matrixClientProvider = FakeMatrixClientProvider( + getClient = { Result.failure(AN_EXCEPTION) }, + ), + ) + val result = sut.handle(A_SECRET) + assertThat(result.isFailure).isTrue() + } + + @Test + fun `handle returns error if cannot unregister the pusher, and user is notified`() = runTest { + val onServiceUnregisteredResult = lambdaRecorder { } + val sut = createDefaultUnifiedPushRemovedGatewayHandler( + pushClientSecret = FakePushClientSecret( + getUserIdFromSecretResult = { A_SESSION_ID }, + ), + matrixClientProvider = FakeMatrixClientProvider( + getClient = { Result.success(FakeMatrixClient()) }, + ), + unregisterUnifiedPushUseCase = FakeUnregisterUnifiedPushUseCase( + unregisterLambda = { _, _, _ -> Result.failure(AN_EXCEPTION) }, + ), + pushService = FakePushService( + onServiceUnregisteredResult = onServiceUnregisteredResult, + ), + ) + val result = sut.handle(A_SECRET) + assertThat(result.isFailure).isTrue() + onServiceUnregisteredResult.assertions().isCalledOnce().with(value(A_SESSION_ID)) + } + + @Test + fun `handle returns error if cannot get current push provider, and user is notified`() = runTest { + val onServiceUnregisteredResult = lambdaRecorder { } + val sut = createDefaultUnifiedPushRemovedGatewayHandler( + pushClientSecret = FakePushClientSecret( + getUserIdFromSecretResult = { A_SESSION_ID }, + ), + matrixClientProvider = FakeMatrixClientProvider( + getClient = { Result.success(FakeMatrixClient()) }, + ), + unregisterUnifiedPushUseCase = FakeUnregisterUnifiedPushUseCase( + unregisterLambda = { _, _, _ -> Result.success(Unit) }, + ), + pushService = FakePushService( + currentPushProvider = { null }, + onServiceUnregisteredResult = onServiceUnregisteredResult, + ), + ) + val result = sut.handle(A_SECRET) + assertThat(result.isFailure).isTrue() + onServiceUnregisteredResult.assertions().isCalledOnce().with(value(A_SESSION_ID)) + } + + @Test + fun `handle returns error if cannot get current distributor, and user is notified`() = runTest { + val onServiceUnregisteredResult = lambdaRecorder { } + val sut = createDefaultUnifiedPushRemovedGatewayHandler( + pushClientSecret = FakePushClientSecret( + getUserIdFromSecretResult = { A_SESSION_ID }, + ), + matrixClientProvider = FakeMatrixClientProvider( + getClient = { Result.success(FakeMatrixClient()) }, + ), + unregisterUnifiedPushUseCase = FakeUnregisterUnifiedPushUseCase( + unregisterLambda = { _, _, _ -> Result.success(Unit) }, + ), + pushService = FakePushService( + currentPushProvider = { + FakePushProvider( + currentDistributor = { null }, + ) + }, + onServiceUnregisteredResult = onServiceUnregisteredResult, + ), + ) + val result = sut.handle(A_SECRET) + assertThat(result.isFailure).isTrue() + onServiceUnregisteredResult.assertions().isCalledOnce().with(value(A_SESSION_ID)) + } + + @Test + fun `handle returns error if cannot register again, and user is notified`() = runTest { + val onServiceUnregisteredResult = lambdaRecorder { } + val sut = createDefaultUnifiedPushRemovedGatewayHandler( + pushClientSecret = FakePushClientSecret( + getUserIdFromSecretResult = { A_SESSION_ID }, + ), + matrixClientProvider = FakeMatrixClientProvider( + getClient = { Result.success(FakeMatrixClient()) }, + ), + unregisterUnifiedPushUseCase = FakeUnregisterUnifiedPushUseCase( + unregisterLambda = { _, _, _ -> Result.success(Unit) }, + ), + pushService = FakePushService( + currentPushProvider = { + FakePushProvider( + currentDistributor = { Distributor("aValue", "aName") }, + ) + }, + registerWithLambda = { _, _, _ -> Result.failure(AN_EXCEPTION) }, + onServiceUnregisteredResult = onServiceUnregisteredResult, + ), + ) + val result = sut.handle(A_SECRET) + assertThat(result.isFailure).isTrue() + onServiceUnregisteredResult.assertions().isCalledOnce().with(value(A_SESSION_ID)) + } + + @Test + fun `handle returns success if can register again, and user is not notified`() = runTest { + val onServiceUnregisteredResult = lambdaRecorder { } + val unregisterLambda = lambdaRecorder> { _, _, _ -> Result.success(Unit) } + val sut = createDefaultUnifiedPushRemovedGatewayHandler( + pushClientSecret = FakePushClientSecret( + getUserIdFromSecretResult = { A_SESSION_ID }, + ), + matrixClientProvider = FakeMatrixClientProvider( + getClient = { Result.success(FakeMatrixClient()) }, + ), + unregisterUnifiedPushUseCase = FakeUnregisterUnifiedPushUseCase( + unregisterLambda = unregisterLambda, + ), + pushService = FakePushService( + currentPushProvider = { + FakePushProvider( + currentDistributor = { Distributor("aValue", "aName") }, + ) + }, + registerWithLambda = { _, _, _ -> Result.success(Unit) }, + onServiceUnregisteredResult = onServiceUnregisteredResult, + ), + ) + val result = sut.handle(A_SECRET) + assertThat(result.isSuccess).isTrue() + unregisterLambda.assertions().isCalledOnce().with( + any(), + value(A_SECRET), + value(false), + ) + onServiceUnregisteredResult.assertions().isNeverCalled() + } + + @Test + fun `handle returns success if can register again, but after 2 removals user is notified`() = runTest { + val onServiceUnregisteredResult = lambdaRecorder { } + val unregisterLambda = lambdaRecorder> { _, _, _ -> Result.success(Unit) } + val registerWithLambda = lambdaRecorder> { _, _, _ -> Result.success(Unit) } + val sut = createDefaultUnifiedPushRemovedGatewayHandler( + pushClientSecret = FakePushClientSecret( + getUserIdFromSecretResult = { A_SESSION_ID }, + ), + matrixClientProvider = FakeMatrixClientProvider( + getClient = { Result.success(FakeMatrixClient()) }, + ), + unregisterUnifiedPushUseCase = FakeUnregisterUnifiedPushUseCase( + unregisterLambda = unregisterLambda, + ), + pushService = FakePushService( + currentPushProvider = { + FakePushProvider( + currentDistributor = { Distributor("aValue", "aName") }, + ) + }, + registerWithLambda = registerWithLambda, + onServiceUnregisteredResult = onServiceUnregisteredResult, + ), + ) + val result = sut.handle(A_SECRET) + assertThat(result.isSuccess).isTrue() + unregisterLambda.assertions().isCalledOnce().with( + any(), + value(A_SECRET), + value(false), + ) + registerWithLambda.assertions().isCalledOnce() + onServiceUnregisteredResult.assertions().isNeverCalled() + // Second attempt in less than 1 minute + val result2 = sut.handle(A_SECRET) + assertThat(result2.isFailure).isTrue() + unregisterLambda.assertions().isCalledExactly(2) + // Registration is not called twice + registerWithLambda.assertions().isCalledOnce() + onServiceUnregisteredResult.assertions().isCalledOnce().with(value(A_SESSION_ID)) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun `handle returns success if can register again, but after 2 distant removals user is not notified`() = runTest { + val onServiceUnregisteredResult = lambdaRecorder { } + val unregisterLambda = lambdaRecorder> { _, _, _ -> Result.success(Unit) } + val registerWithLambda = lambdaRecorder> { _, _, _ -> Result.success(Unit) } + val sut = createDefaultUnifiedPushRemovedGatewayHandler( + pushClientSecret = FakePushClientSecret( + getUserIdFromSecretResult = { A_SESSION_ID }, + ), + matrixClientProvider = FakeMatrixClientProvider( + getClient = { Result.success(FakeMatrixClient()) }, + ), + unregisterUnifiedPushUseCase = FakeUnregisterUnifiedPushUseCase( + unregisterLambda = unregisterLambda, + ), + pushService = FakePushService( + currentPushProvider = { + FakePushProvider( + currentDistributor = { Distributor("aValue", "aName") }, + ) + }, + registerWithLambda = registerWithLambda, + onServiceUnregisteredResult = onServiceUnregisteredResult, + ), + ) + val result = sut.handle(A_SECRET) + assertThat(result.isSuccess).isTrue() + unregisterLambda.assertions().isCalledOnce().with( + any(), + value(A_SECRET), + value(false), + ) + registerWithLambda.assertions().isCalledOnce() + onServiceUnregisteredResult.assertions().isNeverCalled() + // Second attempt in more than 1 minute + advanceTimeBy(61.seconds) + val result2 = sut.handle(A_SECRET) + assertThat(result2.isSuccess).isTrue() + unregisterLambda.assertions().isCalledExactly(2) + // Registration is not called twice + registerWithLambda.assertions().isCalledExactly(2) + onServiceUnregisteredResult.assertions().isNeverCalled() + } + + private fun TestScope.createDefaultUnifiedPushRemovedGatewayHandler( + unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase = FakeUnregisterUnifiedPushUseCase(), + pushClientSecret: PushClientSecret = FakePushClientSecret(), + matrixClientProvider: MatrixClientProvider = FakeMatrixClientProvider(), + pushService: PushService = FakePushService(), + ) = DefaultUnifiedPushRemovedGatewayHandler( + unregisterUnifiedPushUseCase = unregisterUnifiedPushUseCase, + pushClientSecret = pushClientSecret, + matrixClientProvider = matrixClientProvider, + pushService = pushService, + unifiedPushRemovedGatewayThrottler = UnifiedPushRemovedGatewayThrottler( + appCoroutineScope = backgroundScope, + ), + ) +} diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnregisterUnifiedPushUseCaseTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnregisterUnifiedPushUseCaseTest.kt index 8540a2e4ec..ed2337c5bb 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnregisterUnifiedPushUseCaseTest.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnregisterUnifiedPushUseCaseTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeDefaultPushGatewayHttpUrlProvider.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeDefaultPushGatewayHttpUrlProvider.kt index de0a293fc2..288fd30f36 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeDefaultPushGatewayHttpUrlProvider.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeDefaultPushGatewayHttpUrlProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeRegisterUnifiedPushUseCase.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeRegisterUnifiedPushUseCase.kt index e7556ebe19..3055dad6b7 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeRegisterUnifiedPushUseCase.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeRegisterUnifiedPushUseCase.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushApiFactory.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushApiFactory.kt index e089fb4a84..5bd9dfccff 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushApiFactory.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushApiFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushGatewayResolver.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushGatewayResolver.kt index f5da318e9d..f4578c0346 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushGatewayResolver.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushGatewayResolver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushGatewayUrlResolver.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushGatewayUrlResolver.kt index d27ed89921..b02e0c3aad 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushGatewayUrlResolver.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushGatewayUrlResolver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushNewGatewayHandler.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushNewGatewayHandler.kt index aac2e7a28c..9bfef750cd 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushNewGatewayHandler.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushNewGatewayHandler.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushStore.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushStore.kt index 6d2bf51235..ff1c3ef84b 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushStore.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnifiedPushStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnregisterUnifiedPushUseCase.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnregisterUnifiedPushUseCase.kt index 15ab7281c1..182bb5f823 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnregisterUnifiedPushUseCase.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/FakeUnregisterUnifiedPushUseCase.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,14 +12,21 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.tests.testutils.lambda.lambdaError class FakeUnregisterUnifiedPushUseCase( - private val unregisterLambda: (MatrixClient, String) -> Result = { _, _ -> lambdaError() }, - private val cleanupLambda: (String) -> Unit = { lambdaError() }, + private val unregisterLambda: (MatrixClient, String, Boolean) -> Result = { _, _, _ -> lambdaError() }, + private val cleanupLambda: (String, Boolean) -> Unit = { _, _ -> lambdaError() }, ) : UnregisterUnifiedPushUseCase { - override suspend fun unregister(matrixClient: MatrixClient, clientSecret: String): Result { - return unregisterLambda(matrixClient, clientSecret) + override suspend fun unregister( + matrixClient: MatrixClient, + clientSecret: String, + unregisterUnifiedPush: Boolean, + ): Result { + return unregisterLambda(matrixClient, clientSecret, unregisterUnifiedPush) } - override fun cleanup(clientSecret: String) { - cleanupLambda(clientSecret) + override fun cleanup( + clientSecret: String, + unregisterUnifiedPush: Boolean, + ) { + cleanupLambda(clientSecret, unregisterUnifiedPush) } } diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParserTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParserTest.kt index 89d4fec3b2..4b057e9cba 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParserTest.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushParserTest.kt @@ -1,13 +1,15 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.pushproviders.unifiedpush import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.androidutils.json.DefaultJsonProvider import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.pushproviders.api.PushData @@ -25,7 +27,7 @@ class UnifiedPushParserTest { @Test fun `test edge cases UnifiedPush`() { - val pushParser = UnifiedPushParser() + val pushParser = createUnifiedPushParser() // Empty string assertThat(pushParser.parse("".toByteArray(), aClientSecret)).isNull() // Empty Json @@ -36,13 +38,13 @@ class UnifiedPushParserTest { @Test fun `test UnifiedPush format`() { - val pushParser = UnifiedPushParser() + val pushParser = createUnifiedPushParser() assertThat(pushParser.parse(UNIFIED_PUSH_DATA.toByteArray(), aClientSecret)).isEqualTo(validData) } @Test fun `test empty roomId`() { - val pushParser = UnifiedPushParser() + val pushParser = createUnifiedPushParser() assertThrowsInDebug { pushParser.parse(UNIFIED_PUSH_DATA.replace(A_ROOM_ID.value, "").toByteArray(), aClientSecret) } @@ -50,7 +52,7 @@ class UnifiedPushParserTest { @Test fun `test invalid roomId`() { - val pushParser = UnifiedPushParser() + val pushParser = createUnifiedPushParser() assertThrowsInDebug { pushParser.parse(UNIFIED_PUSH_DATA.mutate(A_ROOM_ID.value, "aRoomId:domain"), aClientSecret) } @@ -58,7 +60,7 @@ class UnifiedPushParserTest { @Test fun `test empty eventId`() { - val pushParser = UnifiedPushParser() + val pushParser = createUnifiedPushParser() assertThrowsInDebug { pushParser.parse(UNIFIED_PUSH_DATA.mutate(AN_EVENT_ID.value, ""), aClientSecret) } @@ -66,7 +68,7 @@ class UnifiedPushParserTest { @Test fun `test invalid eventId`() { - val pushParser = UnifiedPushParser() + val pushParser = createUnifiedPushParser() assertThrowsInDebug { pushParser.parse(UNIFIED_PUSH_DATA.mutate(AN_EVENT_ID.value, "anEventId"), aClientSecret) } @@ -81,3 +83,7 @@ class UnifiedPushParserTest { private fun String.mutate(oldValue: String, newValue: String): ByteArray { return replace(oldValue, newValue).toByteArray() } + +fun createUnifiedPushParser() = UnifiedPushParser( + json = DefaultJsonProvider(), +) diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProviderTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProviderTest.kt index 5e48728322..e22c1c3bf4 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProviderTest.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProviderTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,9 +17,9 @@ import io.element.android.libraries.matrix.test.A_SECRET import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.pushproviders.api.Distributor -import io.element.android.libraries.pushproviders.test.aCurrentUserPushConfig -import io.element.android.libraries.pushproviders.unifiedpush.troubleshoot.FakeUnifiedPushCurrentUserPushConfigProvider +import io.element.android.libraries.pushproviders.test.aSessionPushConfig import io.element.android.libraries.pushproviders.unifiedpush.troubleshoot.FakeUnifiedPushDistributorProvider +import io.element.android.libraries.pushproviders.unifiedpush.troubleshoot.FakeUnifiedPushSessionPushConfigProvider import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret import io.element.android.libraries.pushstore.test.userpushstore.clientsecret.FakePushClientSecret import io.element.android.tests.testutils.lambda.lambdaRecorder @@ -117,7 +118,7 @@ class UnifiedPushProviderTest { fun `unregister ok`() = runTest { val matrixClient = FakeMatrixClient() val getSecretForUserResultLambda = lambdaRecorder { A_SECRET } - val unregisterLambda = lambdaRecorder> { _, _ -> Result.success(Unit) } + val unregisterLambda = lambdaRecorder> { _, _, _ -> Result.success(Unit) } val unifiedPushProvider = createUnifiedPushProvider( pushClientSecret = FakePushClientSecret( getSecretForUserResult = getSecretForUserResultLambda, @@ -133,14 +134,14 @@ class UnifiedPushProviderTest { .with(value(A_SESSION_ID)) unregisterLambda.assertions() .isCalledOnce() - .with(value(matrixClient), value(A_SECRET)) + .with(value(matrixClient), value(A_SECRET), value(true)) } @Test fun `unregister ko`() = runTest { val matrixClient = FakeMatrixClient() val getSecretForUserResultLambda = lambdaRecorder { A_SECRET } - val unregisterLambda = lambdaRecorder> { _, _ -> Result.failure(AN_EXCEPTION) } + val unregisterLambda = lambdaRecorder> { _, _, _ -> Result.failure(AN_EXCEPTION) } val unifiedPushProvider = createUnifiedPushProvider( pushClientSecret = FakePushClientSecret( getSecretForUserResult = getSecretForUserResultLambda, @@ -156,7 +157,7 @@ class UnifiedPushProviderTest { .with(value(A_SESSION_ID)) unregisterLambda.assertions() .isCalledOnce() - .with(value(matrixClient), value(A_SECRET)) + .with(value(matrixClient), value(A_SECRET), value(true)) } @Test @@ -211,13 +212,13 @@ class UnifiedPushProviderTest { @Test fun `getCurrentUserPushConfig invokes the provider methods`() = runTest { - val currentUserPushConfig = aCurrentUserPushConfig() + val currentUserPushConfig = aSessionPushConfig() val unifiedPushProvider = createUnifiedPushProvider( - unifiedPushCurrentUserPushConfigProvider = FakeUnifiedPushCurrentUserPushConfigProvider( - currentUserPushConfig = { currentUserPushConfig } + unifiedPushSessionPushConfigProvider = FakeUnifiedPushSessionPushConfigProvider( + config = { currentUserPushConfig } ) ) - val result = unifiedPushProvider.getCurrentUserPushConfig() + val result = unifiedPushProvider.getPushConfig(A_SESSION_ID) assertThat(result).isEqualTo(currentUserPushConfig) } @@ -229,7 +230,7 @@ class UnifiedPushProviderTest { @Test fun `onSessionDeleted should do the cleanup`() = runTest { - val cleanupLambda = lambdaRecorder { } + val cleanupLambda = lambdaRecorder { _, _ -> } val unifiedPushProvider = createUnifiedPushProvider( pushClientSecret = FakePushClientSecret( getSecretForUserResult = { A_SECRET } @@ -239,7 +240,7 @@ class UnifiedPushProviderTest { ), ) unifiedPushProvider.onSessionDeleted(A_SESSION_ID) - cleanupLambda.assertions().isCalledOnce().with(value(A_SECRET)) + cleanupLambda.assertions().isCalledOnce().with(value(A_SECRET), value(true)) } private fun createUnifiedPushProvider( @@ -248,7 +249,7 @@ class UnifiedPushProviderTest { unRegisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase = FakeUnregisterUnifiedPushUseCase(), pushClientSecret: PushClientSecret = FakePushClientSecret(), unifiedPushStore: UnifiedPushStore = FakeUnifiedPushStore(), - unifiedPushCurrentUserPushConfigProvider: UnifiedPushCurrentUserPushConfigProvider = FakeUnifiedPushCurrentUserPushConfigProvider(), + unifiedPushSessionPushConfigProvider: UnifiedPushSessionPushConfigProvider = FakeUnifiedPushSessionPushConfigProvider(), ): UnifiedPushProvider { return UnifiedPushProvider( unifiedPushDistributorProvider = unifiedPushDistributorProvider, @@ -256,7 +257,7 @@ class UnifiedPushProviderTest { unRegisterUnifiedPushUseCase = unRegisterUnifiedPushUseCase, pushClientSecret = pushClientSecret, unifiedPushStore = unifiedPushStore, - unifiedPushCurrentUserPushConfigProvider = unifiedPushCurrentUserPushConfigProvider, + unifiedPushSessionPushConfigProvider = unifiedPushSessionPushConfigProvider, ) } } diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiverTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiverTest.kt index 5bb0a6731f..f10f6430f0 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiverTest.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/VectorUnifiedPushMessagingReceiverTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -22,6 +23,7 @@ import io.element.android.libraries.pushproviders.api.PushData import io.element.android.libraries.pushproviders.api.PushHandler import io.element.android.libraries.pushproviders.unifiedpush.registration.EndpointRegistrationHandler import io.element.android.libraries.pushproviders.unifiedpush.registration.RegistrationResult +import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -50,10 +52,17 @@ class VectorUnifiedPushMessagingReceiverTest { } @Test - fun `onUnregistered does nothing`() = runTest { + fun `onUnregistered invokes the removedGatewayHandler`() = runTest { val context = InstrumentationRegistry.getInstrumentation().context - val vectorUnifiedPushMessagingReceiver = createVectorUnifiedPushMessagingReceiver() + val handleResult = lambdaRecorder> { + Result.success(Unit) + } + val vectorUnifiedPushMessagingReceiver = createVectorUnifiedPushMessagingReceiver( + removedGatewayHandler = UnifiedPushRemovedGatewayHandler { handleResult(it) }, + ) vectorUnifiedPushMessagingReceiver.onUnregistered(context, A_SECRET) + advanceUntilIdle() + handleResult.assertions().isCalledOnce().with(value(A_SECRET)) } @Test @@ -191,21 +200,24 @@ class VectorUnifiedPushMessagingReceiverTest { } private fun TestScope.createVectorUnifiedPushMessagingReceiver( + unifiedPushParser: UnifiedPushParser = createUnifiedPushParser(), pushHandler: PushHandler = FakePushHandler(), unifiedPushStore: UnifiedPushStore = FakeUnifiedPushStore(), unifiedPushGatewayResolver: UnifiedPushGatewayResolver = FakeUnifiedPushGatewayResolver(), unifiedPushGatewayUrlResolver: UnifiedPushGatewayUrlResolver = FakeUnifiedPushGatewayUrlResolver(), unifiedPushNewGatewayHandler: UnifiedPushNewGatewayHandler = FakeUnifiedPushNewGatewayHandler(), endpointRegistrationHandler: EndpointRegistrationHandler = EndpointRegistrationHandler(), + removedGatewayHandler: UnifiedPushRemovedGatewayHandler = UnifiedPushRemovedGatewayHandler { lambdaError() }, ): VectorUnifiedPushMessagingReceiver { return VectorUnifiedPushMessagingReceiver().apply { - this.pushParser = UnifiedPushParser() + this.pushParser = unifiedPushParser this.pushHandler = pushHandler this.guardServiceStarter = NoopGuardServiceStarter() this.unifiedPushStore = unifiedPushStore this.unifiedPushGatewayResolver = unifiedPushGatewayResolver this.unifiedPushGatewayUrlResolver = unifiedPushGatewayUrlResolver this.newGatewayHandler = unifiedPushNewGatewayHandler + this.removedGatewayHandler = removedGatewayHandler this.endpointRegistrationHandler = endpointRegistrationHandler this.coroutineScope = this@createVectorUnifiedPushMessagingReceiver } @@ -223,7 +235,9 @@ private fun aPushMessage( private fun aPushEndpoint( url: String = "anEndpoint", pubKeySet: PublicKeySet? = null, + temporary: Boolean = false, ) = PushEndpoint( url = url, pubKeySet = pubKeySet, + temporary = temporary, ) diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeOpenDistributorWebPageAction.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeOpenDistributorWebPageAction.kt index d048784e05..7ef5dbec6e 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeOpenDistributorWebPageAction.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeOpenDistributorWebPageAction.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeUnifiedPushCurrentUserPushConfigProvider.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeUnifiedPushCurrentUserPushConfigProvider.kt deleted file mode 100644 index 92be615054..0000000000 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeUnifiedPushCurrentUserPushConfigProvider.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.pushproviders.unifiedpush.troubleshoot - -import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig -import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushCurrentUserPushConfigProvider -import io.element.android.tests.testutils.lambda.lambdaError - -class FakeUnifiedPushCurrentUserPushConfigProvider( - private val currentUserPushConfig: () -> CurrentUserPushConfig? = { lambdaError() }, -) : UnifiedPushCurrentUserPushConfigProvider { - override suspend fun provide(): CurrentUserPushConfig? { - return currentUserPushConfig() - } -} diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeUnifiedPushDistributorProvider.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeUnifiedPushDistributorProvider.kt index ac3547f25d..8ee80c7fa9 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeUnifiedPushDistributorProvider.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeUnifiedPushDistributorProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeUnifiedPushSessionPushConfigProvider.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeUnifiedPushSessionPushConfigProvider.kt new file mode 100644 index 0000000000..8d5d58ea44 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeUnifiedPushSessionPushConfigProvider.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.pushproviders.unifiedpush.troubleshoot + +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.pushproviders.api.Config +import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushSessionPushConfigProvider +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeUnifiedPushSessionPushConfigProvider( + private val config: (SessionId) -> Config? = { lambdaError() }, +) : UnifiedPushSessionPushConfigProvider { + override suspend fun provide(sessionId: SessionId): Config? { + return config(sessionId) + } +} diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushMatrixGatewayTestTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushMatrixGatewayTestTest.kt index f4a0cf5da4..f10715fce3 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushMatrixGatewayTestTest.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushMatrixGatewayTestTest.kt @@ -1,15 +1,18 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.pushproviders.unifiedpush.troubleshoot import com.google.common.truth.Truth.assertThat -import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig -import io.element.android.libraries.pushproviders.test.aCurrentUserPushConfig +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.pushproviders.api.Config +import io.element.android.libraries.pushproviders.test.aSessionPushConfig import io.element.android.libraries.pushproviders.unifiedpush.FakeUnifiedPushApiFactory import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushConfig import io.element.android.libraries.pushproviders.unifiedpush.invalidDiscoveryResponse @@ -27,7 +30,7 @@ class UnifiedPushMatrixGatewayTestTest { @Test fun `test UnifiedPushMatrixGatewayTest success`() = runTest { val sut = createUnifiedPushMatrixGatewayTest( - currentUserPushConfig = aCurrentUserPushConfig(), + config = aSessionPushConfig(), discoveryResponse = matrixDiscoveryResponse, ) sut.runAndTestState { @@ -41,7 +44,7 @@ class UnifiedPushMatrixGatewayTestTest { @Test fun `test UnifiedPushMatrixGatewayTest no config found`() = runTest { val sut = createUnifiedPushMatrixGatewayTest( - currentUserPushConfig = null, + config = null, discoveryResponse = matrixDiscoveryResponse, ) sut.runAndTestState { @@ -55,7 +58,7 @@ class UnifiedPushMatrixGatewayTestTest { @Test fun `test UnifiedPushMatrixGatewayTest not valid gateway`() = runTest { val sut = createUnifiedPushMatrixGatewayTest( - currentUserPushConfig = aCurrentUserPushConfig(), + config = aSessionPushConfig(), discoveryResponse = invalidDiscoveryResponse, ) sut.runAndTestState { @@ -72,7 +75,7 @@ class UnifiedPushMatrixGatewayTestTest { @Test fun `test UnifiedPushMatrixGatewayTest network error`() = runTest { val sut = createUnifiedPushMatrixGatewayTest( - currentUserPushConfig = aCurrentUserPushConfig(), + config = aSessionPushConfig(), discoveryResponse = { error("Network error") }, ) sut.runAndTestState { @@ -91,14 +94,16 @@ class UnifiedPushMatrixGatewayTestTest { } private fun TestScope.createUnifiedPushMatrixGatewayTest( - currentUserPushConfig: CurrentUserPushConfig? = null, + sessionId: SessionId = A_SESSION_ID, + config: Config? = null, discoveryResponse: () -> DiscoveryResponse = matrixDiscoveryResponse, ): UnifiedPushMatrixGatewayTest { return UnifiedPushMatrixGatewayTest( + sessionId = sessionId, unifiedPushApiFactory = FakeUnifiedPushApiFactory(discoveryResponse), coroutineDispatchers = testCoroutineDispatchers(), - unifiedPushCurrentUserPushConfigProvider = FakeUnifiedPushCurrentUserPushConfigProvider( - currentUserPushConfig = { currentUserPushConfig } + unifiedPushSessionPushConfigProvider = FakeUnifiedPushSessionPushConfigProvider( + config = { config } ), ) } diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt index 28ab0186da..2804f53519 100644 --- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushstore/api/build.gradle.kts b/libraries/pushstore/api/build.gradle.kts index 7a82c59110..a495a4ea9d 100644 --- a/libraries/pushstore/api/build.gradle.kts +++ b/libraries/pushstore/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStore.kt b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStore.kt index 5b3396b095..8a1b2f9f21 100644 --- a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStore.kt +++ b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStoreFactory.kt b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStoreFactory.kt index fb972376b0..78571d8b30 100644 --- a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStoreFactory.kt +++ b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStoreFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecret.kt b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecret.kt index c03a252f03..da3ea946f0 100644 --- a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecret.kt +++ b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecret.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecretFactory.kt b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecretFactory.kt index 259b732c74..d7ea7d5239 100644 --- a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecretFactory.kt +++ b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecretFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecretStore.kt b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecretStore.kt index ae3be2fe6c..ecb87b2b14 100644 --- a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecretStore.kt +++ b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/clientsecret/PushClientSecretStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushstore/impl/build.gradle.kts b/libraries/pushstore/impl/build.gradle.kts index ea45836d1f..4e643fd5f9 100644 --- a/libraries/pushstore/impl/build.gradle.kts +++ b/libraries/pushstore/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { @@ -35,7 +36,6 @@ dependencies { testCommonDependencies(libs) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.preferences.test) - testImplementation(projects.services.appnavstate.test) testImplementation(projects.libraries.pushstore.test) androidTestImplementation(libs.coroutines.test) 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 index dbc212efe1..a3b31acf6c 100644 --- 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 41dbe5c212..0690fbc569 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.libraries.pushstore.impl import android.content.Context import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.matrix.api.core.SessionId @@ -21,7 +21,6 @@ import java.util.concurrent.ConcurrentHashMap @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DefaultUserPushStoreFactory( @ApplicationContext private val context: Context, private val preferenceDataStoreFactory: PreferenceDataStoreFactory, diff --git a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStore.kt b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStore.kt index 399b9648f4..8ad2d62b33 100644 --- a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStore.kt +++ b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/DataStorePushClientSecretStore.kt b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/DataStorePushClientSecretStore.kt index 98ab23c9ea..827f4ef515 100644 --- a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/DataStorePushClientSecretStore.kt +++ b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/DataStorePushClientSecretStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,14 +12,12 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecretStore import kotlinx.coroutines.flow.first @ContributesBinding(AppScope::class) -@Inject class DataStorePushClientSecretStore( preferenceDataStoreFactory: PreferenceDataStoreFactory, ) : PushClientSecretStore { diff --git a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/DefaultPushClientSecret.kt b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/DefaultPushClientSecret.kt index 7125a7c122..8db0031912 100644 --- a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/DefaultPushClientSecret.kt +++ b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/DefaultPushClientSecret.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,14 +10,12 @@ package io.element.android.libraries.pushstore.impl.clientsecret import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecretFactory import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecretStore @ContributesBinding(AppScope::class) -@Inject class DefaultPushClientSecret( private val pushClientSecretFactory: PushClientSecretFactory, private val pushClientSecretStore: PushClientSecretStore, diff --git a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/DefaultPushClientSecretFactory.kt b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/DefaultPushClientSecretFactory.kt index be991ca0c5..1ac17770a2 100644 --- a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/DefaultPushClientSecretFactory.kt +++ b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/DefaultPushClientSecretFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,12 +10,10 @@ package io.element.android.libraries.pushstore.impl.clientsecret import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecretFactory import java.util.UUID @ContributesBinding(AppScope::class) -@Inject class DefaultPushClientSecretFactory : PushClientSecretFactory { override fun create(): String { return UUID.randomUUID().toString() diff --git a/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStoreTest.kt b/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStoreTest.kt index 2c1a10a333..9191ea4ab4 100644 --- a/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStoreTest.kt +++ b/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/UserPushStoreDataStoreTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/DefaultPushClientSecretTest.kt b/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/DefaultPushClientSecretTest.kt index 7c12979946..2f7d16fbb5 100644 --- a/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/DefaultPushClientSecretTest.kt +++ b/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/DefaultPushClientSecretTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/FakePushClientSecretFactory.kt b/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/FakePushClientSecretFactory.kt index 61eabeada9..325095a386 100644 --- a/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/FakePushClientSecretFactory.kt +++ b/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/FakePushClientSecretFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushstore/test/build.gradle.kts b/libraries/pushstore/test/build.gradle.kts index 79aa02a219..ecc451d3e5 100644 --- a/libraries/pushstore/test/build.gradle.kts +++ b/libraries/pushstore/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStore.kt b/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStore.kt index 3ddbb2e6bb..e095a490e3 100644 --- a/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStore.kt +++ b/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStoreFactory.kt b/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStoreFactory.kt index 2f1afeaaa7..e2bb6ed607 100644 --- a/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStoreFactory.kt +++ b/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStoreFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/clientsecret/FakePushClientSecret.kt b/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/clientsecret/FakePushClientSecret.kt index 87fe760a5b..2e7ccca9ae 100644 --- a/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/clientsecret/FakePushClientSecret.kt +++ b/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/clientsecret/FakePushClientSecret.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/clientsecret/InMemoryPushClientSecretStore.kt b/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/clientsecret/InMemoryPushClientSecretStore.kt index cd08d5fdcc..b82cf1367c 100644 --- a/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/clientsecret/InMemoryPushClientSecretStore.kt +++ b/libraries/pushstore/test/src/main/kotlin/io/element/android/libraries/pushstore/test/userpushstore/clientsecret/InMemoryPushClientSecretStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/qrcode/build.gradle.kts b/libraries/qrcode/build.gradle.kts index a64b4363f6..cf76e117c0 100644 --- a/libraries/qrcode/build.gradle.kts +++ b/libraries/qrcode/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { @@ -18,4 +19,5 @@ dependencies { implementation(libs.androidx.camera.view) implementation(libs.androidx.camera.camera2) implementation(libs.zxing.cpp) + implementation(libs.google.zxing) } diff --git a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt index 777d09362a..168e898965 100644 --- a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt +++ b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QRCodeAnalyzer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,19 +15,24 @@ import timber.log.Timber import zxingcpp.BarcodeReader internal class QRCodeAnalyzer( - private val onScanQrCode: (result: ByteArray?) -> Unit + private val onScanQrCode: (data: ByteArray) -> Unit ) : ImageAnalysis.Analyzer { private val reader by lazy { BarcodeReader() } override fun analyze(image: ImageProxy) { - if (image.format in SUPPORTED_IMAGE_FORMATS) { - try { - val bytes = reader.read(image).firstNotNullOfOrNull { it.bytes } - bytes?.let { onScanQrCode(it) } - } catch (e: Exception) { - Timber.w(e, "Error decoding QR code") - } finally { - image.close() + image.use { + if (image.format in SUPPORTED_IMAGE_FORMATS) { + try { + val bytes = reader.read(image).firstNotNullOfOrNull { it.bytes } + if (bytes != null) { + Timber.d("QR code scanned!") + onScanQrCode(bytes) + } + } catch (e: Exception) { + Timber.w(e, "Error decoding QR code") + } + } else { + Timber.w("Unsupported image format: ${image.format}") } } } diff --git a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QrCodeCameraView.kt b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QrCodeCameraView.kt index 0087a8a9bb..18bd1cff10 100644 --- a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QrCodeCameraView.kt +++ b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QrCodeCameraView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -30,6 +31,7 @@ import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import androidx.lifecycle.compose.LocalLifecycleOwner @@ -44,99 +46,102 @@ import kotlin.coroutines.suspendCoroutine @Composable fun QrCodeCameraView( onScanQrCode: (ByteArray) -> Unit, - renderPreview: Boolean, + isScanning: Boolean, modifier: Modifier = Modifier, ) { - if (LocalInspectionMode.current) { - Box( - modifier = modifier - .background(color = ElementTheme.colors.bgSubtlePrimary), - contentAlignment = Alignment.Center, - ) { - Text("CameraView") - } - } else { - val coroutineScope = rememberCoroutineScope() - val localContext = LocalContext.current - val lifecycleOwner = LocalLifecycleOwner.current - var cameraProvider by remember { mutableStateOf(null) } - val previewUseCase = remember { Preview.Builder().build() } - var lastFrame by remember { mutableStateOf(null) } - val imageAnalysis = remember { - ImageAnalysis.Builder() - .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) - .build() - } + val coroutineScope = rememberCoroutineScope() + val localContext = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + var cameraProvider by remember { mutableStateOf(null) } + val previewUseCase = remember { Preview.Builder().build() } + var lastFrame by remember { mutableStateOf(null) } + val imageAnalysis = remember { + ImageAnalysis.Builder() + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build() + } - LaunchedEffect(Unit) { - cameraProvider = localContext.getCameraProvider() - } + LaunchedEffect(Unit) { + cameraProvider = localContext.getCameraProvider() + } - suspend fun startQRCodeAnalysis(cameraProvider: ProcessCameraProvider, previewView: PreviewView, attempt: Int = 1) { - lastFrame = null - val cameraSelector = CameraSelector.Builder() - .requireLensFacing(CameraSelector.LENS_FACING_BACK) - .build() - imageAnalysis.setAnalyzer( - ContextCompat.getMainExecutor(previewView.context), - QRCodeAnalyzer { result -> - result?.let { - Timber.d("QR code scanned!") - onScanQrCode(it) - } - } + suspend fun startQRCodeAnalysis(cameraProvider: ProcessCameraProvider, attempt: Int = 1) { + lastFrame = null + val cameraSelector = CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_BACK) + .build() + imageAnalysis.setAnalyzer( + ContextCompat.getMainExecutor(localContext), + QRCodeAnalyzer(onScanQrCode) + ) + try { + // Make sure we unbind all use cases before binding them again + cameraProvider.unbindAll() + + cameraProvider.bindToLifecycle( + lifecycleOwner, + cameraSelector, + previewUseCase, + imageAnalysis, ) - try { - // Make sure we unbind all use cases before binding them again - cameraProvider.unbindAll() + lastFrame = null + } catch (e: Exception) { + val maxAttempts = 3 + if (attempt > maxAttempts) { + Timber.e(e, "Use case binding failed after $maxAttempts attempts. Giving up.") + } else { + Timber.e(e, "Use case binding failed (attempt #$attempt). Retrying after a delay...") + delay(100) + startQRCodeAnalysis(cameraProvider, attempt + 1) + } + } + } - cameraProvider.bindToLifecycle( - lifecycleOwner, - cameraSelector, - previewUseCase, - imageAnalysis + fun stopQRCodeAnalysis(previewView: PreviewView) { + // Stop analyzer + imageAnalysis.clearAnalyzer() + + // Save last frame to display it as the 'frozen' preview + if (lastFrame == null) { + lastFrame = previewView.bitmap + Timber.d("Saving last frame for frozen preview.") + } + + // Unbind preview use case + cameraProvider?.unbindAll() + } + + Box(modifier.clipToBounds()) { + if (LocalInspectionMode.current) { + Box( + modifier = modifier + .background(color = ElementTheme.colors.bgSubtlePrimary), + contentAlignment = Alignment.Center, + ) { + Text( + text = buildString { + append("CameraView\n") + append(if (isScanning) "scanning" else "frozen") + }, + textAlign = TextAlign.Center, ) - lastFrame = null - } catch (e: Exception) { - val maxAttempts = 3 - if (attempt > maxAttempts) { - Timber.e(e, "Use case binding failed after $maxAttempts attempts. Giving up.") - } else { - Timber.e(e, "Use case binding failed (attempt #$attempt). Retrying after a delay...") - delay(100) - startQRCodeAnalysis(cameraProvider, previewView, attempt + 1) - } } - } - - fun stopQRCodeAnalysis(previewView: PreviewView) { - // Stop analyzer - imageAnalysis.clearAnalyzer() - - // Save last frame to display it as the 'frozen' preview - if (lastFrame == null) { - lastFrame = previewView.bitmap - Timber.d("Saving last frame for frozen preview.") - } - - // Unbind preview use case - cameraProvider?.unbindAll() - } - - Box(modifier.clipToBounds()) { + } else { AndroidView( factory = { context -> val previewView = PreviewView(context) - previewUseCase.setSurfaceProvider(previewView.surfaceProvider) + previewUseCase.surfaceProvider = previewView.surfaceProvider previewView.previewStreamState.observe(lifecycleOwner) { state -> previewView.alpha = if (state == PreviewView.StreamState.STREAMING) 1f else 0f } previewView }, update = { previewView -> - if (renderPreview) { + if (isScanning) { cameraProvider?.let { provider -> - coroutineScope.launch { startQRCodeAnalysis(provider, previewView) } + coroutineScope.launch { + startQRCodeAnalysis(provider) + } } } else { stopQRCodeAnalysis(previewView) @@ -147,19 +152,21 @@ fun QrCodeCameraView( cameraProvider = null }, ) - lastFrame?.let { - Image(bitmap = it.asImageBitmap(), contentDescription = null) - } + } + lastFrame?.let { + Image(bitmap = it.asImageBitmap(), contentDescription = null) } } } -@Suppress("BlockingMethodInNonBlockingContext") private suspend fun Context.getCameraProvider(): ProcessCameraProvider = suspendCoroutine { continuation -> ProcessCameraProvider.getInstance(this).also { cameraProvider -> - cameraProvider.addListener({ - continuation.resume(cameraProvider.get()) - }, ContextCompat.getMainExecutor(this)) + cameraProvider.addListener( + { + continuation.resume(cameraProvider.get()) + }, + ContextCompat.getMainExecutor(this), + ) } } diff --git a/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QrCodeImage.kt b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QrCodeImage.kt new file mode 100644 index 0000000000..e045e42f17 --- /dev/null +++ b/libraries/qrcode/src/main/kotlin/io/element/android/libraries/qrcode/QrCodeImage.kt @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.qrcode + +import android.graphics.Bitmap +import android.graphics.Color +import androidx.annotation.ColorInt +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.IntSize +import com.google.zxing.BarcodeFormat +import com.google.zxing.common.BitMatrix +import com.google.zxing.qrcode.QRCodeWriter +import io.element.android.libraries.designsystem.modifiers.squareSize +import io.element.android.libraries.designsystem.utils.ForceMaxBrightness + +private fun String.toBitMatrix(size: Int): BitMatrix { + return QRCodeWriter().encode( + this, + BarcodeFormat.QR_CODE, + size, + size, + ) +} + +private fun BitMatrix.toBitmap( + @ColorInt backgroundColor: Int = Color.WHITE, + @ColorInt foregroundColor: Int = Color.BLACK, +): Bitmap { + val colorBuffer = IntArray(width * height) + var rowOffset = 0 + for (y in 0 until height) { + for (x in 0 until width) { + val arrayIndex = x + rowOffset + colorBuffer[arrayIndex] = if (get(x, y)) foregroundColor else backgroundColor + } + rowOffset += width + } + return Bitmap.createBitmap(colorBuffer, width, height, Bitmap.Config.ARGB_8888) +} + +@Composable +fun QrCodeImage( + data: String, + forceMaxBrightness: Boolean = true, + modifier: Modifier = Modifier, +) { + if (forceMaxBrightness) { + ForceMaxBrightness() + } + var size by remember { mutableStateOf(IntSize.Zero) } + Box( + modifier = modifier + .squareSize() + .onSizeChanged { + size = it + }, + ) { + val image = remember(data, size) { + val sideSide = maxOf(size.width, size.height).coerceAtLeast(128) + data.toBitMatrix(sideSide).toBitmap().asImageBitmap() + } + Image( + contentDescription = null, + bitmap = image, + ) + } +} + +@Composable +@Preview +internal fun QrCodeViewPreview() { + QrCodeImage( + modifier = Modifier.fillMaxHeight(), + data = "RANDOM_QRCODE_DATA", + ) +} diff --git a/libraries/recentemojis/api/build.gradle.kts b/libraries/recentemojis/api/build.gradle.kts new file mode 100644 index 0000000000..2fc74c03c7 --- /dev/null +++ b/libraries/recentemojis/api/build.gradle.kts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.recentemojis.api" +} + +dependencies { + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) + + implementation(libs.kotlinx.collections.immutable) + implementation(libs.matrix.emojibase.bindings) +} diff --git a/libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api/AddRecentEmoji.kt b/libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api/AddRecentEmoji.kt new file mode 100644 index 0000000000..5b50823f77 --- /dev/null +++ b/libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api/AddRecentEmoji.kt @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.recentemojis.api + +fun interface AddRecentEmoji { + suspend operator fun invoke(emoji: String): Result +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseProvider.kt b/libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api/EmojibaseProvider.kt similarity index 65% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseProvider.kt rename to libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api/EmojibaseProvider.kt index ce394150b7..9d6e41eb55 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseProvider.kt +++ b/libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api/EmojibaseProvider.kt @@ -1,11 +1,12 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.messages.impl.timeline.components.customreaction +package io.element.android.libraries.recentemojis.api import io.element.android.emojibasebindings.EmojibaseStore diff --git a/libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api/GetRecentEmojis.kt b/libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api/GetRecentEmojis.kt new file mode 100644 index 0000000000..6517512f44 --- /dev/null +++ b/libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api/GetRecentEmojis.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.recentemojis.api + +import kotlinx.collections.immutable.ImmutableList + +/** + * Returns the list of recently used emojis for reactions. + */ +fun interface GetRecentEmojis { + suspend operator fun invoke(): Result> +} diff --git a/libraries/recentemojis/impl/build.gradle.kts b/libraries/recentemojis/impl/build.gradle.kts new file mode 100644 index 0000000000..a1a72c8672 --- /dev/null +++ b/libraries/recentemojis/impl/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +import extension.setupDependencyInjection + +plugins { + id("io.element.android-compose-library") + id("kotlin-parcelize") +} + +android { + namespace = "io.element.android.libraries.recentemojis.impl" +} + +setupDependencyInjection() + +dependencies { + api(projects.libraries.recentemojis.api) + implementation(projects.libraries.matrix.api) + implementation(libs.kotlinx.collections.immutable) + implementation(libs.matrix.emojibase.bindings) + + testImplementation(projects.libraries.recentemojis.test) + testImplementation(libs.test.junit) + testImplementation(libs.coroutines.test) + testImplementation(libs.molecule.runtime) + testImplementation(libs.test.truth) + testImplementation(libs.test.turbine) + testImplementation(projects.libraries.matrix.test) + testImplementation(projects.tests.testutils) +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/recentemojis/GetRecentEmojis.kt b/libraries/recentemojis/impl/src/main/kotlin/io/element/android/libraries/recentemojis/impl/DefaultAddRecentEmoji.kt similarity index 60% rename from libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/recentemojis/GetRecentEmojis.kt rename to libraries/recentemojis/impl/src/main/kotlin/io/element/android/libraries/recentemojis/impl/DefaultAddRecentEmoji.kt index 53adf88c37..4444d7e01a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/recentemojis/GetRecentEmojis.kt +++ b/libraries/recentemojis/impl/src/main/kotlin/io/element/android/libraries/recentemojis/impl/DefaultAddRecentEmoji.kt @@ -1,30 +1,26 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.libraries.matrix.api.recentemojis +package io.element.android.libraries.recentemojis.impl import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.recentemojis.api.AddRecentEmoji import kotlinx.coroutines.withContext -fun interface GetRecentEmojis { - suspend operator fun invoke(): Result> -} - @ContributesBinding(SessionScope::class) -@Inject -class DefaultGetRecentEmojis( +class DefaultAddRecentEmoji( private val client: MatrixClient, private val dispatchers: CoroutineDispatchers, -) : GetRecentEmojis { - override suspend operator fun invoke(): Result> = withContext(dispatchers.io) { - client.getRecentEmojis() +) : AddRecentEmoji { + override suspend operator fun invoke(emoji: String): Result = withContext(dispatchers.io) { + client.addRecentEmoji(emoji) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/DefaultEmojibaseProvider.kt b/libraries/recentemojis/impl/src/main/kotlin/io/element/android/libraries/recentemojis/impl/DefaultEmojibaseProvider.kt similarity index 69% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/DefaultEmojibaseProvider.kt rename to libraries/recentemojis/impl/src/main/kotlin/io/element/android/libraries/recentemojis/impl/DefaultEmojibaseProvider.kt index e07c78f578..161dd0cc56 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/DefaultEmojibaseProvider.kt +++ b/libraries/recentemojis/impl/src/main/kotlin/io/element/android/libraries/recentemojis/impl/DefaultEmojibaseProvider.kt @@ -1,15 +1,17 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -package io.element.android.features.messages.impl.timeline.components.customreaction +package io.element.android.libraries.recentemojis.impl import android.content.Context import io.element.android.emojibasebindings.EmojibaseDatasource import io.element.android.emojibasebindings.EmojibaseStore +import io.element.android.libraries.recentemojis.api.EmojibaseProvider class DefaultEmojibaseProvider(val context: Context) : EmojibaseProvider { override val emojibaseStore: EmojibaseStore by lazy { diff --git a/libraries/recentemojis/impl/src/main/kotlin/io/element/android/libraries/recentemojis/impl/DefaultGetRecentEmojis.kt b/libraries/recentemojis/impl/src/main/kotlin/io/element/android/libraries/recentemojis/impl/DefaultGetRecentEmojis.kt new file mode 100644 index 0000000000..9c5ed178dd --- /dev/null +++ b/libraries/recentemojis/impl/src/main/kotlin/io/element/android/libraries/recentemojis/impl/DefaultGetRecentEmojis.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.recentemojis.impl + +import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.recentemojis.api.EmojibaseProvider +import io.element.android.libraries.recentemojis.api.GetRecentEmojis +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.withContext + +@ContributesBinding(SessionScope::class) +class DefaultGetRecentEmojis( + private val client: MatrixClient, + private val dispatchers: CoroutineDispatchers, + private val emojibaseProvider: EmojibaseProvider, +) : GetRecentEmojis { + override suspend operator fun invoke(): Result> = withContext(dispatchers.io) { + val allEmojis = emojibaseProvider.emojibaseStore.allEmojis + client.getRecentEmojis() + .map { emojis -> + // Remove any possible duplicates + emojis.distinct() + // Return only those emojis that are valid + .filter { recent -> allEmojis.any { recent == it.unicode } } + .toImmutableList() + } + } +} diff --git a/libraries/recentemojis/impl/src/test/kotlin/io/element/android/libraries/recentemojis/impl/DefaultGetRecentEmojisTest.kt b/libraries/recentemojis/impl/src/test/kotlin/io/element/android/libraries/recentemojis/impl/DefaultGetRecentEmojisTest.kt new file mode 100644 index 0000000000..722aaee234 --- /dev/null +++ b/libraries/recentemojis/impl/src/test/kotlin/io/element/android/libraries/recentemojis/impl/DefaultGetRecentEmojisTest.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.recentemojis.impl + +import com.google.common.truth.Truth.assertThat +import io.element.android.emojibasebindings.Emoji +import io.element.android.emojibasebindings.EmojibaseCategory +import io.element.android.emojibasebindings.EmojibaseCategory.People +import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.recentemojis.test.FakeEmojibaseProvider +import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableMap +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentMapOf +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class DefaultGetRecentEmojisTest { + @Test + fun `invoke - deduplicates results`() = runTest { + val recentEmojiResult = persistentListOf(":)", ":D", ":)") + val getRecentEmojis = createDefaultGetRecentEmojis( + recentEmojis = { Result.success(recentEmojiResult) }, + emojibaseContents = persistentMapOf(People to recentEmojiResult.map { emoji(it) }.toImmutableList()) + ) + + assertThat(getRecentEmojis()).isEqualTo(Result.success(persistentListOf(":)", ":D"))) + } + + @Test + fun `invoke - removes non-standard emojis`() = runTest { + val recentEmojiResult = persistentListOf(":)", ":D", "Custom reaction") + val getRecentEmojis = createDefaultGetRecentEmojis( + recentEmojis = { Result.success(recentEmojiResult) }, + emojibaseContents = persistentMapOf( + People to persistentListOf(emoji(":)"), emoji(":D")) + ) + ) + + assertThat(getRecentEmojis()).isEqualTo(Result.success(persistentListOf(":)", ":D"))) + } + + private fun emoji(unicode: String) = Emoji( + hexcode = "", + label = "", + tags = null, + shortcodes = persistentListOf(), + unicode = unicode, + skins = null, + ) + + private fun TestScope.createDefaultGetRecentEmojis( + recentEmojis: () -> Result> = { Result.success(emptyList()) }, + emojibaseContents: ImmutableMap> = persistentMapOf(People to persistentListOf(emoji(":)"))), + ) = DefaultGetRecentEmojis( + client = FakeMatrixClient(getRecentEmojisLambda = recentEmojis), + dispatchers = testCoroutineDispatchers(), + emojibaseProvider = FakeEmojibaseProvider(emojibaseContents), + ) +} diff --git a/libraries/recentemojis/test/build.gradle.kts b/libraries/recentemojis/test/build.gradle.kts new file mode 100644 index 0000000000..7c32d162ee --- /dev/null +++ b/libraries/recentemojis/test/build.gradle.kts @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.recentemojis.test" +} + +dependencies { + api(projects.libraries.matrix.api) + api(libs.coroutines.core) + + implementation(libs.kotlinx.collections.immutable) + implementation(libs.coroutines.test) + implementation(projects.tests.testutils) + implementation(projects.libraries.recentemojis.api) + implementation(libs.matrix.emojibase.bindings) +} diff --git a/libraries/recentemojis/test/src/main/kotlin/io/element/android/libraries/recentemojis/test/FakeEmojibaseProvider.kt b/libraries/recentemojis/test/src/main/kotlin/io/element/android/libraries/recentemojis/test/FakeEmojibaseProvider.kt new file mode 100644 index 0000000000..4774144707 --- /dev/null +++ b/libraries/recentemojis/test/src/main/kotlin/io/element/android/libraries/recentemojis/test/FakeEmojibaseProvider.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.recentemojis.test + +import io.element.android.emojibasebindings.Emoji +import io.element.android.emojibasebindings.EmojibaseCategory +import io.element.android.emojibasebindings.EmojibaseStore +import io.element.android.libraries.recentemojis.api.EmojibaseProvider +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toPersistentMap + +class FakeEmojibaseProvider( + val emojis: Map> = mapOf(), +) : EmojibaseProvider { + override val emojibaseStore: EmojibaseStore + get() = EmojibaseStore(emojis.toPersistentMap()) +} diff --git a/libraries/roomselect/api/build.gradle.kts b/libraries/roomselect/api/build.gradle.kts index 75bdb90b4c..7bd15bb0ac 100644 --- a/libraries/roomselect/api/build.gradle.kts +++ b/libraries/roomselect/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/roomselect/api/src/main/kotlin/io/element/android/libraries/roomselect/api/RoomSelectEntryPoint.kt b/libraries/roomselect/api/src/main/kotlin/io/element/android/libraries/roomselect/api/RoomSelectEntryPoint.kt index a5d13609bd..ba5ca79d40 100644 --- a/libraries/roomselect/api/src/main/kotlin/io/element/android/libraries/roomselect/api/RoomSelectEntryPoint.kt +++ b/libraries/roomselect/api/src/main/kotlin/io/element/android/libraries/roomselect/api/RoomSelectEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -18,12 +19,12 @@ interface RoomSelectEntryPoint : FeatureEntryPoint { val mode: RoomSelectMode, ) - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - interface NodeBuilder { - fun params(params: Params): NodeBuilder - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: Params, + callback: Callback, + ): Node interface Callback : Plugin { fun onRoomSelected(roomIds: List) diff --git a/libraries/roomselect/api/src/main/kotlin/io/element/android/libraries/roomselect/api/RoomSelectMode.kt b/libraries/roomselect/api/src/main/kotlin/io/element/android/libraries/roomselect/api/RoomSelectMode.kt index aef8f203c5..fad4ffae79 100644 --- a/libraries/roomselect/api/src/main/kotlin/io/element/android/libraries/roomselect/api/RoomSelectMode.kt +++ b/libraries/roomselect/api/src/main/kotlin/io/element/android/libraries/roomselect/api/RoomSelectMode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/roomselect/impl/build.gradle.kts b/libraries/roomselect/impl/build.gradle.kts index de0834e635..1a5ae81928 100644 --- a/libraries/roomselect/impl/build.gradle.kts +++ b/libraries/roomselect/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/DefaultRoomSelectEntryPoint.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/DefaultRoomSelectEntryPoint.kt index 0c6ba57b9f..4ca92663a9 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/DefaultRoomSelectEntryPoint.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/DefaultRoomSelectEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,33 +10,25 @@ package io.element.android.libraries.roomselect.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope import io.element.android.libraries.roomselect.api.RoomSelectEntryPoint @ContributesBinding(SessionScope::class) -@Inject class DefaultRoomSelectEntryPoint : RoomSelectEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): RoomSelectEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : RoomSelectEntryPoint.NodeBuilder { - override fun params(params: RoomSelectEntryPoint.Params): RoomSelectEntryPoint.NodeBuilder { - plugins += RoomSelectNode.Inputs(mode = params.mode) - return this - } - - override fun callback(callback: RoomSelectEntryPoint.Callback): RoomSelectEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: RoomSelectEntryPoint.Params, + callback: RoomSelectEntryPoint.Callback, + ): Node { + return parentNode.createNode( + buildContext = buildContext, + plugins = listOf( + RoomSelectNode.Inputs(mode = params.mode), + callback, + ) + ) } } diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectEvents.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectEvents.kt index 98fb21c6a4..b5ea0e0727 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectEvents.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectNode.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectNode.kt index a7e6dc0cb0..93294c9753 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectNode.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,9 +17,9 @@ import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.libraries.architecture.NodeInputs +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope -import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.roomselect.api.RoomSelectEntryPoint import io.element.android.libraries.roomselect.api.RoomSelectMode @@ -35,24 +36,15 @@ class RoomSelectNode( private val inputs: Inputs = inputs() private val presenter = presenterFactory.create(inputs.mode) - - private val callbacks = plugins.filterIsInstance() - - private fun onDismiss() { - callbacks.forEach { it.onCancel() } - } - - private fun onSubmit(roomIds: List) { - callbacks.forEach { it.onRoomSelected(roomIds) } - } + private val callback: RoomSelectEntryPoint.Callback = callback() @Composable override fun View(modifier: Modifier) { val state = presenter.present() RoomSelectView( state = state, - onDismiss = ::onDismiss, - onSubmit = ::onSubmit, + onDismiss = callback::onCancel, + onSubmit = callback::onRoomSelected, modifier = modifier ) } diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenter.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenter.kt index ffd3fda5c0..be66db77dc 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenter.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -63,7 +64,7 @@ class RoomSelectPresenter( } } - fun handleEvents(event: RoomSelectEvents) { + fun handleEvent(event: RoomSelectEvents) { when (event) { is RoomSelectEvents.SetSelectedRoom -> { selectedRooms = persistentListOf(event.room) @@ -87,7 +88,7 @@ class RoomSelectPresenter( query = searchQuery, isSearchActive = isSearchActive, selectedRooms = selectedRooms, - eventSink = { handleEvents(it) } + eventSink = ::handleEvent, ) } } diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectSearchDataSource.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectSearchDataSource.kt index 9e636ead7b..15148eb15b 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectSearchDataSource.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectSearchDataSource.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectState.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectState.kt index 549de0338f..c1ccb0077d 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectState.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectStateProvider.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectStateProvider.kt index 2372ed78e1..5b63d6dbd7 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectStateProvider.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt index a14ac7ab66..4d02e1ba10 100644 --- a/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt +++ b/libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -24,6 +25,10 @@ import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource @@ -80,15 +85,20 @@ fun RoomSelectView( ) } + var canHandleBack by remember { mutableStateOf(true) } fun onBackButton(state: RoomSelectState) { if (state.isSearchActive) { state.eventSink(RoomSelectEvents.ToggleSearchActive) - } else { + } else if (canHandleBack) { + canHandleBack = false onDismiss() } } - BackHandler(onBack = { onBackButton(state) }) + BackHandler( + enabled = canHandleBack, + onBack = { onBackButton(state) } + ) Scaffold( modifier = modifier, @@ -99,7 +109,10 @@ fun RoomSelectView( RoomSelectMode.Share -> stringResource(CommonStrings.common_send_to) }, navigationIcon = { - BackButton(onClick = { onBackButton(state) }) + BackButton( + enabled = canHandleBack, + onClick = { onBackButton(state) } + ) }, actions = { TextButton( diff --git a/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/DefaultRoomSelectEntryPointTest.kt b/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/DefaultRoomSelectEntryPointTest.kt index bb9c15e7e6..0d805904db 100644 --- a/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/DefaultRoomSelectEntryPointTest.kt +++ b/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/DefaultRoomSelectEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -42,10 +43,12 @@ class DefaultRoomSelectEntryPointTest { override fun onCancel() = lambdaError() } val params = RoomSelectEntryPoint.Params(testMode) - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .params(params) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + params = params, + callback = callback, + ) assertThat(result).isInstanceOf(RoomSelectNode::class.java) assertThat(result.plugins).contains(RoomSelectNode.Inputs(params.mode)) assertThat(result.plugins).contains(callback) diff --git a/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTest.kt b/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTest.kt index 8294c97fad..f6c7ef516d 100644 --- a/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTest.kt +++ b/libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/roomselect/test/build.gradle.kts b/libraries/roomselect/test/build.gradle.kts new file mode 100644 index 0000000000..8a3d063b3a --- /dev/null +++ b/libraries/roomselect/test/build.gradle.kts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.roomselect.test" +} + +dependencies { + implementation(projects.libraries.architecture) + implementation(projects.libraries.roomselect.api) + implementation(projects.tests.testutils) +} diff --git a/libraries/roomselect/test/src/main/kotlin/io/element/android/libraries/roomselect/test/FakeRoomSelectEntryPoint.kt b/libraries/roomselect/test/src/main/kotlin/io/element/android/libraries/roomselect/test/FakeRoomSelectEntryPoint.kt new file mode 100644 index 0000000000..6514c2c7d9 --- /dev/null +++ b/libraries/roomselect/test/src/main/kotlin/io/element/android/libraries/roomselect/test/FakeRoomSelectEntryPoint.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.roomselect.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.libraries.roomselect.api.RoomSelectEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeRoomSelectEntryPoint : RoomSelectEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + params: RoomSelectEntryPoint.Params, + callback: RoomSelectEntryPoint.Callback, + ): Node = lambdaError() +} diff --git a/libraries/session-storage/api/build.gradle.kts b/libraries/session-storage/api/build.gradle.kts index 5a03e913b4..74dfa910a9 100644 --- a/libraries/session-storage/api/build.gradle.kts +++ b/libraries/session-storage/api/build.gradle.kts @@ -1,11 +1,12 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { - id("io.element.android-library") + id("io.element.android-compose-library") } android { diff --git a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/LoggedInState.kt b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/LoggedInState.kt index 71fdbe65e6..f9267ee951 100644 --- a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/LoggedInState.kt +++ b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/LoggedInState.kt @@ -1,12 +1,16 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.sessionstorage.api +import androidx.compose.runtime.Immutable + +@Immutable sealed interface LoggedInState { data object NotLoggedIn : LoggedInState data class LoggedIn( diff --git a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/LoginType.kt b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/LoginType.kt index 60e4477a85..3e80dbd38e 100644 --- a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/LoginType.kt +++ b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/LoginType.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt index 90b0a0b7c7..568dbe7e3a 100644 --- a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt +++ b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionData.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -25,8 +26,6 @@ data class SessionData( val homeserverUrl: String, /** The Open ID Connect info for this session, if any. */ val oidcData: String?, - /** The Sliding Sync Proxy URL for this session, if any. */ - val slidingSyncProxy: String?, /** The timestamp of the last login. May be `null` in very old sessions. */ val loginTimestamp: Date?, /** Whether the [accessToken] is valid or not. */ diff --git a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionStore.kt b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionStore.kt index 9d9f143e15..fb60311418 100644 --- a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionStore.kt +++ b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/SessionStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -50,6 +51,11 @@ interface SessionStore { */ suspend fun getAllSessions(): List + /** + * Get the number of sessions. + */ + suspend fun numberOfSessions(): Int + /** * Get the latest session, or null if no session exists. */ @@ -73,3 +79,15 @@ fun List.toUserList(): List { fun Flow>.toUserListFlow(): Flow> { return map { it.toUserList() } } + +/** + * @return a flow emitting the sessionId of the latest session if logged in, null otherwise. + */ +fun SessionStore.sessionIdFlow(): Flow { + return loggedInStateFlow().map { + when (it) { + is LoggedInState.LoggedIn -> it.sessionId + is LoggedInState.NotLoggedIn -> null + } + } +} diff --git a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/observer/SessionListener.kt b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/observer/SessionListener.kt index b0db9fa4bc..8dadcafc7d 100644 --- a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/observer/SessionListener.kt +++ b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/observer/SessionListener.kt @@ -1,13 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.sessionstorage.api.observer interface SessionListener { - suspend fun onSessionCreated(userId: String) - suspend fun onSessionDeleted(userId: String) + suspend fun onSessionCreated(userId: String) {} + suspend fun onSessionDeleted(userId: String, wasLastSession: Boolean) {} } diff --git a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/observer/SessionObserver.kt b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/observer/SessionObserver.kt index dc987457c3..fb120acd56 100644 --- a/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/observer/SessionObserver.kt +++ b/libraries/session-storage/api/src/main/kotlin/io/element/android/libraries/sessionstorage/api/observer/SessionObserver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/session-storage/impl/build.gradle.kts b/libraries/session-storage/impl/build.gradle.kts index 3ceb4076cc..8b7d9d6e15 100644 --- a/libraries/session-storage/impl/build.gradle.kts +++ b/libraries/session-storage/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt index d6197d868d..9be59e4bae 100644 --- a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt +++ b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,13 +13,13 @@ import app.cash.sqldelight.coroutines.mapToList import app.cash.sqldelight.coroutines.mapToOneOrNull import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.sessionstorage.api.LoggedInState import io.element.android.libraries.sessionstorage.api.SessionData import io.element.android.libraries.sessionstorage.api.SessionStore import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -26,7 +27,6 @@ import timber.log.Timber @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DatabaseSessionStore( private val database: SessionDatabase, private val dispatchers: CoroutineDispatchers, @@ -47,6 +47,7 @@ class DatabaseSessionStore( ) } } + .distinctUntilChanged() } override suspend fun addSession(sessionData: SessionData) { @@ -161,6 +162,15 @@ class DatabaseSessionStore( } } + override suspend fun numberOfSessions(): Int { + return sessionDataMutex.withLock { + database.sessionDataQueries.count() + .executeAsOneOrNull() + ?.toInt() + ?: 0 + } + } + override fun sessionsFlow(): Flow> { return database.sessionDataQueries.selectAll() .asFlow() diff --git a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt index 3b694c0124..ea69709bbd 100644 --- a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt +++ b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/SessionDataMapper.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -20,7 +21,6 @@ internal fun SessionData.toDbModel(): DbSessionData { refreshToken = refreshToken, homeserverUrl = homeserverUrl, oidcData = oidcData, - slidingSyncProxy = slidingSyncProxy, loginTimestamp = loginTimestamp?.time, isTokenValid = if (isTokenValid) 1L else 0L, loginType = loginType.name, @@ -42,7 +42,6 @@ internal fun DbSessionData.toApiModel(): SessionData { refreshToken = refreshToken, homeserverUrl = homeserverUrl, oidcData = oidcData, - slidingSyncProxy = slidingSyncProxy, loginTimestamp = loginTimestamp?.let { Date(it) }, isTokenValid = isTokenValid == 1L, loginType = LoginType.fromName(loginType ?: LoginType.UNKNOWN.name), diff --git a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/di/SessionStorageModule.kt b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/di/SessionStorageModule.kt index 00bdfaf587..fb2c9a2c78 100644 --- a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/di/SessionStorageModule.kt +++ b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/di/SessionStorageModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/observer/DefaultSessionObserver.kt b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/observer/DefaultSessionObserver.kt index 78d596e5a6..3c78f5ea8a 100644 --- a/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/observer/DefaultSessionObserver.kt +++ b/libraries/session-storage/impl/src/main/kotlin/io/element/android/libraries/sessionstorage/impl/observer/DefaultSessionObserver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.libraries.sessionstorage.impl.observer import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.annotations.AppCoroutineScope @@ -27,7 +27,6 @@ import java.util.concurrent.CopyOnWriteArraySet @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class DefaultSessionObserver( private val sessionStore: SessionStore, @AppCoroutineScope @@ -62,9 +61,10 @@ class DefaultSessionObserver( // Compute diff // Removed user val removedUsers = currentUserSet - newUserSet + val wasLastSession = newUserSet.isEmpty() removedUsers.forEach { removedUser -> listeners.onEach { listener -> - listener.onSessionDeleted(removedUser) + listener.onSessionDeleted(removedUser, wasLastSession) } } // Added user diff --git a/libraries/session-storage/impl/src/main/sqldelight/databases/11.db b/libraries/session-storage/impl/src/main/sqldelight/databases/11.db new file mode 100644 index 0000000000..92222fe421 Binary files /dev/null and b/libraries/session-storage/impl/src/main/sqldelight/databases/11.db differ diff --git a/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq b/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq index 53d07bfba3..8e5fb7a1f8 100644 --- a/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq +++ b/libraries/session-storage/impl/src/main/sqldelight/io/element/android/libraries/matrix/session/SessionData.sq @@ -14,7 +14,6 @@ CREATE TABLE SessionData ( accessToken TEXT NOT NULL, refreshToken TEXT, homeserverUrl TEXT NOT NULL, - slidingSyncProxy TEXT, -- added in version 2 loginTimestamp INTEGER, -- added in version 3 @@ -47,6 +46,9 @@ SELECT * FROM SessionData ORDER BY lastUsageIndex DESC LIMIT 1; selectAll: SELECT * FROM SessionData ORDER BY lastUsageIndex DESC; +count: +SELECT count(*) FROM SessionData; + selectByUserId: SELECT * FROM SessionData WHERE userId = ?; diff --git a/libraries/session-storage/impl/src/main/sqldelight/migrations/10.sqm b/libraries/session-storage/impl/src/main/sqldelight/migrations/10.sqm new file mode 100644 index 0000000000..3d969333de --- /dev/null +++ b/libraries/session-storage/impl/src/main/sqldelight/migrations/10.sqm @@ -0,0 +1,43 @@ +-- Migrate DB from version 10 +-- Remove field slidingSyncProxy + +-- Equivalent to (DROP not supported by sqldelight): +-- ALTER TABLE SessionData DROP slidingSyncProxy; + +CREATE TABLE SessionData_bak ( + userId TEXT NOT NULL PRIMARY KEY, + deviceId TEXT NOT NULL, + accessToken TEXT NOT NULL, + refreshToken TEXT, + homeserverUrl TEXT NOT NULL, + loginTimestamp INTEGER, + oidcData TEXT, + isTokenValid INTEGER NOT NULL DEFAULT 1, + loginType TEXT, + passphrase TEXT, + sessionPath TEXT NOT NULL DEFAULT "", + cachePath TEXT NOT NULL DEFAULT "", + position INTEGER NOT NULL DEFAULT 0, + lastUsageIndex INTEGER NOT NULL DEFAULT 0, + userDisplayName TEXT, + userAvatarUrl TEXT +); +INSERT INTO SessionData_bak SELECT + userId, + deviceId, + accessToken, + refreshToken, + homeserverUrl, + loginTimestamp, + oidcData, + isTokenValid, + loginType, + passphrase, + sessionPath, + cachePath, + position, + lastUsageIndex, + userDisplayName, + userAvatarUrl FROM SessionData; +DROP TABLE SessionData; +ALTER TABLE SessionData_bak RENAME TO SessionData; diff --git a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTest.kt b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTest.kt index 7d264f42db..e40000986b 100644 --- a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTest.kt +++ b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -24,7 +25,7 @@ class DatabaseSessionStoreTest { private lateinit var database: SessionDatabase private lateinit var databaseSessionStore: DatabaseSessionStore - private val aSessionData = aSessionData() + private val aSessionData = aDbSessionData() @OptIn(ExperimentalCoroutinesApi::class) @Before @@ -52,6 +53,7 @@ class DatabaseSessionStoreTest { assertThat(database.sessionDataQueries.selectLatest().executeAsOneOrNull()).isEqualTo(aSessionData) assertThat(database.sessionDataQueries.selectAll().executeAsList().size).isEqualTo(1) + assertThat(database.sessionDataQueries.count().executeAsOneOrNull()).isEqualTo(1) } @Test @@ -109,6 +111,7 @@ class DatabaseSessionStoreTest { assertThat(foundSession).isEqualTo(aSessionData) assertThat(database.sessionDataQueries.selectAll().executeAsList().size).isEqualTo(2) + assertThat(database.sessionDataQueries.count().executeAsOneOrNull()).isEqualTo(2) } @Test @@ -196,12 +199,16 @@ class DatabaseSessionStoreTest { position = 1, lastUsageIndex = 1, ) + assertThat(database.sessionDataQueries.count().executeAsOneOrNull()).isEqualTo(1) databaseSessionStore.addSession(secondSessionData.toApiModel()) assertThat(awaitItem().size).isEqualTo(2) + assertThat(database.sessionDataQueries.count().executeAsOneOrNull()).isEqualTo(2) databaseSessionStore.removeSession(aSessionData.userId) assertThat(awaitItem().size).isEqualTo(1) + assertThat(database.sessionDataQueries.count().executeAsOneOrNull()).isEqualTo(1) databaseSessionStore.removeSession(secondSessionData.userId) assertThat(awaitItem()).isEmpty() + assertThat(database.sessionDataQueries.count().executeAsOneOrNull()).isEqualTo(0) } } @@ -213,7 +220,6 @@ class DatabaseSessionStoreTest { accessToken = "accessToken", refreshToken = "refreshToken", homeserverUrl = "homeserverUrl", - slidingSyncProxy = "slidingSyncProxy", loginTimestamp = 1, oidcData = "aOidcData", isTokenValid = 1, @@ -232,7 +238,6 @@ class DatabaseSessionStoreTest { accessToken = "accessTokenAltered", refreshToken = "refreshTokenAltered", homeserverUrl = "homeserverUrlAltered", - slidingSyncProxy = "slidingSyncProxyAltered", loginTimestamp = 2, oidcData = "aOidcDataAltered", isTokenValid = 1, @@ -259,7 +264,6 @@ class DatabaseSessionStoreTest { assertThat(alteredSession.accessToken).isEqualTo(secondSessionData.accessToken) assertThat(alteredSession.refreshToken).isEqualTo(secondSessionData.refreshToken) assertThat(alteredSession.homeserverUrl).isEqualTo(secondSessionData.homeserverUrl) - assertThat(alteredSession.slidingSyncProxy).isEqualTo(secondSessionData.slidingSyncProxy) // Check that alteredSession.loginTimestamp is not altered, so equal to firstSessionData.loginTimestamp assertThat(alteredSession.loginTimestamp).isEqualTo(firstSessionData.loginTimestamp) assertThat(alteredSession.oidcData).isEqualTo(secondSessionData.oidcData) @@ -279,7 +283,6 @@ class DatabaseSessionStoreTest { accessToken = "accessToken", refreshToken = "refreshToken", homeserverUrl = "homeserverUrl", - slidingSyncProxy = "slidingSyncProxy", loginTimestamp = 1, oidcData = "aOidcData", isTokenValid = 1, @@ -298,7 +301,6 @@ class DatabaseSessionStoreTest { accessToken = "accessTokenAltered", refreshToken = "refreshTokenAltered", homeserverUrl = "homeserverUrlAltered", - slidingSyncProxy = "slidingSyncProxyAltered", loginTimestamp = 2, oidcData = "aOidcDataAltered", isTokenValid = 1, diff --git a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/Fixtures.kt b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/Fixtures.kt index e8713dac1a..4a488588ff 100644 --- a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/Fixtures.kt +++ b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/Fixtures.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,13 +11,14 @@ package io.element.android.libraries.sessionstorage.impl import io.element.android.libraries.matrix.session.SessionData import io.element.android.libraries.sessionstorage.api.LoginType -internal fun aSessionData() = SessionData( - userId = "userId", +internal fun aDbSessionData( + userId: String = "userId", +) = SessionData( + userId = userId, deviceId = "deviceId", accessToken = "accessToken", refreshToken = "refreshToken", homeserverUrl = "homeserverUrl", - slidingSyncProxy = null, loginTimestamp = null, oidcData = "aOidcData", isTokenValid = 1, diff --git a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/observer/DefaultSessionObserverTest.kt b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/observer/DefaultSessionObserverTest.kt index 8b0184fdc7..54b41a2c60 100644 --- a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/observer/DefaultSessionObserverTest.kt +++ b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/observer/DefaultSessionObserverTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,11 +12,10 @@ import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.sessionstorage.impl.DatabaseSessionStore import io.element.android.libraries.sessionstorage.impl.SessionDatabase -import io.element.android.libraries.sessionstorage.impl.aSessionData +import io.element.android.libraries.sessionstorage.impl.aDbSessionData import io.element.android.libraries.sessionstorage.impl.toApiModel import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runCurrent @@ -23,7 +23,8 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) class DefaultSessionObserverTest { +@OptIn(ExperimentalCoroutinesApi::class) +class DefaultSessionObserverTest { private lateinit var database: SessionDatabase private lateinit var databaseSessionStore: DatabaseSessionStore @@ -46,7 +47,7 @@ import org.junit.Test @Test fun `adding data invokes onSessionCreated`() = runTest { - val sessionData = aSessionData() + val sessionData = aDbSessionData() val sut = createDefaultSessionObserver() runCurrent() val listener = TestSessionListener() @@ -54,12 +55,11 @@ import org.junit.Test databaseSessionStore.addSession(sessionData.toApiModel()) listener.assertEvents(TestSessionListener.Event.Created(sessionData.userId)) sut.removeListener(listener) - coroutineContext.cancelChildren() } @Test fun `adding and deleting data invokes onSessionCreated and onSessionDeleted`() = runTest { - val sessionData = aSessionData() + val sessionData = aDbSessionData() val sut = createDefaultSessionObserver() runCurrent() val listener = TestSessionListener() @@ -69,15 +69,34 @@ import org.junit.Test databaseSessionStore.removeSession(sessionData.userId) listener.assertEvents( TestSessionListener.Event.Created(sessionData.userId), - TestSessionListener.Event.Deleted(sessionData.userId), + TestSessionListener.Event.Deleted(sessionData.userId, true), + ) + } + + @Test + fun `adding and deleting data twice invokes onSessionCreated and onSessionDeleted`() = runTest { + val sessionData1 = aDbSessionData(userId = "user1") + val sessionData2 = aDbSessionData(userId = "user2") + val sut = createDefaultSessionObserver() + runCurrent() + val listener = TestSessionListener() + sut.addListener(listener) + databaseSessionStore.addSession(sessionData1.toApiModel()) + databaseSessionStore.addSession(sessionData2.toApiModel()) + databaseSessionStore.removeSession(sessionData2.userId) + databaseSessionStore.removeSession(sessionData1.userId) + listener.assertEvents( + TestSessionListener.Event.Created(sessionData1.userId), + TestSessionListener.Event.Created(sessionData2.userId), + TestSessionListener.Event.Deleted(sessionData2.userId, wasLastSession = false), + TestSessionListener.Event.Deleted(sessionData1.userId, wasLastSession = true), ) - coroutineContext.cancelChildren() } private fun TestScope.createDefaultSessionObserver(): DefaultSessionObserver { return DefaultSessionObserver( sessionStore = databaseSessionStore, - coroutineScope = this, + coroutineScope = backgroundScope, dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true), ) } diff --git a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/observer/TestSessionListener.kt b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/observer/TestSessionListener.kt index 91ae519538..f104039f57 100644 --- a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/observer/TestSessionListener.kt +++ b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/observer/TestSessionListener.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,7 @@ import io.element.android.libraries.sessionstorage.api.observer.SessionListener class TestSessionListener : SessionListener { sealed interface Event { data class Created(val userId: String) : Event - data class Deleted(val userId: String) : Event + data class Deleted(val userId: String, val wasLastSession: Boolean) : Event } private val trackRecord: MutableList = mutableListOf() @@ -22,8 +23,8 @@ class TestSessionListener : SessionListener { trackRecord.add(Event.Created(userId)) } - override suspend fun onSessionDeleted(userId: String) { - trackRecord.add(Event.Deleted(userId)) + override suspend fun onSessionDeleted(userId: String, wasLastSession: Boolean) { + trackRecord.add(Event.Deleted(userId, wasLastSession)) } fun assertEvents(vararg events: Event) { diff --git a/libraries/session-storage/test/build.gradle.kts b/libraries/session-storage/test/build.gradle.kts index 59759d0d3f..cfdc3018a9 100644 --- a/libraries/session-storage/test/build.gradle.kts +++ b/libraries/session-storage/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/InMemorySessionStore.kt b/libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/InMemorySessionStore.kt index c8f3078e7a..05e58ac623 100644 --- a/libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/InMemorySessionStore.kt +++ b/libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/InMemorySessionStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -67,6 +68,10 @@ class InMemorySessionStore( return sessionDataListFlow.value } + override suspend fun numberOfSessions(): Int { + return sessionDataListFlow.value.size + } + override suspend fun getLatestSession(): SessionData? { return sessionDataListFlow.value.firstOrNull() } @@ -76,8 +81,6 @@ class InMemorySessionStore( } override suspend fun removeSession(sessionId: String) { - val currentList = sessionDataListFlow.value.toMutableList() - currentList.removeAll { it.userId == sessionId } - sessionDataListFlow.value = currentList + sessionDataListFlow.value = sessionDataListFlow.value.filter { it.userId != sessionId } } } diff --git a/libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/SessionData.kt b/libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/SessionData.kt index 61b5370813..c791a20620 100644 --- a/libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/SessionData.kt +++ b/libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/SessionData.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -30,7 +31,6 @@ fun aSessionData( refreshToken = refreshToken, homeserverUrl = "aHomeserverUrl", oidcData = null, - slidingSyncProxy = null, loginTimestamp = null, isTokenValid = isTokenValid, loginType = LoginType.UNKNOWN, diff --git a/libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/observer/FakeSessionObserver.kt b/libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/observer/FakeSessionObserver.kt index 817046517a..fdf5cc5f1b 100644 --- a/libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/observer/FakeSessionObserver.kt +++ b/libraries/session-storage/test/src/main/kotlin/io/element/android/libraries/sessionstorage/test/observer/FakeSessionObserver.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -28,7 +29,7 @@ class FakeSessionObserver : SessionObserver { listeners.forEach { it.onSessionCreated(userId) } } - suspend fun onSessionDeleted(userId: String) { - listeners.forEach { it.onSessionDeleted(userId) } + suspend fun onSessionDeleted(userId: String, wasLastSession: Boolean = true) { + listeners.forEach { it.onSessionDeleted(userId, wasLastSession = wasLastSession) } } } 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 index 9bb6cd7552..ff1010a415 100644 --- 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/testtags/build.gradle.kts b/libraries/testtags/build.gradle.kts index 6db99062a4..68f5a34770 100644 --- a/libraries/testtags/build.gradle.kts +++ b/libraries/testtags/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/Compose.kt b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/Compose.kt index 7f8700adb4..21ce0533d1 100644 --- a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/Compose.kt +++ b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/Compose.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt index 6363c6781b..e564ddac41 100644 --- a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt +++ b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/build.gradle.kts b/libraries/textcomposer/impl/build.gradle.kts index 6f735bec23..a339890201 100644 --- a/libraries/textcomposer/impl/build.gradle.kts +++ b/libraries/textcomposer/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/CaptionWarningBottomSheet.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/CaptionWarningBottomSheet.kt index 4154d7a594..23b4fae3a9 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/CaptionWarningBottomSheet.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/CaptionWarningBottomSheet.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/ComposerModeView.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/ComposerModeView.kt index 1299f99463..ac64245668 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/ComposerModeView.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/ComposerModeView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/ElementRichTextEditorStyle.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/ElementRichTextEditorStyle.kt index 357507ba23..57f0abefbb 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/ElementRichTextEditorStyle.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/ElementRichTextEditorStyle.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/MessageComposerModeSpecialProvider.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/MessageComposerModeSpecialProvider.kt index 8deae21375..60850d26c2 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/MessageComposerModeSpecialProvider.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/MessageComposerModeSpecialProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/SoftKeyboardEffect.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/SoftKeyboardEffect.kt index e98c6453e7..55a985efca 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/SoftKeyboardEffect.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/SoftKeyboardEffect.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt index 1126a39466..f2070c9610 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -52,7 +53,7 @@ import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.androidutils.ui.showKeyboard -import io.element.android.libraries.designsystem.components.media.createFakeWaveform +import io.element.android.libraries.designsystem.components.media.WaveFormSamples import io.element.android.libraries.designsystem.preview.DAY_MODE_NAME import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.NIGHT_MODE_NAME @@ -139,8 +140,8 @@ fun TextComposer( } val layoutModifier = modifier - .fillMaxSize() - .height(IntrinsicSize.Min) + .fillMaxSize() + .height(IntrinsicSize.Min) val composerOptionsButton: @Composable () -> Unit = remember(composerMode) { @Composable { @@ -177,18 +178,18 @@ fun TextComposer( @Composable { TextInputBox( modifier = Modifier - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - ) { - coroutineScope.launch { - state.requestFocus() - view.showKeyboard() - } + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + ) { + coroutineScope.launch { + state.requestFocus() + view.showKeyboard() } - .semantics { - hideFromAccessibility() - }, + } + .semantics { + hideFromAccessibility() + }, composerMode = composerMode, onResetComposerMode = onResetComposerMode, isTextEmpty = state.richTextEditorState.messageHtml.isEmpty(), @@ -198,8 +199,8 @@ fun TextComposer( placeholder = placeholder, registerStateUpdates = true, modifier = Modifier - .padding(top = 6.dp, bottom = 6.dp) - .fillMaxWidth(), + .padding(top = 6.dp, bottom = 6.dp) + .fillMaxWidth(), style = ElementRichTextEditorStyle.composerStyle(hasFocus = state.richTextEditorState.hasFocus), resolveMentionDisplay = resolveMentionDisplay, resolveRoomMentionDisplay = resolveAtRoomMentionDisplay, @@ -298,7 +299,10 @@ fun TextComposer( onSeek = onSeekVoiceMessage, ) is VoiceMessageState.Recording -> - VoiceMessageRecording(voiceMessageState.levels, voiceMessageState.duration) + VoiceMessageRecording( + levels = voiceMessageState.levels, + duration = voiceMessageState.duration, + ) VoiceMessageState.Idle -> {} } } @@ -419,8 +423,8 @@ private fun StandardLayout( if (voiceMessageState is VoiceMessageState.Preview || voiceMessageState is VoiceMessageState.Recording) { Box( modifier = Modifier - .padding(bottom = 5.dp, top = 5.dp, end = 3.dp, start = 3.dp) - .size(48.dp), + .padding(bottom = 5.dp, top = 5.dp, end = 3.dp, start = 3.dp) + .size(48.dp), contentAlignment = Alignment.Center, ) { voiceDeleteButton() @@ -430,8 +434,8 @@ private fun StandardLayout( } Box( modifier = Modifier - .padding(bottom = 8.dp, top = 8.dp) - .weight(1f) + .padding(bottom = 8.dp, top = 8.dp) + .weight(1f) ) { voiceRecording() } @@ -444,17 +448,17 @@ private fun StandardLayout( } Box( modifier = Modifier - .padding(bottom = 8.dp, top = 8.dp) - .weight(1f) + .padding(bottom = 8.dp, top = 8.dp) + .weight(1f) ) { textInput() } } Box( Modifier - .padding(bottom = 5.dp, top = 5.dp, end = 6.dp, start = 6.dp) - .size(48.dp) - .clearAndSetSemantics(endButtonA11y), + .padding(bottom = 5.dp, top = 5.dp, end = 6.dp, start = 6.dp) + .size(48.dp) + .clearAndSetSemantics(endButtonA11y), contentAlignment = Alignment.Center, ) { endButton() @@ -505,8 +509,8 @@ private fun TextFormattingLayout( } Box( modifier = Modifier - .weight(1f) - .padding(horizontal = 12.dp) + .weight(1f) + .padding(horizontal = 12.dp) ) { textInput() } @@ -525,11 +529,11 @@ private fun TextFormattingLayout( } Box( modifier = Modifier - .padding( - start = 14.dp, - end = 6.dp, - ) - .clearAndSetSemantics(endButtonA11y) + .padding( + start = 14.dp, + end = 6.dp, + ) + .clearAndSetSemantics(endButtonA11y) ) { sendButton() } @@ -551,12 +555,12 @@ private fun TextInputBox( Column( modifier = Modifier - .clip(roundedCorners) - .border(0.5.dp, borderColor, roundedCorners) - .background(color = bgColor) - .requiredHeightIn(min = 42.dp) - .fillMaxSize() - .then(modifier), + .clip(roundedCorners) + .border(0.5.dp, borderColor, roundedCorners) + .background(color = bgColor) + .requiredHeightIn(min = 42.dp) + .fillMaxSize() + .then(modifier), ) { if (composerMode is MessageComposerMode.Special) { ComposerModeView( @@ -566,8 +570,8 @@ private fun TextInputBox( } Box( modifier = Modifier - .padding(top = 4.dp, bottom = 4.dp, start = 12.dp, end = 12.dp) - .then(Modifier.testTag(TestTags.textEditor)), + .padding(top = 4.dp, bottom = 4.dp, start = 12.dp, end = 12.dp) + .then(Modifier.testTag(TestTags.textEditor)), contentAlignment = Alignment.CenterStart, ) { textInput() @@ -575,9 +579,9 @@ private fun TextInputBox( var showBottomSheet by remember { mutableStateOf(false) } Icon( modifier = Modifier - .clickable { showBottomSheet = true } - .padding(horizontal = 8.dp, vertical = 4.dp) - .align(Alignment.CenterEnd), + .clickable { showBottomSheet = true } + .padding(horizontal = 8.dp, vertical = 4.dp) + .align(Alignment.CenterEnd), imageVector = CompoundIcons.InfoSolid(), tint = ElementTheme.colors.iconCriticalPrimary, contentDescription = null, @@ -808,30 +812,33 @@ internal fun TextComposerCaptionPreview() = ElementPreview { internal fun TextComposerVoicePreview() = ElementPreview { PreviewColumn( items = persistentListOf( - VoiceMessageState.Recording(61.seconds, createFakeWaveform()), + VoiceMessageState.Recording( + duration = 61.seconds, + levels = WaveFormSamples.realisticWaveForm, + ), VoiceMessageState.Preview( isSending = false, isPlaying = false, showCursor = false, - waveform = createFakeWaveform(), + waveform = WaveFormSamples.realisticWaveForm, time = 0.seconds, - playbackProgress = 0.0f + playbackProgress = 0.0f, ), VoiceMessageState.Preview( isSending = false, isPlaying = true, showCursor = true, - waveform = createFakeWaveform(), + waveform = WaveFormSamples.realisticWaveForm, time = 3.seconds, - playbackProgress = 0.2f + playbackProgress = 0.2f, ), VoiceMessageState.Preview( isSending = true, isPlaying = false, showCursor = false, - waveform = createFakeWaveform(), + waveform = WaveFormSamples.realisticWaveForm, time = 61.seconds, - playbackProgress = 0.0f + playbackProgress = 0.0f, ), ) ) { voiceMessageState -> @@ -848,12 +855,15 @@ internal fun TextComposerVoicePreview() = ElementPreview { internal fun TextComposerVoiceNotEncryptedPreview() = ElementPreview { PreviewColumn( items = persistentListOf( - VoiceMessageState.Recording(61.seconds, createFakeWaveform()), + VoiceMessageState.Recording( + duration = 61.seconds, + levels = WaveFormSamples.realisticWaveForm, + ), VoiceMessageState.Preview( isSending = false, isPlaying = false, showCursor = false, - waveform = createFakeWaveform(), + waveform = WaveFormSamples.realisticWaveForm, time = 0.seconds, playbackProgress = 0.0f ), @@ -861,7 +871,7 @@ internal fun TextComposerVoiceNotEncryptedPreview() = ElementPreview { isSending = false, isPlaying = true, showCursor = true, - waveform = createFakeWaveform(), + waveform = WaveFormSamples.realisticWaveForm, time = 3.seconds, playbackProgress = 0.2f ), @@ -869,7 +879,7 @@ internal fun TextComposerVoiceNotEncryptedPreview() = ElementPreview { isSending = true, isPlaying = false, showCursor = false, - waveform = createFakeWaveform(), + waveform = WaveFormSamples.realisticWaveForm, time = 61.seconds, playbackProgress = 0.0f ), diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposerLinkDialog.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposerLinkDialog.kt index 533a03e62c..3ee191d724 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposerLinkDialog.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposerLinkDialog.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOption.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOption.kt index 3a07dd5ee6..db133a9a09 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOption.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOption.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOptionState.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOptionState.kt index b572bd8965..8c41a3db09 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOptionState.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/FormattingOptionState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/LiveWaveformView.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/LiveWaveformView.kt index 836089fc7d..d18b85c987 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/LiveWaveformView.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/LiveWaveformView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -14,7 +15,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf @@ -50,27 +50,23 @@ fun LiveWaveformView( linePadding: Dp = 2.dp, ) { var canvasSize by remember { mutableStateOf(DpSize(0.dp, 0.dp)) } - var parentWidth by remember { mutableIntStateOf(0) } - - val waveformWidth by remember(levels, lineWidth, linePadding) { - derivedStateOf { - levels.size * (lineWidth.value + linePadding.value) - } + val waveformWidth = remember(levels.size, lineWidth, linePadding) { + levels.size * (lineWidth.value + linePadding.value) } Box( contentAlignment = Alignment.CenterEnd, modifier = modifier - .fillMaxWidth() - .height(waveFormHeight) - .onSizeChanged { parentWidth = it.width } + .fillMaxWidth() + .height(waveFormHeight) + .onSizeChanged { parentWidth = it.width } ) { Canvas( modifier = Modifier - .width(Dp(waveformWidth)) - .graphicsLayer(alpha = DEFAULT_GRAPHICS_LAYER_ALPHA) - .then(modifier) + .width(Dp(waveformWidth)) + .graphicsLayer(alpha = DEFAULT_GRAPHICS_LAYER_ALPHA) + .then(modifier) ) { val width = min(waveformWidth, parentWidth.toFloat()) canvasSize = DpSize(width.dp, size.height.toDp()) diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/SendButton.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/SendButton.kt index 4ee00e550d..a136f63b09 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/SendButton.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/SendButton.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/TextFormatting.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/TextFormatting.kt index 08c1bec23a..64e289a3fd 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/TextFormatting.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/TextFormatting.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/TextInputRoundedCornerShape.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/TextInputRoundedCornerShape.kt index c7d8fb4881..c6519d5168 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/TextInputRoundedCornerShape.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/TextInputRoundedCornerShape.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageDeleteButton.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageDeleteButton.kt index 7d7b54b3c6..af5f443cc7 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageDeleteButton.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageDeleteButton.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessagePreview.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessagePreview.kt index d965a3d0d6..d893979889 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessagePreview.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessagePreview.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -29,8 +30,8 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.libraries.designsystem.components.media.WaveFormSamples import io.element.android.libraries.designsystem.components.media.WaveformPlaybackView -import io.element.android.libraries.designsystem.components.media.createFakeWaveform import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon @@ -138,14 +139,18 @@ private fun PlayerButton( private fun PauseIcon() = Icon( imageVector = CompoundIcons.PauseSolid(), contentDescription = stringResource(id = CommonStrings.a11y_pause), - modifier = Modifier.size(20.dp).padding(2.dp), + modifier = Modifier + .size(20.dp) + .padding(2.dp), ) @Composable private fun PlayIcon() = Icon( imageVector = CompoundIcons.PlaySolid(), contentDescription = stringResource(id = CommonStrings.a11y_play), - modifier = Modifier.size(20.dp).padding(2.dp), + modifier = Modifier + .size(20.dp) + .padding(2.dp), ) @PreviewsDayNight @@ -160,7 +165,7 @@ internal fun VoiceMessagePreviewPreview() = ElementPreview { time = 2.seconds, playbackProgress = 0.2f, showCursor = true, - waveform = createFakeWaveform() + waveform = WaveFormSamples.longRealisticWaveForm, ) AVoiceMessagePreview( isInteractive = true, @@ -168,7 +173,7 @@ internal fun VoiceMessagePreviewPreview() = ElementPreview { time = 0.seconds, playbackProgress = 0.0f, showCursor = true, - waveform = createFakeWaveform() + waveform = WaveFormSamples.longRealisticWaveForm, ) AVoiceMessagePreview( isInteractive = false, @@ -176,7 +181,7 @@ internal fun VoiceMessagePreviewPreview() = ElementPreview { time = 789.seconds, playbackProgress = 0.0f, showCursor = false, - waveform = createFakeWaveform() + waveform = WaveFormSamples.longRealisticWaveForm, ) } } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecorderButton.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecorderButton.kt index 3a92dfbeb0..32fe2847c2 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecorderButton.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecorderButton.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecording.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecording.kt index 7a56d37b3c..e742372108 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecording.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecording.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -73,7 +74,7 @@ internal fun VoiceMessageRecording( modifier = Modifier .height(26.dp) .weight(1f), - levels = levels + levels = levels, ) } } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownEditText.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownEditText.kt index 413760e209..bd808c9acb 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownEditText.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownEditText.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt index ffce571dc4..725abe3dae 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/MarkdownTextInput.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -167,6 +168,7 @@ private fun Editable.checkSuggestionNeeded(): Suggestion? { '@' -> SuggestionType.Mention '#' -> SuggestionType.Room '/' -> SuggestionType.Command + ':' -> SuggestionType.Emoji else -> error("Unknown suggestion type. This should never happen.") } Suggestion(startOfWord, endOfWord, suggestionType, text) diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/StableCharSequence.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/StableCharSequence.kt index bc1de30826..2b2f42a223 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/StableCharSequence.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/markdown/StableCharSequence.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt index 76587d6e12..2531a605b9 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanFormatter.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanFormatter.kt index 4c74a6edd5..96a3dc6335 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanFormatter.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanFormatter.kt @@ -1,14 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.textcomposer.mentions import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.RoomIdOrAlias import io.element.android.libraries.matrix.api.core.UserId @@ -28,7 +28,6 @@ interface MentionSpanFormatter { * based on its MentionType and context. */ @ContributesBinding(RoomScope::class) -@Inject class DefaultMentionSpanFormatter( private val roomMemberProfilesCache: RoomMemberProfilesCache, private val roomNamesCache: RoomNamesCache, diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt index edaf417db7..5942ca9d22 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanTheme.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanTheme.kt index e23fc8e83a..f64c8112d1 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanTheme.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanTheme.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanUpdater.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanUpdater.kt index 9a9714b351..a530d64437 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanUpdater.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanUpdater.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,7 +14,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.staticCompositionLocalOf import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache @@ -27,7 +27,6 @@ interface MentionSpanUpdater { } @ContributesBinding(RoomScope::class) -@Inject class DefaultMentionSpanUpdater( private val formatter: MentionSpanFormatter, private val theme: MentionSpanTheme, diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/ResolvedSuggestion.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/ResolvedSuggestion.kt index fe5d9c1174..d91735fb83 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/ResolvedSuggestion.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/ResolvedSuggestion.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/Fixtures.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/Fixtures.kt index d9a71273f1..1d76494ae0 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/Fixtures.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/Fixtures.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MarkdownTextEditorState.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MarkdownTextEditorState.kt index 14f56f3bbb..ba7e3c50c0 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MarkdownTextEditorState.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/MarkdownTextEditorState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -128,15 +129,15 @@ class MarkdownTextEditorState( } @Parcelize - data class SavedState( + data class SavedValue( val text: CharSequence, val selectionStart: Int, val selectionEnd: Int, ) : Parcelable } -object MarkdownTextEditorStateSaver : Saver { - override fun restore(value: MarkdownTextEditorState.SavedState): MarkdownTextEditorState { +object MarkdownTextEditorStateSaver : Saver { + override fun restore(value: MarkdownTextEditorState.SavedValue): MarkdownTextEditorState { return MarkdownTextEditorState( initialText = "", initialFocus = false, @@ -146,8 +147,8 @@ object MarkdownTextEditorStateSaver : Saver Mention PatternKey.Slash -> Command PatternKey.Hash -> Room + PatternKey.Colon -> Emoji is PatternKey.Custom -> Custom(key.v1) } } diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/TextEditorState.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/TextEditorState.kt index b0090185d2..7161268a6b 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/TextEditorState.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/TextEditorState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessagePlayerEvent.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessagePlayerEvent.kt index 4da60276f9..f806dc4b6a 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessagePlayerEvent.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessagePlayerEvent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageRecorderEvent.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageRecorderEvent.kt index efdd2a4640..ae78501bb8 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageRecorderEvent.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageRecorderEvent.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageState.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageState.kt index 8fa0ffbe55..54fa2bcae7 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageState.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -21,11 +22,13 @@ sealed interface VoiceMessageState { val showCursor: Boolean, val playbackProgress: Float, val time: Duration, + // Values are between 0 and 1 val waveform: ImmutableList, ) : VoiceMessageState data class Recording( val duration: Duration, + // Values are between 0 and 1 val levels: ImmutableList, ) : VoiceMessageState } diff --git a/libraries/textcomposer/impl/src/main/res/values-fa/translations.xml b/libraries/textcomposer/impl/src/main/res/values-fa/translations.xml index 602d35363c..fd55fdf9d9 100644 --- a/libraries/textcomposer/impl/src/main/res/values-fa/translations.xml +++ b/libraries/textcomposer/impl/src/main/res/values-fa/translations.xml @@ -2,7 +2,7 @@ "افزودن پیوست" "تغییر وضعیت سیاههٔ گلوله‌ای" - "بستن گزینه‌های قالب‌بندی" + "لغو و بستن قالب‌بندی متن" "تغییر حالت بلوک کد" "افزودن عنوان" "پیام رمزنگاری شده…" diff --git a/libraries/textcomposer/impl/src/main/res/values-hr/translations.xml b/libraries/textcomposer/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..2b116627fb --- /dev/null +++ b/libraries/textcomposer/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,33 @@ + + + "Dodaj privitak" + "Uključi/isključi popis s grafičkim oznakama" + "Otkaži i zatvori oblikovanje teksta" + "Uključi/isključi blok koda" + "Dodaj opis" + "Šifrirana poruka…" + "Poruka…" + "Nešifrirana poruka…" + "Izradi poveznicu" + "Uredi poveznicu" + "%1$s, stanje: %2$s" + "Primijeni podebljano" + "Primijeni kurziv" + "onemogućen" + "isključen" + "uključen" + "Primijeni precrtavanje" + "Primijeni podcrtavanje" + "Uključi/isključi prikaz na cijelom zaslonu" + "Uvlaka" + "Primijeni kod u retku" + "Postavi poveznicu" + "Uključi/isključi numerirani popis" + "Otvori mogućnosti sastavljanja" + "Uključi/isključi citat" + "Ukloni poveznicu" + "Poništi uvlaku" + "Poveznica" + "Opisi možda neće biti vidljivi osobama koji se služe starijim aplikacijama." + "Držite za snimanje" + diff --git a/libraries/textcomposer/impl/src/main/res/values-uz/translations.xml b/libraries/textcomposer/impl/src/main/res/values-uz/translations.xml index 9b28fd8ef6..d5a7002665 100644 --- a/libraries/textcomposer/impl/src/main/res/values-uz/translations.xml +++ b/libraries/textcomposer/impl/src/main/res/values-uz/translations.xml @@ -5,11 +5,17 @@ "Formatlash parametrlarini yoping" "Kod blokini almashtirish" "Taglavha kiritish" + "Shifrlangan xabar…" "Xabar…" + "Shifrlanmagan xabar…" "Havola yarating" "Havolani tahrirlash" + "%1$s, holat: %2$s" "Qalin formatni qo\'llang" "Kursiv formatini qo\'llang" + "oʻchirilgan" + "o\'chiq" + "yoniq" "Chizilgan formatni qo\'llash" "Pastki chiziq formatini qo\'llang" "Toʻliq ekran rejimiga oʻtish" @@ -22,5 +28,6 @@ "Havolani olib tashlang" "Paragrafni bekor qilish" "Havola" + "Taglavhalar eski ilovalardan foydalanuvchilarga ko‘rinmasligi mumkin." "Yozib olish uchun bosib turing" diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt index fa003b74d8..9a65ca0ad5 100644 --- a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt +++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/components/markdown/MarkdownTextInputTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/IntentionalMentionSpanProviderTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/IntentionalMentionSpanProviderTest.kt index de4b9bc64a..7bac75cc75 100644 --- a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/IntentionalMentionSpanProviderTest.kt +++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/IntentionalMentionSpanProviderTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanFormatterTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanFormatterTest.kt index 2d0156a3df..522912b3d2 100644 --- a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanFormatterTest.kt +++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanFormatterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderFixture.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderFixture.kt index cc40a56bf4..2537615be3 100644 --- a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderFixture.kt +++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/mentions/MentionSpanProviderFixture.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/model/MarkdownTextEditorStateTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/model/MarkdownTextEditorStateTest.kt index 2c7905e2da..04b700925e 100644 --- a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/model/MarkdownTextEditorStateTest.kt +++ b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/impl/model/MarkdownTextEditorStateTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/troubleshoot/api/build.gradle.kts b/libraries/troubleshoot/api/build.gradle.kts index 6ce2d3e9f8..177b2a5fce 100644 --- a/libraries/troubleshoot/api/build.gradle.kts +++ b/libraries/troubleshoot/api/build.gradle.kts @@ -1,11 +1,12 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { - id("io.element.android-library") + id("io.element.android-compose-library") } android { diff --git a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/NotificationTroubleShootEntryPoint.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/NotificationTroubleShootEntryPoint.kt index bf0c6bb883..4000fd1043 100644 --- a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/NotificationTroubleShootEntryPoint.kt +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/NotificationTroubleShootEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,15 +14,14 @@ import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint interface NotificationTroubleShootEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - - interface NodeBuilder { - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: Callback, + ): Node interface Callback : Plugin { fun onDone() - fun openIgnoredUsers() + fun navigateToBlockedUsers() } } diff --git a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/PushHistoryEntryPoint.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/PushHistoryEntryPoint.kt index 0eab9b8e5a..e5a3c9c90c 100644 --- a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/PushHistoryEntryPoint.kt +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/PushHistoryEntryPoint.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,15 +16,14 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId interface PushHistoryEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - - interface NodeBuilder { - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: Callback, + ): Node interface Callback : Plugin { fun onDone() - fun navigateTo(roomId: RoomId, eventId: EventId) + fun navigateToEvent(roomId: RoomId, eventId: EventId) } } diff --git a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootNavigator.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootNavigator.kt index 0cce358072..a4be22e70b 100644 --- a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootNavigator.kt +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootNavigator.kt @@ -1,12 +1,13 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.troubleshoot.api.test interface NotificationTroubleshootNavigator { - fun openIgnoredUsers() + fun navigateToBlockedUsers() } diff --git a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTest.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTest.kt index f729cd3300..23144644d1 100644 --- a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTest.kt +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestDelegate.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestDelegate.kt index 830ba74818..6dbf1f190d 100644 --- a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestDelegate.kt +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestDelegate.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestState.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestState.kt index 4069d78cb3..f022651024 100644 --- a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestState.kt +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestState.kt @@ -1,17 +1,21 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.troubleshoot.api.test +import androidx.compose.runtime.Immutable + data class NotificationTroubleshootTestState( val name: String, val description: String, val status: Status, ) { + @Immutable sealed interface Status { data class Idle(val visible: Boolean) : Status data object InProgress : Status diff --git a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/TestFilterData.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/TestFilterData.kt index f0c5c8378f..960d78e108 100644 --- a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/TestFilterData.kt +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/TestFilterData.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/troubleshoot/impl/build.gradle.kts b/libraries/troubleshoot/impl/build.gradle.kts index bcc7c4357b..e2efdb3a6a 100644 --- a/libraries/troubleshoot/impl/build.gradle.kts +++ b/libraries/troubleshoot/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/DefaultNotificationTroubleShootEntryPoint.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/DefaultNotificationTroubleShootEntryPoint.kt index b9d9c91814..117b3521bc 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/DefaultNotificationTroubleShootEntryPoint.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/DefaultNotificationTroubleShootEntryPoint.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,28 +10,18 @@ package io.element.android.libraries.troubleshoot.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.architecture.createNode import io.element.android.libraries.troubleshoot.api.NotificationTroubleShootEntryPoint @ContributesBinding(AppScope::class) -@Inject class DefaultNotificationTroubleShootEntryPoint : NotificationTroubleShootEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NotificationTroubleShootEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : NotificationTroubleShootEntryPoint.NodeBuilder { - override fun callback(callback: NotificationTroubleShootEntryPoint.Callback): NotificationTroubleShootEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: NotificationTroubleShootEntryPoint.Callback, + ): Node { + return parentNode.createNode(buildContext, listOf(callback)) } } diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsEvents.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsEvents.kt index 894c439324..94330907ea 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsEvents.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsNode.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsNode.kt index 508010a3d6..ce24c723c5 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsNode.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsNode.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,11 +13,11 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.SessionScope import io.element.android.libraries.troubleshoot.api.NotificationTroubleShootEntryPoint import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator @@ -31,20 +32,13 @@ class TroubleshootNotificationsNode( factory: TroubleshootNotificationsPresenter.Factory, ) : Node(buildContext, plugins = plugins), NotificationTroubleshootNavigator { + private val callback: NotificationTroubleShootEntryPoint.Callback = callback() private val presenter = factory.create( navigator = this, ) - private fun onDone() { - plugins().forEach { - it.onDone() - } - } - - override fun openIgnoredUsers() { - plugins().forEach { - it.openIgnoredUsers() - } + override fun navigateToBlockedUsers() { + callback.navigateToBlockedUsers() } @Composable @@ -53,7 +47,7 @@ class TroubleshootNotificationsNode( val state = presenter.present() TroubleshootNotificationsView( state = state, - onBackClick = ::onDone, + onBackClick = callback::onDone, modifier = modifier, ) } diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenter.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenter.kt index 07840b023c..00958b1f99 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenter.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -37,7 +38,7 @@ class TroubleshootNotificationsPresenter( } val testSuiteState by troubleshootTestSuite.state.collectAsState() - fun handleEvents(event: TroubleshootNotificationsEvents) { + fun handleEvent(event: TroubleshootNotificationsEvents) { when (event) { TroubleshootNotificationsEvents.StartTests -> coroutineScope.launch { troubleshootTestSuite.runTestSuite(this) @@ -57,7 +58,7 @@ class TroubleshootNotificationsPresenter( return TroubleshootNotificationsState( testSuiteState = testSuiteState, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } } diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsState.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsState.kt index 2f7bed8202..92f4d668c4 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsState.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsStateProvider.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsStateProvider.kt index 8c2708ed9d..5cc565559e 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsStateProvider.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsView.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsView.kt index 9feeeb505e..fa5e7078d2 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsView.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuite.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuite.kt index c26510fc92..52f0d894dc 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuite.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuite.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,6 +11,7 @@ package io.element.android.libraries.troubleshoot.impl import dev.zacsweers.metro.Inject import im.vector.app.features.analytics.plan.NotificationTroubleshoot import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.push.api.GetCurrentPushProvider import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest @@ -25,6 +27,7 @@ import kotlinx.coroutines.flow.onEach @Inject class TroubleshootTestSuite( + private val sessionId: SessionId, private val notificationTroubleshootTests: Set<@JvmSuppressWildcards NotificationTroubleshootTest>, private val getCurrentPushProvider: GetCurrentPushProvider, private val analyticsService: AnalyticsService, @@ -41,7 +44,7 @@ class TroubleshootTestSuite( suspend fun start(coroutineScope: CoroutineScope) { val testFilterData = TestFilterData( - currentPushProviderName = getCurrentPushProvider.getCurrentPushProvider() + currentPushProviderName = getCurrentPushProvider.getCurrentPushProvider(sessionId) ) tests = notificationTroubleshootTests .filter { it.isRelevant(testFilterData) } diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuiteState.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuiteState.kt index c5ffbf89bc..c51af92be7 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuiteState.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuiteState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/DefaultPushHistoryEntryPoint.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/DefaultPushHistoryEntryPoint.kt index 9a33848cae..d19dcb603e 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/DefaultPushHistoryEntryPoint.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/DefaultPushHistoryEntryPoint.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,28 +10,18 @@ package io.element.android.libraries.troubleshoot.impl.history import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.plugin.Plugin import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.architecture.createNode import io.element.android.libraries.troubleshoot.api.PushHistoryEntryPoint @ContributesBinding(AppScope::class) -@Inject class DefaultPushHistoryEntryPoint : PushHistoryEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): PushHistoryEntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : PushHistoryEntryPoint.NodeBuilder { - override fun callback(callback: PushHistoryEntryPoint.Callback): PushHistoryEntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: PushHistoryEntryPoint.Callback, + ): Node { + return parentNode.createNode(buildContext, listOf(callback)) } } diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryEvents.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryEvents.kt index 893be607a0..5116e0e101 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryEvents.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryEvents.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryNode.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryNode.kt index 69070298ec..ed5eaad997 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryNode.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryNode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,11 +13,11 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.annotations.ContributesNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId @@ -31,16 +32,10 @@ class PushHistoryNode( presenterFactory: PushHistoryPresenter.Factory, private val screenTracker: ScreenTracker, ) : Node(buildContext, plugins = plugins), PushHistoryNavigator { - private fun onDone() { - plugins().forEach { - it.onDone() - } - } + private val callback: PushHistoryEntryPoint.Callback = callback() override fun navigateTo(roomId: RoomId, eventId: EventId) { - plugins().forEach { - it.navigateTo(roomId, eventId) - } + callback.navigateToEvent(roomId, eventId) } private val presenter = presenterFactory.create(this) @@ -51,7 +46,7 @@ class PushHistoryNode( val state = presenter.present() PushHistoryView( state = state, - onBackClick = ::onDone, + onBackClick = callback::onDone, modifier = modifier, ) } diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenter.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenter.kt index b98fcee970..ad1878051d 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenter.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -61,7 +62,7 @@ class PushHistoryPresenter( var resetAction: AsyncAction by remember { mutableStateOf(AsyncAction.Uninitialized) } var showNotSameAccountError by remember { mutableStateOf(false) } - fun handleEvents(event: PushHistoryEvents) { + fun handleEvent(event: PushHistoryEvents) { when (event) { is PushHistoryEvents.SetShowOnlyErrors -> { showOnlyErrors = event.showOnlyErrors @@ -97,7 +98,7 @@ class PushHistoryPresenter( showOnlyErrors = showOnlyErrors, resetAction = resetAction, showNotSameAccountError = showNotSameAccountError, - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } } diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryState.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryState.kt index b4b6d7f75e..dbc98bdbe3 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryState.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryState.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryStateProvider.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryStateProvider.kt index 11d9c509cc..e7384048f4 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryStateProvider.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryStateProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryView.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryView.kt index 3193716d34..9ae748d03a 100644 --- a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryView.kt +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryView.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/troubleshoot/impl/src/main/res/values-hr/translations.xml b/libraries/troubleshoot/impl/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..a39bf5d2ad --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/res/values-hr/translations.xml @@ -0,0 +1,12 @@ + + + "Povijest push obavijesti" + "Pokreni testove" + "Ponovno pokreni testove" + "Neki testovi nisu uspjeli. Provjerite pojedinosti." + "Pokrenite testove kako biste otkrili bilo kakve probleme u konfiguraciji koji bi mogli uzrokovati da se obavijesti ne ponašaju kako se očekuje." + "Pokušaj popravka" + "Svi testovi uspješno su izvedeni." + "Rješavanje problema s obavijestima" + "Neki testovi zahtijevaju vašu pažnju. Provjerite pojedinosti." + diff --git a/libraries/troubleshoot/impl/src/main/res/values-uz/translations.xml b/libraries/troubleshoot/impl/src/main/res/values-uz/translations.xml index e01e098f5f..d7fa7207e7 100644 --- a/libraries/troubleshoot/impl/src/main/res/values-uz/translations.xml +++ b/libraries/troubleshoot/impl/src/main/res/values-uz/translations.xml @@ -1,5 +1,6 @@ + "Bildirishnoma tarixi" "Testlarni ishga tushirish" "Testlarni qayta ishga tushirish" "Ba’zi testlar muvaffaqiyatsiz tugadi. Iltimos, tafsilotlarni tekshirib chiqing." diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/DefaultNotificationTroubleShootEntryPointTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/DefaultNotificationTroubleShootEntryPointTest.kt index e0d817a4ca..4bef725e0a 100644 --- a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/DefaultNotificationTroubleShootEntryPointTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/DefaultNotificationTroubleShootEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -34,11 +35,13 @@ class DefaultNotificationTroubleShootEntryPointTest { } val callback = object : NotificationTroubleShootEntryPoint.Callback { override fun onDone() = lambdaError() - override fun openIgnoredUsers() = lambdaError() + override fun navigateToBlockedUsers() = lambdaError() } - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + callback = callback, + ) assertThat(result).isInstanceOf(TroubleshootNotificationsNode::class.java) assertThat(result.plugins).contains(callback) } diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/FakeNotificationTroubleshootTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/FakeNotificationTroubleshootTest.kt index 7fa2e93d85..597e45a5f7 100644 --- a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/FakeNotificationTroubleshootTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/FakeNotificationTroubleshootTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenterTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenterTest.kt index 4d6b338db7..c8f8382ecf 100644 --- a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenterTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,6 +10,7 @@ package io.element.android.libraries.troubleshoot.impl import com.google.common.truth.Truth.assertThat import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.push.test.FakeGetCurrentPushProvider import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootNavigator import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest @@ -170,6 +172,7 @@ private fun createTroubleshootTestSuite( currentPushProvider: String? = null, ): TroubleshootTestSuite { return TroubleshootTestSuite( + sessionId = A_SESSION_ID, notificationTroubleshootTests = tests, getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider), analyticsService = FakeAnalyticsService(), @@ -178,7 +181,7 @@ private fun createTroubleshootTestSuite( internal fun createTroubleshootNotificationsPresenter( navigator: NotificationTroubleshootNavigator = object : NotificationTroubleshootNavigator { - override fun openIgnoredUsers() = lambdaError() + override fun navigateToBlockedUsers() = lambdaError() }, troubleshootTestSuite: TroubleshootTestSuite = createTroubleshootTestSuite(), ): TroubleshootNotificationsPresenter { diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsViewTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsViewTest.kt index 8dcf18217f..0ba6c22710 100644 --- a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsViewTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsViewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/DefaultPushHistoryEntryPointTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/DefaultPushHistoryEntryPointTest.kt index 858956488c..1aa98fc157 100644 --- a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/DefaultPushHistoryEntryPointTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/DefaultPushHistoryEntryPointTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -46,11 +47,13 @@ class DefaultPushHistoryEntryPointTest { } val callback = object : PushHistoryEntryPoint.Callback { override fun onDone() = lambdaError() - override fun navigateTo(roomId: RoomId, eventId: EventId) = lambdaError() + override fun navigateToEvent(roomId: RoomId, eventId: EventId) = lambdaError() } - val result = entryPoint.nodeBuilder(parentNode, BuildContext.root(null)) - .callback(callback) - .build() + val result = entryPoint.createNode( + parentNode = parentNode, + buildContext = BuildContext.root(null), + callback = callback, + ) assertThat(result).isInstanceOf(PushHistoryNode::class.java) assertThat(result.plugins).contains(callback) } diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenterTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenterTest.kt index 9c1cdee25c..15fb6b5f25 100644 --- a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenterTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenterTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryViewTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryViewTest.kt index ada8b61f35..fa4e65ad9a 100644 --- a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryViewTest.kt +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryViewTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/troubleshoot/test/build.gradle.kts b/libraries/troubleshoot/test/build.gradle.kts index 830eb5d6b0..ff0743c72e 100644 --- a/libraries/troubleshoot/test/build.gradle.kts +++ b/libraries/troubleshoot/test/build.gradle.kts @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { @@ -13,19 +14,10 @@ android { } dependencies { + implementation(projects.libraries.architecture) implementation(projects.libraries.troubleshoot.api) implementation(projects.tests.testutils) implementation(libs.coroutines.test) implementation(libs.test.core) implementation(libs.test.turbine) } - -ktlint { - filter { - exclude { element -> - val path = element.file.path - // Exclude this file, that ktlint cannot parse. - path.contains("libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/Utils.kt") - } - } -} diff --git a/libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/FakeNotificationTroubleShootEntryPoint.kt b/libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/FakeNotificationTroubleShootEntryPoint.kt new file mode 100644 index 0000000000..85a30e4e8d --- /dev/null +++ b/libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/FakeNotificationTroubleShootEntryPoint.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.troubleshoot.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.libraries.troubleshoot.api.NotificationTroubleShootEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeNotificationTroubleShootEntryPoint : NotificationTroubleShootEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: NotificationTroubleShootEntryPoint.Callback, + ): Node = lambdaError() +} diff --git a/libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/FakeNotificationTroubleshootNavigator.kt b/libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/FakeNotificationTroubleshootNavigator.kt index 63445e5a3e..f983c8bcf1 100644 --- a/libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/FakeNotificationTroubleshootNavigator.kt +++ b/libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/FakeNotificationTroubleshootNavigator.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,5 +14,5 @@ import io.element.android.tests.testutils.lambda.lambdaError class FakeNotificationTroubleshootNavigator( private val openIgnoredUsersResult: () -> Unit = { lambdaError() }, ) : NotificationTroubleshootNavigator { - override fun openIgnoredUsers() = openIgnoredUsersResult() + override fun navigateToBlockedUsers() = openIgnoredUsersResult() } diff --git a/libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/FakePushHistoryEntryPoint.kt b/libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/FakePushHistoryEntryPoint.kt new file mode 100644 index 0000000000..28643f4551 --- /dev/null +++ b/libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/FakePushHistoryEntryPoint.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.troubleshoot.test + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import io.element.android.libraries.troubleshoot.api.PushHistoryEntryPoint +import io.element.android.tests.testutils.lambda.lambdaError + +class FakePushHistoryEntryPoint : PushHistoryEntryPoint { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: PushHistoryEntryPoint.Callback, + ): Node = lambdaError() +} diff --git a/libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/Utils.kt b/libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/Utils.kt index 77034da584..5636195713 100644 --- a/libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/Utils.kt +++ b/libraries/troubleshoot/test/src/main/kotlin/io/element/android/libraries/troubleshoot/test/Utils.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/ui-common/build.gradle.kts b/libraries/ui-common/build.gradle.kts index 55ef7eaf49..76c772f9fc 100644 --- a/libraries/ui-common/build.gradle.kts +++ b/libraries/ui-common/build.gradle.kts @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyNode.kt b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyNode.kt index ab622b4b9d..53cc8c8443 100644 --- a/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyNode.kt +++ b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyNode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/ui-strings/build.gradle.kts b/libraries/ui-strings/build.gradle.kts index 7e34891c66..06c23bd026 100644 --- a/libraries/ui-strings/build.gradle.kts +++ b/libraries/ui-strings/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/ui-strings/src/main/kotlin/io/element/android/libraries/ui/strings/CommonPlurals.kt b/libraries/ui-strings/src/main/kotlin/io/element/android/libraries/ui/strings/CommonPlurals.kt index e35c7d7c1f..36fb0d21dc 100644 --- a/libraries/ui-strings/src/main/kotlin/io/element/android/libraries/ui/strings/CommonPlurals.kt +++ b/libraries/ui-strings/src/main/kotlin/io/element/android/libraries/ui/strings/CommonPlurals.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/ui-strings/src/main/kotlin/io/element/android/libraries/ui/strings/CommonStrings.kt b/libraries/ui-strings/src/main/kotlin/io/element/android/libraries/ui/strings/CommonStrings.kt index 4f6b442e03..bf3c2c4930 100644 --- a/libraries/ui-strings/src/main/kotlin/io/element/android/libraries/ui/strings/CommonStrings.kt +++ b/libraries/ui-strings/src/main/kotlin/io/element/android/libraries/ui/strings/CommonStrings.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/ui-strings/src/main/res/values-be/translations.xml b/libraries/ui-strings/src/main/res/values-be/translations.xml index 247dc9f026..d2a1f4dea7 100644 --- a/libraries/ui-strings/src/main/res/values-be/translations.xml +++ b/libraries/ui-strings/src/main/res/values-be/translations.xml @@ -117,6 +117,7 @@ "Прапусціць" "Пачаць" "Пачаць чат" + "Пачаць спачатку" "Пачаць праверку" "Націсніце, каб загрузіць карту" "Зрабіць фота" @@ -132,6 +133,7 @@ "Палітыка дапушчальнага выкарыстання" "Пашыраныя налады" "Аналітыка" + "Вы выйшлі з пакоя" "Знешні выгляд" "Аўдыя" "Заблакіраваныя карыстальнікі" @@ -272,6 +274,7 @@ "Памылка" "Поспех" "Папярэджанне" + "У вас ёсць незахаваныя змены." "Вашы змены не былі захаваны. Вы ўпэўнены, што хочаце вярнуцца?" "Захаваць змены?" "Ваш хатні сервер неабходна абнавіць для падтрымкі Matrix Authentication Service і стварэння ўліковага запісу." diff --git a/libraries/ui-strings/src/main/res/values-bg/translations.xml b/libraries/ui-strings/src/main/res/values-bg/translations.xml index 57b3a43d1a..483c47fbea 100644 --- a/libraries/ui-strings/src/main/res/values-bg/translations.xml +++ b/libraries/ui-strings/src/main/res/values-bg/translations.xml @@ -84,6 +84,7 @@ "Управление на профила" "Управление на устройствата" "Съобщение" + "Напред" "Не" "Не сега" "Добре" @@ -129,9 +130,11 @@ "Относно" "Политика за приемлива употреба" "Добавяне на акаунт" + "Добавяне на друг акаунт" "Разширени настройки" + "изображение" "Статистика" - "Напуснахте стаята" + "Вие напуснахте стаята" "Облик" "Аудио" "Блокирани потребители" @@ -140,6 +143,8 @@ "Резервно копие на чатовете" "Авторски права" "Създаване на стая…" + "Стаята е напусната" + "Пространството е напуснато" "Тъмен" "Грешка при разшифроване" "Описание" @@ -170,6 +175,7 @@ "Инсталиране на APK" "Този Matrix ID не може да бъде намерен, така че поканата може да не бъде получена." "Стаята се напуска" + "Пространството се напуска" "Светъл" "Връзката е копирана в клипборда" "Зарежда се…" @@ -212,6 +218,7 @@ "Подготвя се…" "Политика за поверителност" "Частна стая" + "Частно пространство" "Общодостъпна стая" "Общодостъпно пространство" "Реакция" @@ -241,15 +248,19 @@ "Резултати от търсенето" "Защита" "Видяно от" + "Избиране на акаунт" "Изпраща се…" "Изпращането е неуспешно" "Изпратено" "Сървърът не се поддържа" "URL адрес на сървъра" "Настройки" + "Споделяне на пространството" "Споделено местоположение" "Излизате" + "Нещо се обърка" "Възникна проблем. Моля, опитайте отново." + "Пространство" "%1$d пространство" "%1$d пространства" @@ -266,6 +277,7 @@ "Тема" "За какво се отнася тази стая?" "Не може да се разшифрова" + "Нямате достъп до това съобщение" "Поканите не можаха да бъдат изпратени до един или повече потребители." "Не може да се изпрати покана(и)" "Отключване" @@ -280,6 +292,9 @@ "Потвърждаване на самоличността" "Потвърждаване на потребителя" "Видео" + "Високо качество" + "Ниско качество" + "Стандартно качество" "Гласово съобщение" "Изчаква се…" "В очакване на това съобщение" @@ -316,9 +331,13 @@ "Отваряне в Google Maps" "Отваряне в OpenStreetMap" "Споделяне на това местоположение" + "%1$s пространство" + "Пространства" + "Преглед на членовете" "Местоположение" "Версия: %1$s (%2$s)" "bg" "Трябва да потвърдите това устройство за да достъпите исторически съобщения" + "Нямате достъп до това съобщение" "Съобщението не може да се разшифрова" diff --git a/libraries/ui-strings/src/main/res/values-cs/translations.xml b/libraries/ui-strings/src/main/res/values-cs/translations.xml index f0e10894b4..196ec6f175 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -97,6 +97,8 @@ "Zapomněli jste heslo?" "Přeposlat" "Přejít zpět" + "Přejít na role a oprávnění" + "Přejít do nastavení" "Ignorovat" "Pozvat" "Pozvat přátele" @@ -113,6 +115,7 @@ "Spravovat účet" "Spravovat zařízení" "Zpráva" + "Minimalizovat" "Další" "Ne" "Teď ne" @@ -155,6 +158,7 @@ "Přeskočit" "Začít" "Zahájit chat" + "Začít znovu" "Zahájit ověření" "Klepnutím načtete mapu" "Vyfotit" @@ -180,6 +184,7 @@ "Byli jste odhlášeni z relace" "Vzhled" "Zvuk" + "Beta" "Blokovaní uživatelé" "Bubliny" "Hovor zahájen" @@ -228,6 +233,7 @@ Důvod: %1$s." "Nainstalovat APK" "Tento Matrix identifikátor nelze najít, takže pozvánka nemusí být přijata." "Opuštění místnosti" + "Opuštění prostoru" "Světlý" "Řádek zkopírován do schránky" "Odkaz zkopírován do schránky" @@ -319,6 +325,7 @@ Důvod: %1$s." "Nastavení" "Sdílet prostor" "Sdílená poloha" + "Sdílený prostor" "Odhlašování" "Něco se nepovedlo" "Narazili jsme na problém. Zkuste to prosím znovu." @@ -388,6 +395,7 @@ Opravdu chcete pokračovat?" "Chyba" "Úspěch" "Upozornění" + "Máte neuložené změny." "Vaše změny nebyly uloženy. Opravdu se chcete vrátit?" "Uložit změny?" "Maximální povolená velikost souboru je: %1$s" @@ -468,6 +476,7 @@ Opravdu chcete pokračovat?" "%1$s • %2$s" "%1$s prostor" "Prostory" + "Zobrazit členy" "Zpráva nebyla odeslána, protože ověřená identita uživatele %1$s se změnila." "Zpráva nebyla odeslána, protože%1$s neověřil(a) všechna zařízení." "Zpráva nebyla odeslána, protože jste neověřili jedno nebo více zařízení." diff --git a/libraries/ui-strings/src/main/res/values-cy/translations.xml b/libraries/ui-strings/src/main/res/values-cy/translations.xml index 11b46cb85c..1aa932f8ed 100644 --- a/libraries/ui-strings/src/main/res/values-cy/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cy/translations.xml @@ -161,6 +161,7 @@ "Hepgor" "Cychwyn" "Dechrau sgwrs" + "Cychwyn eto" "Dechrau dilysu" "Tapio i lwytho map" "Cymryd llun" @@ -414,6 +415,7 @@ Ydych chi\'n siŵr eich bod am barhau?" "Gwall" "Llwyddiant" "Rhybudd" + "Mae gennych newidiadau heb eu cadw." "Dyw eich newidiadau heb gael eu cadw. Ydych chi\'n siŵr eich bod am fynd nôl?" "Cadw\'r newidiadau?" "Y maint ffeil mwyaf sy\'n cael ei ganiatáu yw: %1$s" diff --git a/libraries/ui-strings/src/main/res/values-da/translations.xml b/libraries/ui-strings/src/main/res/values-da/translations.xml index e133ec28ac..73b70cf4f1 100644 --- a/libraries/ui-strings/src/main/res/values-da/translations.xml +++ b/libraries/ui-strings/src/main/res/values-da/translations.xml @@ -95,6 +95,7 @@ "Har du glemt din adgangskode?" "Videresend" "Gå tilbage" + "Gå til roller og rettigheder" "Gå til indstillinger" "Ignorér" "Invitér" @@ -107,7 +108,7 @@ "Forlad" "Forlad samtalen" "Forlad rum" - "Forlad klynge" + "Forlad gruppe" "Indlæs mere" "Administrer konto" "Administrer enheder" @@ -155,6 +156,7 @@ "Spring over" "Start" "Start samtale" + "Begynd forfra" "Begynd verifikation" "Tryk for at indlæse kort" "Tag billede" @@ -190,7 +192,7 @@ "Opretter rum…" "Anmodning annulleret" "Forlod rummet" - "Forlod klynge" + "Forlod gruppe" "Invitationen blev afvist" "Mørkt tema" "Fejl under dekryptering" @@ -229,7 +231,7 @@ "Installer APK" "Dette Matrix-ID kan ikke findes, så invitationen modtages muligvis ikke." "Forlader rummet" - "Forlader klynge" + "Forlader gruppe" "Lyst tema" "Linje kopieret til udklipsholder" "Linket er kopieret til udklipsholderen" @@ -252,7 +254,7 @@ "%1$s (%2$s)" "Ingen resultater" "Intet rumnavn" - "Intet klyngenavn" + "Intet gruppenavn" "Ikke krypteret" "Offline" "Open Source-licenser" @@ -275,9 +277,9 @@ "Forbereder…" "Privatlivspolitik" "Privat rum" - "Privat klynge" + "Privat gruppe" "Offentligt rum" - "Offentlig klynge" + "Offentlig gruppe" "Reaktion" "Reaktioner" "Årsag" @@ -315,16 +317,16 @@ "Serveren er ikke tilgængelig" "Server URL" "Indstillinger" - "Del klynge" + "Del gruppe" "Delt placering" - "Delt klynge" + "Delt gruppe" "Logger ud" "Noget gik galt" "Vi stødte på et problem. Prøv venligst igen." - "Klynge" + "Gruppe" - "%1$d Klynge" - "%1$d Klynger" + "%1$d Gruppe" + "%1$d Grupper" "Starter samtale…" "Klistermærke" @@ -386,6 +388,7 @@ Er du sikker på, at du vil fortsætte?"
"Fejl" "Succes" "Advarsel" + "Du har ændringer, der ikke er gemt." "Dine ændringer er ikke blevet gemt. Er du sikker på, at du vil gå tilbage?" "Gem ændringer?" "Den maksimalt tilladte filstørrelse er: %1$s" @@ -461,10 +464,11 @@ Er du sikker på, at du vil fortsætte?"
"Åbn i Google Maps" "Åbn i OpenStreetMap" "Del denne lokation" - "Klynger, du har oprettet eller deltager i" + "Grupper, du har oprettet eller deltager i" "%1$s•%2$s" - "%1$s klynge" - "Klynger" + "%1$s gruppe" + "Grupper" + "Vis medlemmer" "Beskeden blev ikke sendt fordi %1$s s bekræftede identitet blev nulstillet." "Meddelelsen er ikke sendt, fordi %1$s ikke har bekræftet alle enheder." "Beskeden er ikke sendt, fordi du ikke har verificeret en eller flere af dine enheder." diff --git a/libraries/ui-strings/src/main/res/values-de/translations.xml b/libraries/ui-strings/src/main/res/values-de/translations.xml index d1c8b7a6f4..65803db3aa 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -155,6 +155,7 @@ "Überspringen" "Start" "Chat starten" + "Neu beginnen" "Verifizierung starten" "Tippe, um die Karte zu laden" "Foto aufnehmen" @@ -366,7 +367,7 @@ Grund: %1$s."
"Sprachnachricht" "Warten…" "Warte auf diese Nachricht" - "Sie" + "Du" "%1$s\'s Identität has sich geändert. %2$s" "%1$s\'s %2$s Identität hat sich geändert. %3$s" "(%1$s)" @@ -387,6 +388,7 @@ Möchtest du wirklich fortfahren?"
"Fehler" "Erfolg" "Warnung" + "Du hast nicht gespeicherte Änderungen." "Deine Änderungen wurden nicht gespeichert. Bist du sicher, dass du zurückgehen willst?" "Änderungen speichern?" "Die maximal erlaubte Dateigröße ist: %1$s" @@ -466,6 +468,7 @@ Möchtest du wirklich fortfahren?"
"%1$s • %2$s" "%1$s Space" "Spaces" + "Mitglieder anzeigen" "Nachricht nicht gesendet, weil sich die verifizierte Identität von %1$s geändert hat." "Die Nachricht wurde nicht gesendet, weil %1$s nicht alle Geräte verifiziert hat." "Die Nachricht wurde nicht gesendet, weil du eines oder mehrere deiner Geräte nicht verifiziert hast." diff --git a/libraries/ui-strings/src/main/res/values-el/translations.xml b/libraries/ui-strings/src/main/res/values-el/translations.xml index 5085f404a1..98d20a1599 100644 --- a/libraries/ui-strings/src/main/res/values-el/translations.xml +++ b/libraries/ui-strings/src/main/res/values-el/translations.xml @@ -130,6 +130,7 @@ "Παράλειψη" "Εκκίνηση" "Έναρξη συνομιλίας" + "Ξανά από την αρχή" "Έναρξη επαλήθευσης" "Πάτα για φόρτωση χάρτη" "Τράβηξε φωτογραφία" @@ -148,6 +149,7 @@ "Ρυθμίσεις για προχωρημένους" "μια εικόνα" "Στατιστικά στοιχεία" + "Αποχωρήσατε από την αίθουσα" "Εμφάνιση" "Ήχος" "Αποκλεισμένοι χρήστες" @@ -326,6 +328,7 @@ "Σφάλμα" "Επιτυχία" "Προειδοποίηση" + "Έχεις μη αποθηκευμένες αλλαγές." "Οι αλλαγές σου δεν έχουν αποθηκευτεί. Σίγουρα θες να πας πίσω;" "Αποθήκευση αλλαγών;" "Ο οικιακός διακομιστής σου πρέπει να αναβαθμιστεί για να υποστηρίζει το Matrix Authentication Service και τη δημιουργία λογαριασμού." diff --git a/libraries/ui-strings/src/main/res/values-en-rUS/translations.xml b/libraries/ui-strings/src/main/res/values-en-rUS/translations.xml index 00d2674ed7..2fc2fb0889 100644 --- a/libraries/ui-strings/src/main/res/values-en-rUS/translations.xml +++ b/libraries/ui-strings/src/main/res/values-en-rUS/translations.xml @@ -1,6 +1,8 @@ + "Minimize message text field" "Minimize" "Favorite" "Favorited" + "This room has been configured so that new members can read history. %1$s" diff --git a/libraries/ui-strings/src/main/res/values-eo/translations.xml b/libraries/ui-strings/src/main/res/values-eo/translations.xml deleted file mode 100644 index 6f796aef09..0000000000 --- a/libraries/ui-strings/src/main/res/values-eo/translations.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - "Manage linked devices" - "Start fresh" - "Backup password" - "Sent from an unconfirmed device" - "Sender\'s security verification has changed" - "Confirm device" - "Verify user" - "%1$s\'s security verification has changed. %2$s" - "%1$s\'s %2$s security verification has changed. %3$s" - "%1$s\'s security verification has changed." - "%1$s\'s %2$s security verification has changed. %3$s" - "You\'re about to go to your %1$s account to start fresh. Afterwards you\'ll be taken back to the app." - "Can\'t confirm? Go to your account to start fresh." - "Your message was not sent because %1$s\'s security verification has changed" - "%1$s is using one or more unconfirmed devices. You can send the message anyway, or you can cancel for now and try again later after %2$s has confirmed all their devices." - "Your message was not sent because %1$s has not confirmed all devices" - "One or more of your linked devices are unconfirmed. You can send the message anyway, or you can cancel for now and try again later after you have confirmed all of your linked devices." - "Your message was not sent because you have not confirmed one or more of your linked devices" - "Message not sent because %1$s\'s security verification has changed." - "Message not sent because %1$s has not confirmed all their devices." - "Message not sent because you have not confirmed one or more of your linked devices." - "You need to confirm this device for access to historical messages" - "This message was blocked either because you did not confirm your device or because the sender needs to verify you." - diff --git a/libraries/ui-strings/src/main/res/values-es/translations.xml b/libraries/ui-strings/src/main/res/values-es/translations.xml index 4428e0b864..740f96edd4 100644 --- a/libraries/ui-strings/src/main/res/values-es/translations.xml +++ b/libraries/ui-strings/src/main/res/values-es/translations.xml @@ -128,6 +128,7 @@ "Saltar" "Comenzar" "Iniciar chat" + "Empezar de nuevo" "Iniciar la verificación" "Pulsa para cargar el mapa" "Hacer foto" @@ -145,6 +146,7 @@ "Añadiendo leyenda" "Ajustes avanzados" "Estadísticas" + "Saliste de la sala" "Apariencia" "Sonido" "Usuarios bloqueados" @@ -318,6 +320,7 @@ Motivo: %1$s."
"Error" "Terminado" "Atención" + "Tienes cambios sin guardar." "Tus cambios no se han guardado. ¿Estás seguro de que quieres volver atrás?" "¿Guardar cambios?" "Tu servidor base debe actualizarse para admitir Matrix Authentication Service y la creación de cuentas." diff --git a/libraries/ui-strings/src/main/res/values-et/translations.xml b/libraries/ui-strings/src/main/res/values-et/translations.xml index dd6bae100e..67d323e77b 100644 --- a/libraries/ui-strings/src/main/res/values-et/translations.xml +++ b/libraries/ui-strings/src/main/res/values-et/translations.xml @@ -95,6 +95,8 @@ "Kas unustasid salasõna?" "Edasta" "Tagasi eelmisesse vaatesse" + "Ava „Rollid ja õigused“" + "Ava seadistused" "Eira" "Kutsu" "Kutsu osalejaid" @@ -154,10 +156,12 @@ "Jäta vahele" "Alusta" "Alusta vestlust" + "Alusta uuesti" "Alusta verifitseerimist" "Kaardi laadimiseks klõpsa" "Tee pilt" "Valikuteks klõpsa" + "Tõlgi" "Proovi uuesti" "Eemalda esiletõstmine" "Vaata" @@ -175,10 +179,11 @@ "Täiendavad seadistused" "pilt" "Analüütika" - "Sa oled jututoast lahkunud" + "Sina lahkusid jututoast" "Sa olid sessioonist väljaloginud" "Välimus" "Heli" + "Beetaversioon" "Blokeeritud kasutajad" "Mullid" "Kõne algas" @@ -227,9 +232,11 @@ Põhjus: %1$s."
"Paigalda APK-failist" "Sellist Matrix\'i kasutajatunnust ei õnnestu leida, seega sõnumit ilmselt keegi kätte ei saa." "Oled lahkumas jututoast" + "Oled lahkumas kogukonnast" "Hele" "Rida on kopeeritud lõikelauale" "Link on kopeeritud lõikelauale" + "Seo uus seade" "Laadime…" "Laadime veel…" @@ -242,10 +249,12 @@ Põhjus: %1$s."
"Sõnum" "Tegevused sõnumiga" + "Sõnumi saatmine ei õnnestunud" "Sõnumi paigutus" "Sõnum on eemaldatud" "Kaasaegne" "Summutatud" + "Nimi" "%1$s (%2$s)" "Otsingul pole tulemusi" "Jututoal puudub nimi" @@ -315,10 +324,12 @@ Põhjus: %1$s."
"Seadistused" "Jaga kogukonda" "Jagatud asukoht" + "Jagatud kogukond" "Logime välja" "Midagi läks valesti" "Tekkis viga. Palun proovi uuesti." "Kogukond" + "Mida selles kogukonnas tehakse?" "%1$d kogukond" "%1$d kogukonda" @@ -363,6 +374,7 @@ Põhjus: %1$s."
"Ootame…" "Ootame selle sõnumi dekrüptimisvõtit" "Sina" + "See jututuba on seadistatud sedaviisi, et ka uued liikmed saavad lugeda varasemat ajalugu. %1$s" "Kasutaja %1$s võrguidentiteet on lähtestatud. %2$s" "Kasutaja %1$s %2$s võrguidentiteet on lähtestatud. %3$s" "(%1$s)" @@ -383,6 +395,7 @@ Kas sa oled kindel, et soovid jätkata?"
"Viga" "Õnnestus" "Hoiatus" + "Sul on salvestamata muudatusi" "Sinu tehtud muudatused pole veel salvestatud. Kas sa oled kindel, et soovid minna tagasi?" "Kas salvestame muudatused?" "Suurim lubatud failisuurus on: %1$s" @@ -422,6 +435,11 @@ Kas sa oled kindel, et soovid jätkata?"
"Valikud" "Kustuta: %1$s" "Seadistused" + "Kogukonnad, milles on võimalik jututoaga liituda ilma kutseta." + "Halda kogukondi" + "(Tundmatu kogukond)" + "Muud kogukonnad, mille liige sa ei ole" + "Sinu kogukonnad" "Meediafaili valimine ei õnnestunud. Palun proovi uuesti." "Siia lisamiseks vajuta sõnumil ja vali „%1$s“." "Et olulisi sõnumeid oleks lihtsam leida, tõsta nad esile" @@ -462,6 +480,7 @@ Kas sa oled kindel, et soovid jätkata?"
"%1$s • %2$s" "Kogukond: %1$s" "Kogukonnad" + "Vaata liikmeid" "Sõnum on saatmata, kuna kasutaja %1$s verifitseeritud identiteet on lähtestatud." "Sõnum on saatmata, kuna %1$s pole verifitseerinud kõiki oma seadmeid." "Kuna sa pole üks või enamgi oma seadet verifitseerinud, siis sinu sõnum on saatmata." diff --git a/libraries/ui-strings/src/main/res/values-eu/translations.xml b/libraries/ui-strings/src/main/res/values-eu/translations.xml index 15bfa21fb5..9a92bf6289 100644 --- a/libraries/ui-strings/src/main/res/values-eu/translations.xml +++ b/libraries/ui-strings/src/main/res/values-eu/translations.xml @@ -137,6 +137,7 @@ "Saltatu" "Hasi" "Hasi txata" + "Hasi berriro" "Hasi egiaztapena" "Sakatu mapa kargatzeko" "Egin argazkia" @@ -155,6 +156,7 @@ "Ezarpen aurreratuak" "irudia" "Estatistikak" + "Gelatik atera zara" "Itxura" "Audioa" "Blokeatutako erabiltzaileak" @@ -336,6 +338,7 @@ Ziur jarraitu nahi duzula?"
"Errorea" "Arrakasta" "Abisua" + "Gorde gabeko aldaketak dituzu." "Zure aldaketak ez dira gorde. Ziur itzuli nahi duzula?" "Aldaketak gorde?" "Hautatu bideoaren igoera-kalitatea" diff --git a/libraries/ui-strings/src/main/res/values-fa/translations.xml b/libraries/ui-strings/src/main/res/values-fa/translations.xml index c599150e83..16872a2e91 100644 --- a/libraries/ui-strings/src/main/res/values-fa/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fa/translations.xml @@ -30,6 +30,10 @@ "واکنش با %1$s" "واکنش با شکلک‌های دیگر" "خوانده به دست %1$s و %2$s" + + "خوانده شده توسط%1$s و%2$d نفر دیگر" + "خوانده شده توسط%1$s و%2$d نفر دیگر" + "خوانده به دست %1$s" "زدن برای نمایش همه" "برداشتن واکنش با %1$s" @@ -73,6 +77,7 @@ "رد" "رد و انسداد" "حذف نظرسنجی" + "ناگزینش همه" "از کار انداختن" "دور ریختن" "دورانداختن" @@ -83,6 +88,7 @@ "به کار انداختن" "پایان نظرسنجی" "ورود پین" + "پایان" "گذرواژه را فراموش کردید؟" "پیشروی" "پس‌روی" @@ -97,6 +103,7 @@ "ترک" "ترک گفت‌وگو" "ترک اتاق" + "ترک فضا" "بار کردن بیش‌تر" "مدیریت حساب" "مدیریت افزاره‌ها" @@ -113,7 +120,7 @@ "نقل قول" "واکنش" "رد کردن" - "برداشتن" + "حذف" "برداشتن عنوان" "برداشتن پیام‌ها" "پاسخ" @@ -129,6 +136,7 @@ "تلاش دوباره برای رمزگشایی" "ذخیره" "جست‌وجو" + "گزینش همه" "فرستادن" "فرستادن پیام ویراسته" "فرستادن پیام" @@ -142,6 +150,7 @@ "پرش" "آغاز" "آغاز گپ" + "آغاز از نو" "آغاز تأیید" "زدن برای بار کردن نقشه" "عکس گرفتن" @@ -156,11 +165,15 @@ "ارتقا موجود است" "درباره" "سیاست استفادهٔ پذیرفتنی" + "افزودن یک حساب" + "افزودن حسابی دیگر" "افزودن عنوان" "تنظیمات پیش‌رفته" "تجزیه و تحلیل" + "اتاق را ترک کردید" "ظاهر" "صدا" + "آزمایشی" "کاربران مسدود" "حباب‌ها" "تماس آغاز شد" @@ -170,9 +183,11 @@ "ایجاد کردن اتاق…" "درخواست لغو شد" "اتاق را ترک کرد" + "فضا را ترک کرد" "دعوت لغو شد" "تیره" "خطای رمزگشایی" + "توضیح" "گزینه‌های توسعه دهنده" "شناسه دستگاه" "گپ مستقیم" @@ -204,6 +219,7 @@ "نصب APK" "شناسهٔ ماتریکس نتوانست پیدا شود. ممکن است دعوت نرسیده باشد." "ترک کردن اتاق" + "ترک کردن فضا" "روشن" "خط در تخته‌گیره رونوشت شد" "پیوند در تخته‌گیره رونوشت شد" @@ -222,6 +238,7 @@ "%1$s ‏(%2$s)" "بدون نتیجه" "بدون نام اتاق" + "بدون نام فضا" "رمزنگاری نشده" "برون‌خط" "پروانه‌های نرم‌افزاری آزاد" @@ -244,7 +261,9 @@ "آماده سازی…" "سیاست محرمانگی" "اتاق خصوصی" + "فضای خصوصی" "اتاق عمومی" + "فضای عمومی" "واکنش" "واکنش‌ها" "دلیل" @@ -265,17 +284,22 @@ "نتایج جست‌وجو" "امنیت" "دیده شده به دست" + "گزینش حساب" "فرستادن به" "فرستادن…" "فرستادن شکست خورد" "فرستاده" ". " "کارساز پشتیبانی نمی‌شود" + "کارساز ناپاسخگو" "نشانی کارساز" "تنظیمات" + "هم‌رسانی فضا" "مکان هم‌رسانده" + "فضای اشتراکی" "خارج شدن" "چیزی اشتباه پیش رفت" + "فاصله" "آغازیدن گپ…" "عکس برگردان" "موفّقیت" @@ -304,6 +328,9 @@ "تأیید هویت" "تأیید کاربر" "ویدیو" + "کیفیت بالا" + "کیفیت پایین" + "کیفیت استاندارد" "پیام صوتی" "در انتظار…" "در انتظار این پیام" @@ -312,14 +339,17 @@ "(%1$s)" "انصراف از تأیید" "این پیوند را دوباره بررسی کنید" + "کیفیت بارگذاری ویدیو" "اتاق گزارش شد" "گزارش و از اتاق خارج شد" "تایید" "خطا" "موفّقیت" "هشدار" + "تغییراتی ذخیره نشده دارید." "تغییراتتان ذخیره نشده‌اند. مطمئنید که می‌خواهید برگردید؟" "ذخیرهٔ تغییرات؟" + "حست‌وجوی شکلک‌ها" "شکست در ایجاد پایاپیوند" "%1$s نتوانست نقشه را بارگیری کند. لطفا بعدا دوباره امتحان کنید." "شکست در بار کردن پیام‌ها" @@ -349,10 +379,12 @@ "پیام‌های سنجاق شده" "داردید برای بازنشانی هویتتان به حساب %1$s می‌روید. پس از آن به کاره برگردانده خواهید شد." "فرستادن پیام به هر روی" + "ویرایش مدیران یا مالکان" "پردازش رسانه برای بارگذاری شکست خورد. لطفاً دوباره تلاش کنید." "نمی توان جزئیات کاربر را بازیابی کرد" "پیام در %1$s" "گسترش" + "کاهش" "%1$s از %2$s" "%1$s پیام‌های سنجاق شده" "بار کردن پیام‌ها…" @@ -364,6 +396,11 @@ "گشودن در نقشه‌های گوگل" "گشودن در اوپن‌استریت‌مپ" "هم‌رسانی این مکان" + "فضاهایی که ساخته یا پیوسته‌اید." + "%1$s • %2$s" + "‏%1$s فضا" + "فضاها" + "دیدن اعضا" "مکان" "نگارش : %1$s (%2$s)" "fa" diff --git a/libraries/ui-strings/src/main/res/values-fi/translations.xml b/libraries/ui-strings/src/main/res/values-fi/translations.xml index 5959066e9b..f6a7678358 100644 --- a/libraries/ui-strings/src/main/res/values-fi/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fi/translations.xml @@ -95,10 +95,11 @@ "Unohditko salasanan?" "Välitä" "Takaisin" + "Siirry rooleihin ja oikeuksiin" "Siirry asetuksiin" "Ohita" "Kutsu" - "Kutsu ihmisiä" + "Kutsu henkilöitä" "Kutsu ihmisiä %1$s -sovellukseen" "Kutsu ihmisiä %1$s -sovellukseen" "Kutsut" @@ -155,6 +156,7 @@ "Ohita" "Aloita" "Aloita keskustelu" + "Aloita alusta" "Aloita vahvistus" "Lataa kartta napauttamalla" "Ota kuva" @@ -387,6 +389,7 @@ Haluatko varmasti jatkaa?"
"Virhe" "Onnistui" "Varoitus" + "Sinulla on tallentamattomia muutoksia" "Muutoksiasi ei ole tallennettu. Haluatko varmasti palata takaisin?" "Tallennetaanko muutokset?" "Suurin sallittu tiedostokoko on: %1$s" @@ -466,6 +469,7 @@ Haluatko varmasti jatkaa?"
"%1$s • %2$s" "%1$s tila" "Tilat" + "Näytä jäsenet" "Viestiä ei lähetetty, koska käyttäjän %1$s vahvistettu identiteetti nollattiin." "Viestiä ei lähetetty, koska %1$s ei ole vahvistanut kaikkia laitteitaan." "Viestiä ei lähetetty, koska et ole vahvistanut yhtä tai useampaa laitettasi." diff --git a/libraries/ui-strings/src/main/res/values-fr/translations.xml b/libraries/ui-strings/src/main/res/values-fr/translations.xml index 591795aaee..9392933cf8 100644 --- a/libraries/ui-strings/src/main/res/values-fr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml @@ -95,6 +95,7 @@ "Mot de passe oublié ?" "Transférer" "Retour" + "Accédez à l’écran « Rôles et autorisations »." "Ouvrir les paramètres" "Ignorer" "Inviter" @@ -155,6 +156,7 @@ "Passer" "Démarrer" "Démarrer une discussion" + "Recommencer" "Commencer la vérification" "Cliquez pour charger la carte" "Prendre une photo" @@ -176,7 +178,7 @@ "Paramètres avancés" "une image" "Statistiques d’utilisation" - "Vous avez quitter le salon" + "Vous avez quitté le salon" "Vous avez été déconnecté de la session" "Apparence" "Audio" @@ -245,6 +247,7 @@ Raison : %1$s."
"Message" "Actions sur le message" + "Échec de l’envoi du message" "Mode d’affichage des messages" "Message supprimé" "Moderne" @@ -367,6 +370,7 @@ Raison : %1$s."
"En attente…" "En attente de la clé de déchiffrement" "Vous" + "Les messages que vous enverrez seront partagés avec les nouveaux membres invités dans ce salon. %1$s" "L’identité de %1$s a été réinitialisée. %2$s" "L’identité de %1$s %2$s a été réinitialisée. %3$s" "(%1$s)" @@ -387,6 +391,7 @@ Raison : %1$s."
"Erreur" "Succès" "Attention" + "Vous avez des modifications non-enregistrées." "Vos modifications n’ont pas été enregistrées. Êtes-vous certain de vouloir quitter ?" "Enregistrer les changements ?" "La taille maximale de fichier autorisée est: %1$s" @@ -426,6 +431,11 @@ Raison : %1$s."
"Options" "Supprimer %1$s" "Paramètres" + "Espaces où les membres peuvent rejoindre le salon sans invitation." + "Gérer les espaces" + "(Espace inconnu)" + "Autres espaces dont vous n’êtes pas membre" + "Vos espaces" "Échec de la sélection du média, veuillez réessayer." "Cliquez (clic long) sur un message et choisissez « %1$s » pour qu‘il apparaisse ici." "Épinglez les messages importants pour leur donner plus de visibilité" @@ -466,6 +476,7 @@ Raison : %1$s."
"%1$s • %2$s" "Espace %1$s" "Espaces" + "Voir les membres" "Le message n’a pas été envoyé car l’identité vérifiée de %1$s a été réinitialisée." "Le message n’a pas été envoyé car %1$s n’a pas vérifié tous ses appareils." "Message non envoyé car vous n’avez pas vérifié tous vos appareils." diff --git a/libraries/ui-strings/src/main/res/values-hr/translations.xml b/libraries/ui-strings/src/main/res/values-hr/translations.xml new file mode 100644 index 0000000000..91caf1b6f9 --- /dev/null +++ b/libraries/ui-strings/src/main/res/values-hr/translations.xml @@ -0,0 +1,504 @@ + + + "Dodaj reakciju: %1$s" + "Avatar" + "Minimiziraj tekstno polje poruke" + "Izbriši" + + "%1$d unesena znamenka" + "%1$d unesene znamenke" + "%1$d unesenih znamenaka" + + "Uredi avatar" + "Potpuna adresa bit će %1$s" + "Pojedinosti šifriranja" + "Proširi tekstno polje poruke" + "Sakrij zaporku" + "Pridruži se pozivu" + "Idi na dno" + "Pomakni kartu na moju lokaciju" + "Samo spominjanja" + "Bez zvuka" + "Nova spominjanja" + "Nove poruke" + "Poziv u tijeku" + "Avatar drugog korisnika" + "Stranica %1$d" + "Pauziraj" + "Glasovna poruka, trajanje: %1$s, trenutačno zaustavljeno na: %2$s" + "Polje za PIN" + "Reproduciraj" + "Anketa" + "Završena anketa" + "Reagiraj s %1$s" + "Reagiraj s drugim emotikonima" + "Pročitali %1$s i %2$s" + + "Pročitao %1$s i ostalih %2$d" + "Pročitala %1$s i ostala %2$d" + "Pročitalo %1$s i ostalih %2$d" + + "Pročitao korisnik %1$s" + "Dodirnite za prikaz svih" + "Ukloni reakciju: %1$s" + "Ukloni reakciju s %1$s" + "Avatar sobe" + "Pošalji datoteke" + "Potrebna je vremenski ograničena radnja, imate jednu minutu za potvrdu" + "Prikaži zaporku" + "Započni poziv" + "Soba označena za uklanjanje" + "Korisnički avatar" + "Korisnički izbornik" + "Prikaži avatar" + "Prikaži pojedinosti" + "Glasovna poruka, trajanje: %1$s" + "Snimite glasovnu poruku." + "Zaustavi snimanje" + "Vaš avatar" + "Prihvati" + "Dodaj opis" + "Dodaj na vremensku crtu" + "Natrag" + "Poziv" + "Odustani" + "Otkaži za sada" + "Odaberi fotografiju" + "Očisti" + "Zatvori" + "Dovrši provjeru" + "Potvrdi" + "Potvrdi zaporku" + "Nastavi" + "Kopiraj" + "Kopiraj opis" + "Kopiraj poveznicu" + "Kopiraj poveznicu u poruku" + "Kopiraj tekst" + "Stvori" + "Stvori sobu" + "Deaktiviraj" + "Deaktiviraj račun" + "Odbij" + "Odbij i blokiraj" + "Izbriši anketu" + "Poništi sve odabire" + "Onemogući" + "Odbaci" + "Odbaci" + "Gotovo" + "Uredi" + "Uredi opis" + "Uredi anketu" + "Omogući" + "Završi anketu" + "Unesite PIN" + "Završi" + "Zaboravili ste zaporku?" + "Proslijedi" + "Idi natrag" + "Idi na uloge i dopuštenja" + "Idi na postavke" + "Zanemari" + "Pozovi" + "Pozovi osobe" + "Pozovi osobe u %1$s" + "Pozovi osobe u %1$s" + "Pozivnice" + "Pridruži se" + "Saznajte više" + "Napusti" + "Napusti razgovor" + "Napusti sobu" + "Napusti prostor" + "Učitaj više" + "Upravljanje računom" + "Upravljanje uređajima" + "Poruka" + "Minimiziraj" + "Dalje" + "Ne" + "Ne sada" + "U redu" + "Otvori kontekstni izbornik" + "Postavke" + "Otvori s" + "Prikvači" + "Brzi odgovor" + "Citiraj" + "Reagiraj" + "Odbij" + "Ukloni" + "Ukloni opis" + "Ukloni poruku" + "Odgovori" + "Odgovori u niti" + "Prijavi" + "Prijavi grešku" + "Prijavi sadržaj" + "Prijavi razgovor" + "Prijavi sobu" + "Poništi" + "Poništi identitet" + "Pokušaj ponovno" + "Ponovno pokušaj dešifrirati" + "Spremi" + "Pretraži" + "Odaberi sve" + "Pošalji" + "Pošalji uređenu poruku" + "Pošalji poruku" + "Pošalji glasovnu poruku" + "Podijeli" + "Podijeli poveznicu" + "Prikaži" + "Ponovno se prijavite" + "Odjava" + "Svejedno se odjavi" + "Preskoči" + "Započni" + "Započni razgovor" + "Kreni ispočetka" + "Započni provjeru" + "Dodirnite za učitavanje karte" + "Uslikaj" + "Dodirnite za mogućnosti" + "Prevedi" + "Pokušajte ponovno" + "Otkvači" + "Prikaz" + "Prikaži na vremenskoj traci" + "Prikaži izvor" + "Da" + "Da, pokušaj ponovno" + "Vaš poslužitelj sada podržava novi, brži protokol. Odjavite se i ponovno prijavite da biste odmah izvršili nadogradnju. Ako to sada napravite, izbjeći ćete prisilnu odjavu kada se poslije ukloni stari protokol." + "Dostupna je nadogradnja" + "O aplikaciji" + "Pravilnik o prihvatljivoj upotrebi" + "Dodaj račun" + "Dodaj još jedan račun" + "Dodavanje opisa" + "Napredne postavke" + "slika" + "Analitika" + "Napustili ste sobu" + "Odjavljeni ste iz sesije" + "Izgled" + "Audiozapis" + "Beta" + "Blokirani korisnici" + "Mjehurići" + "Poziv je započeo" + "Sigurnosna kopija razgovora" + "Kopirano u međuspremnik" + "Autorsko pravo" + "Stvaranje sobe…" + "Zahtjev je otkazan" + "Napustio/la je sobu" + "Napušteni prostor" + "Poziv je odbijen" + "Tamno" + "Pogreška kod dešifriranja" + "Opis" + "Mogućnosti za razvojne inženjere" + "ID uređaja" + "Izravni razgovor" + "Ne prikazuj ovo ponovno" + "Preuzimanje nije uspjelo" + "Preuzimanje" + "(uređeno)" + "Uređivanje" + "Uređivanje opisa" + "* %1$s %2$s" + "Prazna datoteka" + "Šifriranje" + "Šifriranje je omogućeno" + "Unesite svoj PIN" + "Pogreška" + "Došlo je do pogreške; možda nećete primati obavijesti za nove poruke. Riješite problem s obavijestima u postavkama. + +Razlog: %1$s ." + "Svi" + "Nije uspjelo" + "Favorit" + "Označeno kao favorit" + "Datoteka" + "Datoteka je izbrisana" + "Datoteka je spremljena" + "Datoteka je spremljena u preuzimanja" + "Proslijedi poruku" + "Često korišteno" + "GIF" + "Slika" + "Kao odgovor osobi %1$s" + "Instaliraj APK" + "Ovaj Matrix ID nije moguće pronaći, pa se pozivnica možda neće primiti." + "Izlazak iz sobe" + "Napuštanje prostora" + "Svijetlo" + "Redak je kopiran u međuspremnik" + "Poveznica je kopirana u međuspremnik." + "Poveži novi uređaj" + "Učitavanje…" + "Učitava se još…" + + "još %d" + "još %d" + "još %d" + + + "%1$d član" + "%1$d člana" + "%1$d članova" + + "Poruka" + "Radnje s porukama" + "Slanje poruke nije uspjelo" + "Izgled poruke" + "Poruka je uklonjena" + "Suvremeni" + "Isključi zvuk" + "Naziv" + "%1$s (%2$s)" + "Nema rezultata" + "Nema naziva sobe" + "Nema naziva prostora" + "Nije šifrirano" + "Izvan mreže" + "Licencije otvorenog koda" + "ili" + "Zaporka" + "Osobe" + "Stalna poveznica" + "Dopuštenje" + "Prikvačeno" + "Provjerite svoju internetsku vezu" + "Pričekajte…" + "Jeste li sigurni da želite završiti ovu anketu?" + "Anketa: %1$s" + "Ukupno glasova: %1$s" + "Rezultati će se objaviti nakon što završi anketa" + + "%d glas" + "%d glasa" + "%d glasova" + + "Priprema…" + "Pravilnik o zaštiti privatnosti" + "Privatna soba" + "Privatni prostor" + "Javna soba" + "Javni prostor" + "Reakcija" + "Reakcije" + "Razlog" + "Ključ za oporavak" + "Osvježavanje…" + + "%1$d odgovor" + "%1$d odgovora" + "%1$d odgovora" + + "Odgovara korisniku %1$s" + "Prijavi pogrešku" + "Prijavi problem" + "Prijava je podnesena" + "Uređivač obogaćenog teksta" + "Soba" + "Naziv sobe" + "npr. naziv vašeg projekta" + + "%1$d soba" + "%1$d sobe" + "%1$d soba" + + "Spremljene promjene" + "Spremanje" + "Zaključavanje zaslona" + "Potraži nekoga" + "Rezultati pretraživanja" + "Sigurnost" + "Vidio/la" + "Odaberi račun" + "Pošalji" + "Slanje…" + "Slanje nije uspjelo" + "Poslano" + ". " + "Poslužitelj nije podržan" + "Poslužitelj nije dostupan" + "URL poslužitelja" + "Postavke" + "Podijeli prostor" + "Podijeljena lokacija" + "Zajednički prostor" + "Odjava je u tijeku" + "Nešto je pošlo po zlu" + "Naišli smo na problem. Pokušajte ponovno." + "Prostor" + "O čemu se radi u ovom prostoru?" + + "%1$d prostor" + "%1$d prostora" + "%1$d prostora" + + "Započinjanje razgovora…" + "Naljepnica" + "Uspjeh" + "Prijedlozi" + "Sinkronizacija" + "Sustav" + "Tekst" + "Obavijesti trećih strana" + "Nit" + "Tema" + "O čemu je ova soba?" + "Nije moguće dešifrirati" + "Poslano s nesigurnog uređaja" + "Nemate pristup ovoj poruci" + "Pošiljateljev potvrđeni identitet je poništen" + "Pozivnice se nisu mogle poslati jednom korisniku ili više njih." + "Nije moguće poslati pozivnicu/e" + "Otključaj" + "Uključi zvuk" + "Nepodržani poziv" + "Nepodržani događaj" + "Korisničko ime" + "Provjera je otkazana" + "Provjera je dovršena" + "Provjera nije uspjela" + "Potvrđeno" + "Provjera uređaja" + "Potvrdi identitet" + "Provjeri korisnika" + "Videozapis" + "Visoka kvaliteta" + "Najbolja kvaliteta, ali veća veličina datoteke" + "Niska kvaliteta" + "Najbrža brzina prijenosa i najmanja veličina datoteke" + "Standardna kvaliteta" + "Ravnoteža kvalitete i brzine prijenosa" + "Glasovna poruka" + "Čekanje…" + "Čekam ovu poruku" + "Vi" + "Ova je soba konfigurirana tako da novi članovi mogu čitati stare poruke. %1$s" + "Identitet korisnika %1$s je poništen. %2$s" + "Identitet korisnika %1$s %2$s je poništen. %3$s" + "(%1$s)" + "Identitet korisnika %1$s je poništen." + "Identitet korisnika %1$s %2$s je poništen. %3$s" + "Povuci provjeru" + "Poveznica %1$s vodi vas na drugo mrežno mjesto %2$s + +Jeste li sigurni da želite nastaviti?" + "Dvaput provjerite ovu poveznicu" + "Odaberite zadanu kvalitetu videozapisa koje prenosite." + "Kvaliteta prijenosa videozapisa" + "Maksimalna dopuštena veličina datoteke je: %1$s" + "Datoteka je prevelika za prijenos" + "Soba je prijavljena" + "Soba je prijavljena i napuštena" + "Potvrda" + "Pogreška" + "Uspjeh" + "Upozorenje" + "Niste spremili sve promjene." + "Vaše promjene nisu spremljene. Jeste li sigurni da se želite vratiti?" + "Želite li spremiti promjene?" + "Maksimalna dopuštena veličina datoteke je: %1$s" + "Odaberite kvalitetu videozapisa koji želite prenijeti." + "Odaberi kvalitetu prijenosa videozapisa" + "Pretraživanje emotikona" + "Već ste prijavljeni na ovom uređaju kao %1$s." + "Vaš matični poslužitelj potrebno je nadograditi kako bi podržavao uslugu Matrix Authentication Service i stvaranje računa." + "Nije uspjelo stvaranje trajne poveznice" + "%1$s nije mogao učitati kartu. Pokušajte ponovno poslije." + "Učitavanje poruka nije uspjelo" + "%1$s nije mogao pristupiti vašoj lokaciji. Pokušajte ponovno poslije." + "Prijenos vaše glasovne poruke nije uspio." + "Soba više ne postoji ili pozivnica više ne vrijedi." + "Poruka nije pronađena" + "%1$s nema dopuštenje za pristup vašoj lokaciji. Pristup možete omogućiti u postavkama." + "%1$s nema dopuštenje za pristup vašoj lokaciji. Omogućite pristup u nastavku." + "%1$s nema dopuštenje za pristup vašem mikrofonu. Omogućite pristup kako biste mogli snimiti glasovnu poruku." + "To može biti zbog problema s mrežom ili poslužiteljem." + "Ova adresa sobe već postoji. Pokušajte urediti polje za adresu sobe ili promijeniti naziv sobe" + "Neki znakovi nisu dopušteni. Podržana su samo slova, brojke i simboli ! $ & ‘ ( ) * + / ; = ? @ [ ] - . _" + "Neke poruke nisu poslane" + "Žao nam je, došlo je do pogreške" + "Pošiljatelj događaja ne podudara se s vlasnikom uređaja koji ga je poslao." + "Autentičnost ove šifrirane poruke ne može se jamčiti na ovom uređaju" + "Šifrirao je prethodno provjereni korisnik." + "Nije šifrirano." + "Šifrirano nepoznatim ili izbrisanim uređajem." + "Šifrirano uređajem koji vlasnik nije potvrdio." + "Šifrirao neprovjereni korisnik." + "🔐️ Pridruži mi se u %1$s" + "Hej, razgovaraj sa mnom u aplikaciji %1$s: %2$s" + "%1$s Android" + "Snažno protresi za prijavu pogreške" + "Snimka zaslona" + "%1$s: %2$s" + "Mogućnosti" + "Ukloni %1$s" + "Postavke" + "Prostori u kojima se članovi mogu pridružiti sobi bez pozivnice." + "Upravljaj prostorima" + "(nepoznati prostor)" + "Drugi prostori čiji niste član" + "Vaši prostori" + "Odabir medija nije uspio, pokušajte ponovno." + "Pritisnite poruku i odaberite “%1$s” kako biste uključili ovdje." + "Prikvačite važne poruke kako bi ih se lakše moglo pronaći" + + "%1$d prikvačena poruka" + "%1$d prikvačene poruke" + "%1$d prikvačenih poruka" + + "Prikvačene poruke" + "Spremate se otići u svoj %1$s račun kako biste poništili identitet. Nakon toga bit ćete vraćeni u aplikaciju." + "Ne možete potvrditi? Idite na svoj račun kako biste poništili svoj identitet." + "Povuci potvrdu i pošalji" + "Možete povući svoju potvrdu i svejedno poslati ovu poruku ili možete za sada otkazati i pokušati ponovno poslije nakon ponovne potvrde korisnika %1$s." + "Vaša poruka nije poslana jer je poništen potvrđeni identitet korisnika %1$s" + "Svejedno pošalji poruku" + "%1$s se služi jednim nepotvrđenim uređajem ili više njih. Možete svejedno poslati poruku ili je za sada otkazati i pokušati ponovno poslije. %2$s je potvrdio/la sve svoje uređaje." + "Vaša poruka nije poslana jer %1$s nije potvrdio sve uređaje" + "Jedan vaš uređaj ili više njih nije potvrđeno. Možete svejedno poslati poruku ili za sada otkazati i pokušati ponovno poslije nakon što potvrdite sve svoje uređaje." + "Vaša poruka nije poslana jer niste potvrdili jedan svoj uređaj ili više njih" + "Uredi administratore ili vlasnike" + "Prijenos medija za obradu nije uspio, pokušajte ponovno." + "Nije moguće dohvatiti korisničke podatke" + "Poruka u sobi %1$s" + "Proširi" + "Smanji" + "Već gledam ovu sobu!" + "%1$s od %2$s" + "%1$s Prikvačene poruke" + "Učitavanje poruke…" + "Prikaži sve" + "Razgovor" + "Podijeli lokaciju" + "Podijeli moju lokaciju" + "Otvori u Apple Maps" + "Otvori u Google Maps" + "Otvori u OpenStreetMap" + "Podijeli ovu lokaciju" + "Prostori koje ste stvorili ili kojima ste se pridružili." + "%1$s • %2$s" + "Prostor %1$s" + "Prostori" + "Prikaži članove" + "Poruka nije poslana jer je poništen potvrđeni identitet korisnika %1$s." + "Poruka nije poslana jer %1$s nije potvrdio sve uređaje." + "Poruka nije poslana jer niste potvrdili jedan svoj uređaj ili više njih." + "Lokacija" + "Inačica: %1$s (%2$s)" + "hr" + "Prijašnje poruke nisu dostupne na ovom uređaju" + "Trebate potvrditi ovaj uređaj kako biste mogli pristupiti prijašnjim porukama" + "Nemate pristup ovoj poruci" + "Nije moguće dešifrirati poruku" + "Ova je poruka blokirana jer niste potvrdili svoj uređaj ili zato što pošiljatelj treba potvrditi vaš identitet." + diff --git a/libraries/ui-strings/src/main/res/values-hu/translations.xml b/libraries/ui-strings/src/main/res/values-hu/translations.xml index 40afdf7c1d..0174bd79b1 100644 --- a/libraries/ui-strings/src/main/res/values-hu/translations.xml +++ b/libraries/ui-strings/src/main/res/values-hu/translations.xml @@ -95,6 +95,8 @@ "Elfelejtette a jelszót?" "Tovább" "Visszalépés" + "Ugrás a szerepkörökre és jogosultságokra" + "Ugrás a beállításokhoz" "Mellőzés" "Meghívás" "Ismerősök meghívása" @@ -111,6 +113,7 @@ "Fiók kezelése" "Eszközök kezelése" "Üzenet" + "Minimalizálás" "Következő" "Nem" "Most nem" @@ -153,6 +156,7 @@ "Kihagyás" "Indítás" "Csevegés indítása" + "Újrakezdés" "Ellenőrzés elindítása" "Koppintson a térkép betöltéséhez" "Fénykép készítése" @@ -178,6 +182,7 @@ "Ki lett jelentkeztetve a munkamenetből" "Megjelenítés" "Hang" + "Béta" "Letiltott felhasználók" "Buborékok" "A hívás elindult" @@ -226,6 +231,7 @@ Ok: %1$s."
"APK telepítése" "Ez a Matrix-azonosító nem található, ezért előfordulhat, hogy a meghívó nem érkezik meg." "Szoba elhagyása" + "Tér elhagyása" "Világos" "A sor a vágólapra másolva" "Hivatkozás a vágólapra másolva" @@ -313,6 +319,7 @@ Ok: %1$s." "Beállítások" "Tér megosztása" "Megosztott tartózkodási hely" + "Megosztott tér" "Kijelentkezés" "Valamilyen hiba történt" "Problémába ütköztünk. Próbálja újra." @@ -381,6 +388,7 @@ Biztos, hogy folytatja?" "Hiba" "Sikeres" "Figyelmeztetés" + "Mentetlen módosításai vannak." "A módosítások nem lettek mentve. Biztos, hogy visszalép?" "Menti a módosításokat?" "A legnagyobb megengedett fájlméret: %1$s" @@ -460,6 +468,7 @@ Biztos, hogy folytatja?" "%1$s • %2$s" "%1$s tér" "Terek" + "Tagok megtekintése" "Az üzenet nem lett elküldve, mert %1$s ellenőrzött személyazonossága megváltozott." "Az üzenet nem lett elküldve, mert %1$s nem ellenőrizte az összes eszközét." "Az üzenet nem lett elküldve, mert egy vagy több eszközét nem ellenőrizte." diff --git a/libraries/ui-strings/src/main/res/values-in/translations.xml b/libraries/ui-strings/src/main/res/values-in/translations.xml index bd7739d5ba..cc2bad1f2b 100644 --- a/libraries/ui-strings/src/main/res/values-in/translations.xml +++ b/libraries/ui-strings/src/main/res/values-in/translations.xml @@ -139,6 +139,7 @@ "Lewati" "Mulai" "Mulai obrolan" + "Mulai dari awal" "Mulai verifikasi" "Ketuk untuk memuat peta" "Ambil foto" @@ -158,6 +159,7 @@ "Pengaturan tingkat lanjut" "sebuah gambar" "Analitik" + "Anda keluar dari ruangan" "Penampilan" "Audio" "Pengguna yang diblokir" @@ -332,6 +334,7 @@ Apakah Anda yakin ingin melanjutkan?" "Eror" "Berhasil" "Peringatan" + "Anda memiliki perubahan yang belum disimpan." "Perubahan Anda belum disimpan. Apakah Anda yakin ingin kembali?" "Simpan perubahan?" "Homeserver Anda perlu ditingkatkan untuk mendukung Matrix Authentication Service dan pembuatan akun." diff --git a/libraries/ui-strings/src/main/res/values-it/translations.xml b/libraries/ui-strings/src/main/res/values-it/translations.xml index 77a1a20782..abde675f0f 100644 --- a/libraries/ui-strings/src/main/res/values-it/translations.xml +++ b/libraries/ui-strings/src/main/res/values-it/translations.xml @@ -42,7 +42,7 @@ "Rimuovere la reazione con %1$s" "Avatar della stanza" "Invia file" - "Azione a tempo limitato richiesta" + "Azione richiesta a tempo limitato, hai un minuto per la verifica" "Mostra password" "Avvia una chiamata" "Stanza obsoleta" @@ -80,6 +80,7 @@ "Rifiuta" "Rifiuta e blocca" "Elimina sondaggio" + "Deseleziona tutti" "Disabilita" "Annulla" "Chiudi" @@ -94,6 +95,8 @@ "Password dimenticata?" "Inoltra" "Indietro" + "Vai a ruoli & autorizzazioni" + "Vai alle impostazioni" "Ignora" "Invita" "Invita persone" @@ -105,10 +108,12 @@ "Esci" "Abbandona la conversazione" "Esci dalla stanza" + "Esci dallo spazio" "Carica altro" "Gestisci account" "Gestisci dispositivi" "Invia messaggio" + "Riduci" "Avanti" "No" "Non ora" @@ -137,6 +142,7 @@ "Riprova la decrittazione" "Salva" "Ricerca" + "Seleziona tutti" "Invia" "Invia messaggio modificato" "Invia messaggio" @@ -150,6 +156,7 @@ "Salta" "Inizia" "Avvia conversazione" + "Ricomincia" "Avvia la verifica" "Tocca per caricare la mappa" "Scatta foto" @@ -165,6 +172,8 @@ "Aggiornamento disponibile" "Informazioni" "Regole sull\'utilizzo consentito" + "Aggiungi un account" + "Aggiungi un altro account" "Aggiunta didascalia" "Impostazioni avanzate" "un\'immagine" @@ -173,6 +182,7 @@ "Sei stato disconnesso dalla sessione" "Aspetto" "Audio" + "Beta" "Utenti bloccati" "Fumetti" "Chiamata avviata" @@ -182,9 +192,11 @@ "Creazione stanza…" "Richiesta annullata" "Hai lasciato la stanza" + "Hai lasciato lo spazio" "Invito rifiutato" "Scuro" "Errore di decrittazione" + "Descrizione" "Opzioni sviluppatore" "ID dispositivo" "Conversazione diretta" @@ -219,6 +231,7 @@ Motivo:. %1$s" "Installa APK" "Questo ID Matrix non può essere trovato, quindi l\'invito potrebbe non essere ricevuto." "Lascio la stanza" + "Uscendo dallo spazio" "Chiaro" "Riga copiata negli appunti" "Collegamento copiato negli appunti" @@ -241,6 +254,7 @@ Motivo:. %1$s" "%1$s (%2$s)" "Nessun risultato" "Nessun nome della stanza" + "Spazio senza nome" "Non cifrato" "Non in linea" "Licenze open source" @@ -294,15 +308,19 @@ Motivo:. %1$s" "Risultati di ricerca" "Sicurezza" "Visto da" + "Seleziona un account" "Invia a" "Invio in corso…" "Invio fallito" "Inviato" "." "Server non supportato" + "Server non raggiungibile" "URL del server" "Impostazioni" + "Condividi lo spazio" "Posizione condivisa" + "Spazio condiviso" "Disconnessione" "Qualcosa è andato storto" "Abbiamo riscontrato un problema. Per favore riprova." @@ -371,11 +389,14 @@ Sei sicuro di voler continuare?" "Errore" "Operazione riuscita" "Attenzione" + "Hai delle modifiche non salvate." "Le modifiche non sono state salvate. Vuoi davvero tornare indietro?" "Salvare le modifiche?" "La dimensione massima consentita per il file è: %1$s" "Seleziona la qualità del video che vuoi caricare." "Seleziona la qualità di caricamento del video" + "Cerca emoji" + "Hai già effettuato l\'accesso su questo dispositivo come %1$s ." "Il tuo homeserver deve essere aggiornato per supportare il Matrix Authentication Service e la creazione di account." "Impossibile creare il collegamento permanente" "%1$s non è riuscito a caricare la mappa. Riprova più tardi." @@ -446,7 +467,9 @@ Sei sicuro di voler continuare?" "Condividi questa posizione" "Spazi che hai creato o a cui hai aderito." "%1$s • %2$s" + "%1$s spazio" "Spazi" + "Visualizza membri" "Messaggio non inviato perché l\'identità verificata di %1$s è stata reimpostata." "Messaggio non inviato perché %1$s non ha verificato tutti i dispositivi." "Messaggio non inviato perché non hai verificato uno o più dispositivi." diff --git a/libraries/ui-strings/src/main/res/values-ka/translations.xml b/libraries/ui-strings/src/main/res/values-ka/translations.xml index dd67ca02c2..0a7b612e55 100644 --- a/libraries/ui-strings/src/main/res/values-ka/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ka/translations.xml @@ -113,6 +113,7 @@ "მისაღები გამოყენების პოლიტიკა" "გაფართოებული პარამეტრები" "ანალიტიკა" + "თქვენ დატოვეთ ოთახი" "გარეგნობა" "აუდიო" "დაბლოკილი მომხმარებლები" @@ -236,6 +237,7 @@ "შეცდომა" "წარმატება" "გაფრთხილება" + "თქვენ გაქვთ შეუნახავი ცვლილებები" "თქვენი ცვლილებები არაა შენახული. დარწმუნებული ხართ დაბრუნებაში?" "შენახვა?" "მუდმივი ბმულის შექმნა ვერ მოხერხდა" diff --git a/libraries/ui-strings/src/main/res/values-ko/translations.xml b/libraries/ui-strings/src/main/res/values-ko/translations.xml index 256e561db5..2ea2017c76 100644 --- a/libraries/ui-strings/src/main/res/values-ko/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ko/translations.xml @@ -148,6 +148,7 @@ "건너뛰기" "시작" "채팅 시작" + "다시 시작하다" "인증 시작" "탭해서 지도 불러오기" "사진 찍기" @@ -167,7 +168,7 @@ "고급 설정" "이미지" "통계" - "방에서 나갔습니다" + "방을 떠남" "세션에서 로그아웃되었습니다." "외관" "소리" @@ -364,6 +365,7 @@ "오류" "성공" "경고" + "저장되지 않은 변경 사항이 있습니다." "변경 내용이 저장되지 않았습니다. 정말로 돌아가시겠습니까?" "변경 사항을 저장하시겠습니까?" "허용되는 최대 파일 크기: %1$s diff --git a/libraries/ui-strings/src/main/res/values-lt/translations.xml b/libraries/ui-strings/src/main/res/values-lt/translations.xml index 535e57f7fe..90aed6b6f8 100644 --- a/libraries/ui-strings/src/main/res/values-lt/translations.xml +++ b/libraries/ui-strings/src/main/res/values-lt/translations.xml @@ -64,6 +64,7 @@ "Taip" "Apie" "Analitika" + "Jūs išėjote iš kambario" "Garsas" "Burbulai" "Pokalbio atsarginė kopija" diff --git a/libraries/ui-strings/src/main/res/values-nb/translations.xml b/libraries/ui-strings/src/main/res/values-nb/translations.xml index 6b0db984ea..09d3eab84c 100644 --- a/libraries/ui-strings/src/main/res/values-nb/translations.xml +++ b/libraries/ui-strings/src/main/res/values-nb/translations.xml @@ -45,6 +45,7 @@ "Tidsbegrenset handling kreves, du har ett minutt på deg til å verifisere" "Vis passord" "Start en samtale" + "Deaktivert rom" "Brukeravatar" "Brukermeny" "Vis avatar" @@ -94,6 +95,7 @@ "Glemt passordet?" "Videresend" "Gå tilbake" + "Gå til roller og tillatelser" "Gå til innstillinger" "Ignorer" "Inviter" @@ -154,6 +156,7 @@ "Hopp over" "Start" "Start chat" + "Begynn på nytt" "Start verifisering" "Trykk for å laste inn kart" "Ta bilde" @@ -251,6 +254,7 @@ "%1$s (%2$s)" "Ingen resultater" "Ingen romnavn" + "Ikke noe områdenavn" "Ikke kryptert" "Frakoblet" "Åpen kildekode-lisenser" @@ -383,6 +387,7 @@ Er du sikker på at du vil fortsette?" "Feil" "Suksess" "Advarsel" + "Du har endringer som ikke er lagret." "Endringene dine er ikke lagret. Er du sikker på at du vil gå tilbake?" "Lagre endringer?" "Maksimal tillatt filstørrelse er: %1$s" @@ -462,6 +467,7 @@ Er du sikker på at du vil fortsette?" "%1$s • %2$s" "%1$s område" "Områder" + "Vis medlemmer" "Meldingen ble ikke sendt fordi %1$ss verifiserte identitet er tilbakestilt." "Meldingen ble ikke sendt fordi %1$s ikke har verifisert alle enheter." "Meldingen ble ikke sendt fordi du ikke har verifisert en eller flere av enhetene dine." diff --git a/libraries/ui-strings/src/main/res/values-nl/translations.xml b/libraries/ui-strings/src/main/res/values-nl/translations.xml index 7c682233a6..790363ba54 100644 --- a/libraries/ui-strings/src/main/res/values-nl/translations.xml +++ b/libraries/ui-strings/src/main/res/values-nl/translations.xml @@ -140,6 +140,7 @@ "Overslaan" "Starten" "Chat starten" + "Opnieuw beginnen" "Verificatie starten" "Tik om kaart te laden" "Foto maken" @@ -158,6 +159,7 @@ "Bijschrift toevoegen" "Geavanceerde instellingen" "Gebruiksgegevens" + "Je hebt de kamer verlaten" "Weergave" "Geluid" "Geblokkeerde gebruikers" @@ -300,6 +302,7 @@ Reden: %1$s." "Fout" "Geslaagd" "Waarschuwing" + "Je hebt niet-opgeslagen wijzigingen" "Je wijzigingen zijn niet opgeslagen. Weet je zeker dat je terug wilt gaan?" "Wijzigingen opslaan?" "Je homeserver moet worden geüpgraded om de Matrix Authentication Service en het aanmaken van accounts te ondersteunen." diff --git a/libraries/ui-strings/src/main/res/values-pl/translations.xml b/libraries/ui-strings/src/main/res/values-pl/translations.xml index 71c60b5f6d..aafac63332 100644 --- a/libraries/ui-strings/src/main/res/values-pl/translations.xml +++ b/libraries/ui-strings/src/main/res/values-pl/translations.xml @@ -2,6 +2,7 @@ "Dodaj reakcję: %1$s" "Awatar" + "Zmniejsz pole tekstowe wiadomości" "Usuń" "Wprowadzono %1$d cyfrę" @@ -11,6 +12,7 @@ "Edytuj awatar" "Podgląd pełnego adresu %1$s" "Szczegóły szyfrowania" + "Powiększ pole tekstowe wiadomości" "Ukryj hasło" "Dołącz do połączenia" "Przejdź na dół" @@ -42,7 +44,7 @@ "Usuń reakcję z %1$s" "Awatar pokoju" "Wyślij pliki" - "Wymagane jest działanie ograniczone czasowo" + "Wymagane jest działanie ograniczone czasowo, została jedna minuta" "Pokaż hasło" "Rozpocznij rozmowę" "Pokój nagrobkowy" @@ -80,6 +82,7 @@ "Odrzuć" "Odrzuć i zablokuj" "Usuń ankietę" + "Odznacz wszystko" "Wyłącz" "Odrzuć" "Zamknij" @@ -90,9 +93,11 @@ "Włącz" "Zakończ ankietę" "Wprowadź PIN" + "Zakończ" "Nie pamiętasz hasła?" "Przekaż dalej" "Wróć" + "Przejdź do ustawień" "Ignoruj" "Zaproś" "Zaproś znajomych" @@ -104,10 +109,12 @@ "Opuść" "Opuść rozmowę" "Opuść pokój" + "Opuść przestrzeń" "Załaduj więcej" "Zarządzaj kontem" "Zarządzaj urządzeniami" "Wiadomość" + "Minimalizuj" "Dalej" "Nie" "Nie teraz" @@ -136,6 +143,7 @@ "Ponów próbę odszyfrowania" "Zapisz" "Szukaj" + "Zaznacz wszystko" "Wyślij" "Wyślij edytowaną wiadomość" "Wyślij wiadomość" @@ -149,6 +157,7 @@ "Pomiń" "Rozpocznij" "Rozpocznij chat" + "Zacznij od nowa" "Rozpocznij weryfikację" "Stuknij, aby załadować mapę" "Zrób zdjęcie" @@ -164,12 +173,17 @@ "Dostępna aktualizacja" "O programie" "Polityka użytkowania" + "Dodaj konto" + "Dodaj kolejne konto" "Dodawanie opisu" "Ustawienia zaawansowane" "obraz" "Dane analityczne" + "Opuściłeś pokój" + "Zostałeś wylogowany z sesji" "Wygląd" "Dźwięk" + "Beta" "Zablokowani użytkownicy" "Bąbelki" "Rozpoczęto rozmowę" @@ -179,9 +193,11 @@ "Tworzenie pokoju…" "Anulowano żądanie" "Opuszczono pokój" + "Opuścił przestrzeń" "Odrzucono zaproszenie" "Ciemny" "Błąd deszyfrowania" + "Opis" "Opcje programisty" "ID urządzenia" "Czat prywatny" @@ -216,6 +232,7 @@ Powód: %1$s." "Zainstaluj APK" "Nie można znaleźć identyfikatora Matrix ID, zaproszenie mogło nie dotrzeć." "Opuszczanie pokoju" + "Opuszczam przestrzeń" "Jasny" "Wiersz skopiowany do schowka" "Link został skopiowany do schowka" @@ -228,7 +245,7 @@ Powód: %1$s." "%1$d członek" - "%1$d członki" + "%1$d członków" "%1$d członków" "Wiadomość" @@ -240,6 +257,7 @@ Powód: %1$s." "%1$s (%2$s)" "Brak wyników" "Brak nazwy pokoju" + "Brak nazwy przestrzeni" "Nieszyfrowane" "Offline" "Licencje open-source" @@ -263,7 +281,9 @@ Powód: %1$s." "Przygotowuję…" "Polityka prywatności" "Pokój prywatny" + "Prywatna przestrzeń" "Pokój publiczny" + "Przestrzeń publiczna" "Reakcja" "Reakcje" "Powód" @@ -282,6 +302,11 @@ Powód: %1$s." "Pokój" "Nazwa pokoju" "np. nazwa projektu" + + "%1$d Pokój" + "%1$d Pokoje" + "%1$d Pokoi" + "Zapisano zmiany" "Zapisywanie" "Blokada ekranu" @@ -289,18 +314,28 @@ Powód: %1$s." "Wyniki wyszukiwania" "Bezpieczeństwo" "Wyświetlone przez" + "Wybierz konto" "Wyślij do" "Wysyłanie…" "Błąd wysyłania" "Wysłano" ". " "Serwer nie jest obsługiwany" + "Serwer niedostępny" "Adres URL serwera" "Ustawienia" + "Udostępnij przestrzeń" "Udostępniona lokalizacja" + "Udostępniona przestrzeń" "Wylogowywanie" "Coś poszło nie tak" "Napotkaliśmy problem. Spróbuj ponownie." + "Przestrzeń" + + "%1$d Przestrzeń" + "%1$d Przestrzenie" + "%1$d Przestrzeni" + "Rozpoczynanie czatu…" "Naklejka" "Sukces" @@ -331,6 +366,12 @@ Powód: %1$s." "Zweryfikuj tożsamość" "Zweryfikuj użytkownika" "Film" + "Wysoka jakość" + "Najlepsza jakość, większy rozmiar pliku" + "Niska jakość" + "Największa prędkość przesyłania i najmniejszy rozmiar pliku" + "Jakość standardowa" + "Balans między jakością a szybkością przesyłania" "Wiadomość głosowa" "Oczekiwanie…" "Oczekiwanie na tę wiadomość" @@ -345,14 +386,24 @@ Powód: %1$s." Czy na pewno chcesz kontynuować?" "Sprawdź dwukrotnie ten link" + "Wybierz domyślną jakość przesyłanych filmów." + "Jakość przesyłania wideo" + "Maksymalny dozwolony rozmiar pliku to: %1$s" + "Rozmiar pliku jest za duży, aby go przesłać" "Pokój zgłoszony" "Opuszczono i zgłoszono pokój" "Potwierdzenie" "Błąd" "Sukces" "Ostrzeżenie" + "Masz niezapisane zmiany." "Zmiany nie zostały zapisane. Czy na pewno chcesz wrócić?" "Zapisać zmiany?" + "Maksymalny dozwolony rozmiar pliku to: %1$s" + "Wybierz jakość filmu, który chcesz przesłać." + "Wybierz jakość przesyłania wideo" + "Wyszukaj emoji" + "Jesteś już zalogowany na tym urządzeniu jako %1$s." "Twój serwer domowy wymaga aktualizacji, aby uzyskać wsparcie usługi Matrix Authentication Service i tworzenia kont." "Nie udało się utworzyć linku bezpośredniego" "%1$s nie mogło wczytać mapy. Spróbuj ponownie później." @@ -404,6 +455,7 @@ Czy na pewno chcesz kontynuować?" "Twoja wiadomość nie została wysłana, ponieważ %1$s nie zweryfikował swoich wszystkich urządzeń" "Jedno lub więcej z Twoich urządzeń jest niezweryfikowanych. Wyślij wiadomość mimo to lub anuluj i spróbuj ponownie po zweryfikowaniu wszystkich swoich urządzeń." "Twoja wiadomość nie została wysłana, ponieważ nie zweryfikowałeś jednego lub więcej swoich urządzeń." + "Edytuj administratorów lub właścicieli" "Przetwarzanie multimediów do przesłania nie powiodło się, spróbuj ponownie." "Nie można pobrać danych użytkownika" "Wiadomość w %1$s" @@ -421,6 +473,10 @@ Czy na pewno chcesz kontynuować?" "Otwórz w Google Maps" "Otwórz w OpenStreetMap" "Udostępnij tę lokalizację" + "Przestrzenie, które stworzyłeś lub do których dołączyłeś." + "%1$s • %2$s" + "Przestrzeń %1$s" + "Przestrzenie" "Wiadomość nie została wysłana, ponieważ tożsamość %1$s została zresetowana." "Wiadomość nie została wysłana, ponieważ %1$s nie zweryfikował wszystkich urządzeń." "Wiadomość nie została wysłana, ponieważ nie zweryfikowałeś jednego lub więcej swoich urządzeń." diff --git a/libraries/ui-strings/src/main/res/values-pt-rBR/translations.xml b/libraries/ui-strings/src/main/res/values-pt-rBR/translations.xml index fabc600b67..3863a24fd8 100644 --- a/libraries/ui-strings/src/main/res/values-pt-rBR/translations.xml +++ b/libraries/ui-strings/src/main/res/values-pt-rBR/translations.xml @@ -2,6 +2,7 @@ "Adicionar reação: %1$s" "Avatar" + "Minimizar campo de texto de mensagem" "Apagar" "%1$d dígito digitado" @@ -10,6 +11,7 @@ "Editar avatar" "O endereço completo será %1$s" "Detalhes de criptografia" + "Expandir campo de texto de mensagem" "Ocultar senha" "Entrar à chamada" "Ir para o final" @@ -40,7 +42,7 @@ "Remover reação com %1$s" "Avatar da sala" "Enviar arquivos" - "Ação de tempo limitado necessária" + "Ação de tempo limitado necessária, você tem um minuto para verificar" "Mostrar senha" "Iniciar uma chamada" "Sala morta" @@ -78,6 +80,7 @@ "Recusar" "Recusar e bloquear" "Excluir enquete" + "Desselecionar tudo" "Desativar" "Descartar" "Dispensar" @@ -88,9 +91,12 @@ "Ativar" "Encerrar enquete" "Digitar PIN" + "Concluir" "Esqueceu a senha?" "Encaminhar" "Voltar" + "Ir aos cargos e permissões" + "Ir às configurações" "Ignorar" "Convidar" "Convidar pessoas" @@ -102,10 +108,12 @@ "Sair" "Sair da conversa" "Sair da sala" + "Sair do espaço" "Carregar mais" "Gerenciar conta" "Gerenciar dispositivos" "Mensagem" + "Minimizar" "Avançar" "Não" "Agora não" @@ -134,6 +142,7 @@ "Tentar descriptografar novamente" "Salvar" "Pesquisar" + "Selecionar tudo" "Enviar" "Enviar mensagem editada" "Enviar mensagem" @@ -147,6 +156,7 @@ "Pular" "Iniciar" "Iniciar conversa" + "Começar de novo" "Iniciar verificação" "Toque para carregar o mapa" "Tirar foto" @@ -162,12 +172,17 @@ "Atualização disponível" "Sobre" "Política de uso aceitável" + "Adicionar uma conta" + "Adicionar outra conta" "Adicionando legenda" "Configurações avançadas" "uma imagem" "Telemetria" + "Você saiu da sala" + "Você foi desconectado da sessão" "Aparência" "Áudio" + "Beta" "Usuários bloqueados" "Balões" "Chamada iniciada" @@ -177,9 +192,11 @@ "Criando sala…" "Solicitação cancelada" "Saiu da sala" + "Saiu do espaço" "Convite recusado" "Escuro" "Erro de descriptografia" + "Descrição" "Opções de desenvolvedor" "ID do dispositivo" "Conversa direta" @@ -214,9 +231,11 @@ Motivo:​ %1$s." "Instalar APK" "Este ID Matrix não foi encontrado, então o convite pode não ser recebido" "Saindo da sala" + "Saindo do espaço" "Claro" "Linha copiada para a área de transferência" "Link copiado para área de transferência" + "Vincular novo dispositivo" "Carregando…" "Carregando mais…" @@ -229,13 +248,16 @@ Motivo:​ %1$s." "Mensagem" "Ações de mensagem" + "Falha no envio da mensagem" "Layout da mensagem" "Mensagem removida" "Moderno" "Mudo" + "Nome" "%1$s (%2$s)" "Não há resultados" "Não há um nome para a sala" + "Sem nome de espaço" "Sem criptografia" "Off-line" "Licenças de código aberto" @@ -289,18 +311,24 @@ Motivo:​ %1$s." "Resultados da pesquisa" "Segurança" "Visto por" + "Selecionar uma conta" "Enviar para" "Enviando…" "Envio falhou" "Enviado" ". " "Servidor não suportado" + "Servidor inacessível" "URL do servidor" "Configurações" + "Compartilhar espaço" "Localização compartilhada" + "Espaço compartilhado" "Saindo" "Algo deu errado" "Encontramos um problema. Tente novamente." + "Espaço" + "Sobre o que é esse espaço?" "%1$d espaço" "%1$d espaços" @@ -335,10 +363,17 @@ Motivo:​ %1$s." "Verificar identidade" "Verificar usuário" "Vídeo" + "Alta qualidade" + "Melhor qualidade, mas tamanho de arquivo maior" + "Baixa qualidade" + "Maior velocidade de envio e menor tamanho de arquivo" + "Qualidade normal" + "Equilíbrio entre qualidade e velocidade de envio" "Mensagem de voz" "Aguardando…" "Aguardando por esta mensagem" "Você" + "Esta sala foi configurada para que membros novos possam ler o histórico. %1$s" "A identidade de %1$s foi redefinida. %2$s" "A identidade de %1$s %2$s foi redefinida. %3$s" "(%1$s)" @@ -349,14 +384,24 @@ Motivo:​ %1$s." Você tem certeza de que deseja continuar?" "Verifique este link duas vezes" + "Selecione a qualidade padrão dos videos que você envia." + "Qualidade de envio de vídeos" + "O tamanho máximo permitido de arquivos é: %1$s" + "O tamanho do arquivo é muito grande para ser enviado" "Sala denunciada" "Denunciou e deixou a sala" "Confirmação" "Erro" "Sucesso" "Alerta" + "Você tem alterações não salvas." "Suas alterações não foram salvas. Tem certeza de que você quer voltar?" "Salvar alterações?" + "O tamanho máximo permitido de arquivos é: %1$s" + "Selecione a qualidade do video que quer enviar." + "Selecione a qualidade de envio dos vídeos" + "Pesquisar emojis" + "Você já está conectado neste dispositivo como %1$s ." "Seu servidor-casa precisa ser atualizado para oferecer suporte ao Matrix Authentication Service e à criação de contas." "Falha ao criar o link permanente" "%1$s não conseguiu carregar o mapa. Por favor, tente novamente mais tarde." @@ -389,6 +434,11 @@ Você tem certeza de que deseja continuar?" "Opções" "Remover %1$s" "Configurações" + "Os espaços dos quais os membros podem entrar na sala sem um convite." + "Gerenciar espaços" + "(Espaço desconhecido)" + "Outros espaços dos quais você não é um membro" + "Seus espaços" "Falha ao selecionar a mídia, tente novamente." "Pressione em uma mensagem e escolha \"%1$s\" para incluir aqui." "Fixe mensagens importantes para que elas possam ser facilmente descobertas" @@ -427,7 +477,9 @@ Você tem certeza de que deseja continuar?" "Compartilhar esta localização" "Os espaços que você criou ou entrou." "%1$s • %2$s" + "Espaço %1$s" "Espaços" + "Ver membros" "Mensagem não enviada porque a identidade verificada de %1$s foi redefinida." "A mensagem não foi enviada porque %1$s não verificou todos os dispositivos." "Mensagem não enviada porque você não verificou um ou mais dos seus dispositivos." diff --git a/libraries/ui-strings/src/main/res/values-pt/translations.xml b/libraries/ui-strings/src/main/res/values-pt/translations.xml index 5e55f3a9de..019f2e6ac4 100644 --- a/libraries/ui-strings/src/main/res/values-pt/translations.xml +++ b/libraries/ui-strings/src/main/res/values-pt/translations.xml @@ -153,6 +153,7 @@ "Saltar" "Iniciar" "Iniciar conversa" + "Começar de novo" "Iniciar verificação" "Toca para carregar o mapa" "Tirar foto" @@ -382,6 +383,7 @@ Tens a certeza de que queres continuar?" "Erro" "Sucesso" "Aviso" + "Tens alterações por guardar." "As tuas alterações não foram guardadas. Tens a certeza que queres voltar atrás?" "Guardar alterações?" "O tamanho máximo de ficheiro permitido é: %1$s" diff --git a/libraries/ui-strings/src/main/res/values-ro/translations.xml b/libraries/ui-strings/src/main/res/values-ro/translations.xml index 0cf1acf673..9e56d09b16 100644 --- a/libraries/ui-strings/src/main/res/values-ro/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ro/translations.xml @@ -97,6 +97,7 @@ "Ați uitat parola?" "Redirecționați" "Înapoi" + "Mergeți la roluri și permisiuni" "Mergeți la setări" "Ignorați" "Invitați" @@ -157,6 +158,7 @@ "Omiteți" "Începeți" "Începeți discuția" + "Începeți din nou" "Începeți verificarea" "Atingeți pentru a încărca harta" "Faceți o fotografie" @@ -235,6 +237,7 @@ Motiv:%1$s." "Deschis" "Linie copiată în clipboard" "Linkul a fost copiat în clipboard" + "Conectați un dispozitiv nou" "Se încarcă…" "Se încarcă…" @@ -249,10 +252,12 @@ Motiv:%1$s." "Mesaj" "Acțiuni mesaj" + "Mesajul nu a putut fi trimis" "Aspectul mesajelor" "Mesaj șters" "Modern" "Dezactivați sunetul" + "Nume" "%1$s (%2$s)" "Niciun rezultat" "Fără nume de cameră" @@ -330,6 +335,7 @@ Motiv:%1$s." "Ceva nu a mers bine" "Am întâmpinat o problemă. Vă rugăm să încercați din nou." "Spațiu" + "Despre ce este vorba în acest spațiu?" "%1$d Spațiu" "%1$d Spații" @@ -375,6 +381,7 @@ Motiv:%1$s." "Se aşteaptă…" "Mesaj în așteptare" "Dumneavoastră" + "Această cameră a fost configurată astfel încât noii membri să poată citi istoricul. %1$s" "Identitatea lui %1$s a fost resetată. %2$s" "Identitatea %2$s a lui %1$s a fost resetată. %3$s" "(%1$s)" @@ -395,6 +402,7 @@ Sunteți sigur că doriți să continuați?" "Eroare" "Succes" "Avertisment" + "Aveți modificări nesalvate." "Modificările dumneavoastră nu au fost salvate. Sunteți sigur că doriți să vă întoarceți?" "Salvați modificările?" "Dimensiunea maximă permisă pentru fișiere este: %1$s" @@ -434,6 +442,11 @@ Sunteți sigur că doriți să continuați?" "Opțiuni" "Ștergeți %1$s" "Setări" + "Spațile din care membrii se pot alătura camerei fără invitație." + "Gestionați spațiile" + "(Spațiu necunoscut)" + "Alte spații din care nu faceți parte" + "Spațiile dumneavoastră" "Selectarea fișierelor media a eșuat, încercați din nou." "Apăsați pe un mesaj și alegeți \"%1$s\" pentru a-l include aici." "Fixați mesajele importante, astfel încât să poată fi descoperite cu ușurință" @@ -475,6 +488,7 @@ Sunteți sigur că doriți să continuați?" "%1$s • %2$s" "Spațiu %1$s" "Spații" + "Vizualizați membrii" "Mesajul nu a fost trimis deoarece identitatea verificată a lui %1$s s-a schimbat." "Mesajul nu a fost trimis deoarece %1$s nu a verificat toate dispozitivele." "Mesajul nu a fost trimis deoarece nu ați verificat unul sau mai multe dispozitive." diff --git a/libraries/ui-strings/src/main/res/values-ru/translations.xml b/libraries/ui-strings/src/main/res/values-ru/translations.xml index fc856adf94..1ccde058c3 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -1,6 +1,6 @@ - "Добавить реакцию:%1$s" + "Добавить реакцию: %1$s" "Аватар" "Свернуть поле текста сообщения" "Удалить" @@ -29,7 +29,7 @@ "Поле PIN-кода" "Воспроизвести" "Опрос" - "Опрос завершен" + "Завершённый опрос" "Реагировать вместе с %1$s" "Реакция с помощью эмодзи" "Прочитано %1$s и %2$s" @@ -85,7 +85,7 @@ "Отменить выбор" "Отключить" "Отменить" - "Отклонить" + "Закрыть" "Готово" "Редактировать" "Изменить подпись" @@ -97,6 +97,8 @@ "Забыли пароль?" "Переслать" "Вернуться" + "Перейти к ролям и разрешениям" + "Перейти к настройкам" "Игнорировать" "Пригласить" "Пригласить в комнату" @@ -156,6 +158,7 @@ "Пропустить" "Начать" "Начать чат" + "Начать заново" "Начать подтверждение" "Нажмите, чтобы загрузить карту" "Сделать фото" @@ -193,7 +196,7 @@ "Покинул комнату" "Покинуть пространство" "Приглашение отклонено" - "Тёмное" + "Темная" "Ошибка расшифровки" "Описание" "Для разработчика" @@ -229,7 +232,7 @@ "В ответ на %1$s" "Установить APK" "Идентификатор Matrix ID не найден, приглашение может быть не получено." - "Покидание комнаты" + "Покидаем комнату" "Покинуть пространство" "Светлое" "Строка скопирована в буфер обмена" @@ -324,10 +327,11 @@ "Настройки" "Поделиться пространством" "Поделился местоположением" + "Общее пространство" "Выход…" "Что-то пошло не так" "Мы столкнулись с проблемой. Пожалуйста, попробуйте еще раз." - "Подпространство" + "Пространство" "%1$d Пространство" "%1$d Пространств" @@ -393,6 +397,7 @@ "Ошибка" "Успешно" "Предупреждение" + "У вас есть несохраненные изменения." "Изменения не сохранены. Вы действительно хотите вернуться?" "Сохранить изменения?" "Максимально допустимый размер файла: %1$s" @@ -473,6 +478,7 @@ "%1$s • %2$s" "%1$s пространство" "Пространства" + "Просмотреть участников" "Сообщение не отправлено, потому что подтвержденная личность %1$s была сброшена." "Сообщение не отправлено, потому что %1$s не проверил одно или несколько устройств." "Сообщение не отправлено, поскольку вы не подтвердили одно или несколько своих устройств." diff --git a/libraries/ui-strings/src/main/res/values-sk/translations.xml b/libraries/ui-strings/src/main/res/values-sk/translations.xml index baee4b4367..0928010918 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -2,6 +2,7 @@ "Pridať reakciu: %1$s" "Obrázok" + "Minimalizovať textové pole správy" "Vymazať" "%1$d zadaná číslica" @@ -11,6 +12,7 @@ "Upraviť obrázok" "Celá adresa bude %1$s" "Podrobnosti o šifrovaní" + "Rozbaliť textové pole správy" "Skryť heslo" "Pripojiť sa k hovoru" "Prejsť na spodok" @@ -42,7 +44,7 @@ "Odstrániť reakciu s %1$s" "Obrázok miestnosti" "Odoslať súbory" - "Vyžaduje sa časovo obmedzená akcia" + "Vyžaduje sa časovo obmedzená akcia, na overenie máte jednu minútu" "Zobraziť heslo" "Začať hovor" "Opustená miestnosť" @@ -80,6 +82,7 @@ "Odmietnuť" "Odmietnuť a zablokovať" "Odstrániť anketu" + "Zrušiť výber všetkých" "Vypnúť" "Zahodiť" "Zamietnuť" @@ -94,6 +97,8 @@ "Zabudnuté heslo?" "Preposlať" "Ísť späť" + "Prejsť na roly a oprávnenia" + "Prejsť na nastavenia" "Ignorovať" "Pozvať" "Pozvať ľudí" @@ -105,10 +110,12 @@ "Opustiť" "Opustiť konverzáciu" "Opustiť miestnosť" + "Opustiť priestor" "Načítať viac" "Spravovať účet" "Spravovať zariadenia" "Poslať správu" + "Minimalizovať" "Ďalej" "Nie" "Teraz nie" @@ -137,6 +144,7 @@ "Opakovať dešifrovanie" "Uložiť" "Hľadať" + "Vybrať všetko" "Odoslať" "Odoslať upravenú správa" "Odoslať správu" @@ -150,6 +158,7 @@ "Preskočiť" "Spustiť" "Začať konverzáciu" + "Začať odznova" "Spustiť overovanie" "Ťuknutím načítate mapu" "Urobiť fotku" @@ -165,6 +174,8 @@ "Aktualizácia je k dispozícii" "O aplikácii" "Zásady prijateľného používania" + "Pridať účet" + "Pridať ďalší účet" "Pridáva sa titulok" "Pokročilé nastavenia" "obrázok" @@ -173,6 +184,7 @@ "Boli ste odhlásení zo relácie." "Vzhľad" "Zvuk" + "Beta" "Blokovaní používatelia" "Bubliny" "Hovor sa začal" @@ -182,9 +194,11 @@ "Vytváranie miestnosti…" "Žiadosť bola zrušená" "Opustil/a miestnosť" + "Opustil priestor" "Pozvánka bola odmietnutá" "Tmavý" "Chyba dešifrovania" + "Popis" "Možnosti pre vývojárov" "ID zariadenia" "Priama konverzácia" @@ -219,6 +233,7 @@ Dôvod: %1$s." "Inštalovať APK" "Toto Matrix ID sa nedá nájsť, takže pozvánka nemusí byť prijatá." "Opustenie miestnosti" + "Opúšťanie priestoru" "Svetlý" "Riadok skopírovaný do schránky" "Odkaz bol skopírovaný do schránky" @@ -236,6 +251,7 @@ Dôvod: %1$s." "Správa" "Akcie správy" + "Správu sa nepodarilo odoslať" "Štýl správ" "Správa odstránená" "Moderné" @@ -243,6 +259,7 @@ Dôvod: %1$s." "%1$s (%2$s)" "Žiadne výsledky" "Žiadny názov miestnosti" + "Žiadny názov priestoru" "Nešifrované" "Offline" "Licencie s otvoreným zdrojom" @@ -299,15 +316,19 @@ Dôvod: %1$s." "Výsledky hľadania" "Bezpečnosť" "Videné" + "Vyberte účet" "Odoslať" "Odosiela sa…" "Odoslanie zlyhalo" "Odoslané" ". " "Server nie je podporovaný" + "Server je nedostupný" "URL adresa servera" "Nastavenia" + "Zdieľať priestor" "Zdieľaná poloha" + "Zdieľaný priestor" "Odhlasovanie" "Niečo sa pokazilo" "Vyskytol sa problém. Skúste to prosím znova." @@ -357,6 +378,7 @@ Dôvod: %1$s." "Čaká sa…" "Čaká sa na dešifrovací kľúč" "Vy" + "Správy, ktoré odošlete, budú zdieľané s novými členmi pozvanými do tejto miestnosti. %1$s" "Totožnosť používateľa %1$s sa obnovila.%2$s" "Totožnosť používateľa %1$s %2$s bola obnovená. %3$s" "(%1$s)" @@ -377,11 +399,14 @@ Naozaj chcete pokračovať?" "Chyba" "Úspech" "Upozornenie" + "Máte neuložené zmeny." "Vaše zmeny neboli uložené. Naozaj sa chcete vrátiť?" "Uložiť zmeny?" "Maximálna povolená veľkosť súboru je: %1$s" "Vyberte kvalitu videa, ktoré chcete nahrať." "Vyberte kvalitu nahrávania videa" + "Hľadať emotikony" + "Už ste prihlásený/á na tomto zariadení ako %1$s ." "Váš domovský server musí byť aktualizovaný tak, aby podporoval Matrix Authentication Service a vytvorenie účtu." "Nepodarilo sa vytvoriť trvalý odkaz" "%1$s nedokázal načítať mapu. Skúste to prosím neskôr." @@ -414,6 +439,11 @@ Naozaj chcete pokračovať?" "Možnosti" "Odstrániť %1$s" "Nastavenia" + "Priestory, kde sa členovia môžu pripojiť k miestnosti bez pozvania." + "Spravovať priestory" + "(Neznámy priestor)" + "Iné priestory, ktorých nie ste členom" + "Vaše priestory" "Nepodarilo sa vybrať médium, skúste to prosím znova." "Stlačte správu a vyberte možnosť „%1$s“, ktorú chcete zahrnúť sem." "Pripnite dôležité správy, aby sa dali ľahko nájsť" @@ -453,7 +483,9 @@ Naozaj chcete pokračovať?" "Zdieľajte túto polohu" "Priestory, ktoré ste vytvorili alebo ku ktorým ste sa pripojili." "%1$s • %2$s" + "%1$s priestor" "Priestory" + "Zobraziť členov" "Správa nebola odoslaná, pretože sa zmenila overená totožnosť používateľa %1$s." "Správa nebola odoslaná, pretože %1$s neoveril/a všetky zariadenia." "Správa nebola odoslaná, pretože ste neoverili jedno alebo viac svojich zariadení." diff --git a/libraries/ui-strings/src/main/res/values-sv/translations.xml b/libraries/ui-strings/src/main/res/values-sv/translations.xml index e3cf26193e..7d84b11d87 100644 --- a/libraries/ui-strings/src/main/res/values-sv/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sv/translations.xml @@ -148,6 +148,7 @@ "Hoppa över" "Starta" "Starta chat" + "Börja om" "Starta verifiering" "Tryck för att ladda kartan" "Ta ett foto" @@ -369,6 +370,7 @@ Anledning:%1$s." "Fel" "Lyckades" "Varning" + "Du har osparade ändringar." "Dina ändringar har inte sparats. Är du säker på att du vill gå tillbaka?" "Spara ändringar?" "Den maximala tillåtna filstorleken är: %1$s" diff --git a/libraries/ui-strings/src/main/res/values-tr/translations.xml b/libraries/ui-strings/src/main/res/values-tr/translations.xml index c72f5a6139..b7b0bc841d 100644 --- a/libraries/ui-strings/src/main/res/values-tr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-tr/translations.xml @@ -122,6 +122,7 @@ "Atla" "Başlat" "Sohbeti başlat" + "Yeniden Başla" "Doğrulamayı başlat" "Haritayı yüklemek için dokunun" "Fotoğraf çek" @@ -139,6 +140,7 @@ "Açıklama ekleme" "Gelişmiş Ayarlar" "Analizler" + "Odadan ayrıldın." "Görünüm" "Ses" "Engellenen kullanıcılar" @@ -307,6 +309,7 @@ Devam etmek istediğinizden emin misiniz?" "Hata" "Başarılı" "Uyarı" + "Kaydedilmemiş değişiklikleriniz var." "Değişiklikleriniz kaydedilmedi. Geri dönmek istediğinden emin misin?" "Değişiklikleri Kaydet?" "Ana sunucunuzun Matrix Authentication Service ve hesap oluşturmayı destekleyecek şekilde güncellenmesi gerekiyor." diff --git a/libraries/ui-strings/src/main/res/values-uk/translations.xml b/libraries/ui-strings/src/main/res/values-uk/translations.xml index a4c171a21b..0b1083ea1a 100644 --- a/libraries/ui-strings/src/main/res/values-uk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-uk/translations.xml @@ -152,6 +152,7 @@ "Пропустити" "Розпочати" "Розпочати бесіду" + "Почати спочатку" "Почати верифікацію" "Натисніть, щоб завантажити мапу" "Зробити фото" @@ -382,6 +383,7 @@ "Помилка" "Успіх" "Попередження" + "У вас є не збережені зміни." "Внесені зміни не збережено. Ви впевнені, що хочете повернутися?" "Зберегти зміни?" "Максимально дозволений розмір файлу: %1$s" diff --git a/libraries/ui-strings/src/main/res/values-ur/translations.xml b/libraries/ui-strings/src/main/res/values-ur/translations.xml index 5abd2e0894..18d8b49ed5 100644 --- a/libraries/ui-strings/src/main/res/values-ur/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ur/translations.xml @@ -114,6 +114,7 @@ "چھوڑیں" "شروع کریں" "گفتگو شروع کریں" + "از سر نو شروع کریں" "توثیق شروع کریں" "نقشہ لادنے کیلئے تھپتھپائیں" "تصویر لیں" @@ -129,6 +130,7 @@ "قابل قبول استعمال کی سیاست" "اعلیٰ ترتیبات" "تجزیات" + "آپ کمرے سے رخصت ہوگئے" "ظہور" "صوت" "مسدود صارفین" @@ -265,6 +267,7 @@ "خرابی" "کامیابی" "انتباہ" + "آپکے پاس غیر محفوظ تبدیلیاں ہیں" "آپ کی تبدیلیاں محفوظ نہیں کی گئیں۔ کیا آپ کو یقین ہے کہ آپ واپس جانا چاہتے ہیں؟" "تبدیلیاں محفوظ کریں؟" "‏Matrix Authentication Service اور اکاؤنٹ بنانے میں معاونت کے لیے آپ کے ہوم سرور کو اپ گریڈ کرنے کی ضرورت ہے۔" diff --git a/libraries/ui-strings/src/main/res/values-uz/translations.xml b/libraries/ui-strings/src/main/res/values-uz/translations.xml index 40da86f9d5..5d5b7ea4d4 100644 --- a/libraries/ui-strings/src/main/res/values-uz/translations.xml +++ b/libraries/ui-strings/src/main/res/values-uz/translations.xml @@ -2,6 +2,7 @@ "Reaksiya qoʻyish: %1$s" "Avatar" + "Xabar matni maydonini kichraytirish" "Oʻchirish" "%1$d ta raqam kiritildi" @@ -10,6 +11,7 @@ "Avatarni tahrirlash" "To\'liq manzil %1$s bo\'ladi" "Shifrlash tafsilotlari" + "Xabar matni maydonini kengaytirish" "Parolni yashirish" "Qoʻngʻiroqga qoʻshilish" "Pastga o\'tish" @@ -40,7 +42,7 @@ "%1$s bilan reaktsiyani olib tashlang" "Xona avatari" "Fayllarni yuborish" - "Vaqt cheklangan harakat talab qilinadi" + "Amal bajarish vaqti cheklangan, tasdiqlash uchun bir daqiqa vaqtingiz bor" "Parolni ko\'rsatish" "Qoʻngʻiroqni boshlash" "Arxivlangan xona" @@ -88,6 +90,7 @@ "Yoqish" "So‘rovnomani tugatish" "PIN kodni kiriting" + "Tugatish" "Parolni unutdingizmi?" "Oldinga" "Ortga qaytish" @@ -147,6 +150,7 @@ "Oʻtkazib yuborish" "Boshlash" "Suhbatni boshlash" + "Qaytadan boshlang" "Tasdiqlashni boshlang" "Xaritani yuklash uchun bosing" "Rasmga olmoq" @@ -162,10 +166,14 @@ "Yangilash mavjud" "Haqida" "Qabul qilinadigan foydalanish siyosati" + "Hisob qo‘shish" + "Boshqa hisob qo‘shish" "Sarlavha qoʻshish" "Kengaytirilgan sozlamalar" "rasm" "Analitika" + "Siz xonani tark etdingiz" + "Siz sessiyadan chiqdingiz" "Ko\'rinish" "Audio" "Bloklangan foydalanuvchilar" @@ -180,6 +188,7 @@ "Taklif rad etildi" "Tungi" "Shifrni ochish xatosi" + "Tavsif" "Dasturchi variantlari" "Qurilma ID" "Shaxsiy suhbat" @@ -258,12 +267,18 @@ Sababi:%1$s." "Tayyorlanmoqda…" "Maxfiylik siyosati" "Shaxsiy xona" + "Shaxsiy guruh" "Jamoat xonasi" + "Jamoat guruhi" "Reaktsiya" "reaksiyalar" "Sabab" "Qayta tiklash kaliti" "Yangilanmoqda…" + + "%1$d ta javob" + "%1$d ta javob" + "%1$sga Javob berilmoqda" "Xato haqida xabar bering" "Muammo haqida xabar bering" @@ -272,6 +287,10 @@ Sababi:%1$s." "Xona" "Xona nomi" "masalan, loyihangiz nomi" + + "%1$d Xona" + "%1$d Xonalar" + "Saqlangan oʻzgarishlar" "Saqlash" "Ekran qulfi" @@ -279,18 +298,25 @@ Sababi:%1$s." "Qidiruv natijalari" "Xavfsizlik" "Tomonidan koʻrilgan" + "Hisobni tanlang" "Yubirish" "Yuborilmoqda…" "Yuborilmadi" "Yuborilgan" ". " "Server qo\'llab-quvvatlanmaydi" + "Serverga kirish imkonsiz" "Server URL manzili" "Sozlamalar" "Joylashuvi ulashildi" "Chiqish" "Nimadir xato ketdi" "Muammoga duch keldik. Iltimos, qayta urinib koʻring." + "Bo‘shliq" + + "%1$d Guruh" + "%1$d Guruhlar" + "Chat boshlanmoqda…" "Stiker" "Muvaffaqiyat" @@ -310,6 +336,7 @@ Sababi:%1$s." "Taklif(lar)ni yuborib bo‘lmadi" "Qulfni ochish" "Ovozni yoqish" + "Qo‘llab-quvvatlanmaydigan chaqiruv" "Qo\'llab-quvvatlanmagan hodisa" "Foydalanuvchi nomi" "Tasdiqlash bekor qilindi" @@ -318,31 +345,63 @@ Sababi:%1$s." "Tasdiqlangan" "Qurilmani tasdiqlash" "Shaxsni tasdiqlash" + "Foydalanuvchini tasdiqlang" "Video" + "Yuqori sifatli" + "Eng yaxshi sifat, lekin kattaroq fayl hajmi" + "Past sifat" + "Eng tez yuklash tezligi va eng kichik fayl hajmi" + "Standart sifat" + "Sifat va yuklash tezligi balansi" "Ovozli xabar" "Kutilmoqda…" "Ushbu xabarni kutilmoqda" "Siz" "%1$sning shaxsi qayta tiklandi.%2$s" + "%1$sʼning %2$s shaxsiy ma’lumotlari qayta tiklandi.%3$s" "(%1$s )" + "%1$sning shaxsi qayta tiklandi." + "%1$sʼning %2$s shaxsiy ma’lumotlari qayta o‘rnatildi.%3$s" + "Tasdiqlashni bekor qilish" + "%1$s havolasi sizni boshqa %2$s saytiga olib boradi + +Davom etasizmi?" + "Ushbu havolani ikki marta tekshiring" + "Yuklagan videolaringizning standart sifatini tanlang." + "Video yuklash sifati" + "Ruxsat etilgan maksimal fayl hajmi: %1$s" + "Fayl hajmi yuklash uchun juda katta" + "Xona haqida xabar" + "Xabar berildi va xona tark etildi" "Tasdiqlash" "Xato" "Muvaffaqiyat" "Ogohlantirish" + "Sizda saqlanmagan oʻzgarishlar bor" "Oʻzgarishlar saqlanmadi. Haqiqatan ham orqaga qaytmoqchimisiz?" "O‘zgartirishlarni saqlaysizmi?" + "Ruxsat etilgan maksimal fayl hajmi: %1$s" + "Yuklanadigan video sifatini tanlang." + "Video yuklash sifatini tanlang" + "Emojilarni qidiring" + "Bu qurilmada allaqachon %1$s hisobiga kirgansiz." "Matrix autentifikatsiya xizmati va hisob yaratish imkoniyatini qo‘llab-quvvatlash uchun uy serveringizni yangilash talab etiladi." "Doimiy havola yaratilmadi" "%1$sxaritani yuklay olmadi. Iltimos keyinroq qayta urinib ko\'ring." "Xabarlar yuklanmadi" "%1$sjoylashuvingizga kira olmadi. Iltimos keyinroq qayta urinib ko\'ring." "Ovozli xabaringizni yuklashda xatolik roʻy berdi." + "Xona endi mavjud emas yoki taklif yaroqsiz." "Xabar topilmadi" "%1$sjoylashuvingizga kirishga ruxsati yo\'q. Sozlamalar orqali kirishni yoqishingiz mumkin." "%1$sjoylashuvingizga kirishga ruxsati yo\'q. Quyida kirishni yoqing." "%1$smikrofoningizga kirish ruxsatiga ega emas. Ovozli xabar yozish uchun ruxsatni yoqing." + "Bu tarmoq yoki server muammolari bilan bog‘liq bo‘lishi mumkin." + "Bu xona manzili allaqachon mavjud. Xona manzili maydonini tahrirlang yoki xona nomini o‘zgartiring" + "Ayrim belgilarga ruxsat berilmagan. Faqat harf, raqam va quyidagi belgilar ishlaydi! $ &’() * + /; =? @ [ ] -. _" "Bazi xabarlar yuborilmagan" "Kechirasiz, xatolik yuz berdi" + "Hodisani yuborgan shaxs uni yuborgan qurilmaning egasi bilan mos kelmaydi." "Bu qurilmada shifrlangan xabarning haqiqiyligini kafolatlash imkonsiz." "Avval tasdiqlangan foydalanuvchi tomonidan shifrlangan." "Shifrlanmagan" @@ -353,6 +412,11 @@ Sababi:%1$s." "Hey, men bilan gaplash%1$s :%2$s" "%1$sAndroid" "Xato haqida xabar berish uchun G\'azablanish" + "Skrinshot" + "%1$s:%2$s" + "Parametrlar" + "%1$sni olib tashlash" + "Sozlamalar" "Media tanlash jarayonida xatolik yuz berdi, qayta urinib ko\'ring" "Xabarni bosib, bu yerga kiritish uchun \"%1$s\"-ni tanlang." "Muhim xabarlarni osongina topish uchun qadang" @@ -371,8 +435,13 @@ Sababi:%1$s." "%1$s barcha qurilmalarni tasdiqlamagani uchun xabaringiz yuborilmadi" "Bir yoki bir nechta qurilmangiz tasdiqlanmagan. Xabarni istalgancha yuborishingiz yoki hozircha bekor qilishingiz va barcha qurilmalaringizni tasdiqlaganingizdan keyin qayta urinishingiz mumkin." "Xabaringiz yuborilmadi, chunki bir yoki bir nechta qurilmangizni tasdiqlamagansiz" + "Administratorlar yoki egalarni tahrirlash" "Mediani yuklab bo‘lmadi, qayta urinib ko‘ring." "Foydalanuvchi tafsilotlarini olinmadi" + "Xabar %1$sda" + "Kengaytirish" + "Kamaytirish" + "Bu xona allaqachon ko‘rilmoqda!" "%1$sʼdan %2$s" "%1$s ta qadalgan xabar" "Xabar yuklanmoqda…" @@ -384,11 +453,18 @@ Sababi:%1$s." "Google Mapsda oching" "OpenStreetMapda oching" "Bu joylashuvni ulashing" + "Siz yaratgan yoki qo‘shilgan guruhlar." + "%1$s•%2$s" + "Bo‘shliqlar" "Xabar yuborilmadi, chunki %1$sʼning tasdiqlangan identifikatori asliga qaytarildi." "Xabar yuborilmadi, chunki %1$s barcha qurilmalarni tasdiqlamagan." "Xabaringiz yuborilmadi, chunki siz bir yoki bir nechta qurilmangizni tasdiqlamagan ekansiz." "Joylashuv" "Versiya:%1$s (%2$s )" "en" + "Bu qurilmada tarixiy xabarlar mavjud emas" + "Tarixiy xabarlarga kirish uchun bu qurilmani tasdiqlashingiz kerak" "Sizni ushbu xabarga ruxsatingiz yoʻq" + "Xabarni shifrini ochib bo‘lmadi" + "Bu xabar bloklandi, chunki siz qurilmangizni tasdiqlamadingiz yoki yuboruvchi shaxsingizni tasdiqlashi kerak bo‘lgani sababli bloklandi" diff --git a/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml b/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml index 346195863e..046d7f36ba 100644 --- a/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml +++ b/libraries/ui-strings/src/main/res/values-zh-rTW/translations.xml @@ -78,6 +78,7 @@ "拒絕" "拒絕並封鎖" "刪除投票" + "取消全選" "停用" "捨棄" "關閉" @@ -92,6 +93,8 @@ "忘記密碼?" "轉寄" "返回" + "前往角色與權限" + "前往設定" "忽略" "邀請" "邀請夥伴" @@ -108,6 +111,7 @@ "管理帳號" "管理裝置" "聊天" + "最小化" "下一步" "否" "以後再說" @@ -136,6 +140,7 @@ "再次嘗試解密" "儲存" "搜尋" + "選取全部" "傳送" "傳送已編輯的訊息" "傳送訊息" @@ -149,6 +154,7 @@ "略過" "開始" "開始聊天" + "重新開始" "開始驗證" "點擊以載入地圖" "拍照" @@ -170,10 +176,11 @@ "進階設定" "影像" "分析" - "您離開了聊天室" + "您離開聊天室" "您已登出工作階段" "外觀" "音訊" + "測試版" "封鎖的使用者" "泡泡" "開始通話" @@ -222,6 +229,7 @@ "安裝 APK" "找不到此 Matrix ID,因此可能沒有人會收到邀請。" "正在離開聊天室" + "離開空間" "淺色" "行已複製到剪貼簿" "連結已複製到剪貼簿" @@ -242,6 +250,7 @@ "%1$s (%2$s)" "查無結果" "無聊天室名稱" + "沒有空間名稱" "未加密" "離線" "開放原始碼授權條款" @@ -302,7 +311,9 @@ "無法連線至伺服器" "伺服器 URL" "設定" + "分享空間" "位置分享" + "共享空間" "正在登出" "有錯誤發生" "我們了遇到了問題。請再試一次。" @@ -370,6 +381,7 @@ "錯誤" "成功" "警告" + "您有尚未儲存的變更" "變更尚未儲存,您確定要返回嗎?" "是否儲存變更?" "最大允許的檔案大小為:%1$s" @@ -446,7 +458,9 @@ "分享這個位置" "您建立或加入的空間" "%1$s • %2$s" + "%1$s 空間" "空間" + "檢視成員" "因為 %1$s 的驗證身份已重設,因此未傳送訊息。" "訊息未傳送,因為 %1$s 尚未驗證所有裝置。" "因為您尚未驗證一個或多個裝置,因此未傳送訊息" diff --git a/libraries/ui-strings/src/main/res/values-zh/translations.xml b/libraries/ui-strings/src/main/res/values-zh/translations.xml index a4144eb8f0..774a3d3f89 100644 --- a/libraries/ui-strings/src/main/res/values-zh/translations.xml +++ b/libraries/ui-strings/src/main/res/values-zh/translations.xml @@ -93,6 +93,8 @@ "忘记密码?" "转发" "返回" + "前往角色与权限" + "前往设置" "忽略" "邀请" "邀请朋友" @@ -152,6 +154,7 @@ "跳过" "开始" "开始聊天" + "重新开始" "开始验证" "点击以加载地图" "拍摄照片" @@ -177,6 +180,7 @@ "您已被注销当前会话" "外观" "音频" + "测试版" "已屏蔽用户" "气泡" "通话已开始" @@ -307,7 +311,9 @@ "无法访问服务器" "服务器 URL" "设置" + "共享空间" "共享位置" + "共享空间" "正在登出" "发生了一些错误" "我们遇到了一个问题。请重试。" @@ -375,6 +381,7 @@ "错误" "成功" "警告" + "您有未保存的更改。" "更改尚未保存,确定要返回吗?" "保存更改?" "允许的最大文件大小为:%1$s" @@ -451,7 +458,9 @@ "分享这个位置" "您创建或加入的空间。" "%1$s • %2$s" + "%1$s空间" "空间" + "查看成员" "消息未发送,因为%1$s的已验证身份已被重置。" "消息未发送,因为%1$s尚未验证所有设备。" "消息未发送,因为您有尚未验证的设备。" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 2484b0be64..df47b71577 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -95,6 +95,7 @@ "Forgot password?" "Forward" "Go back" + "Go to roles & permissions" "Go to settings" "Ignore" "Invite" @@ -155,10 +156,12 @@ "Skip" "Start" "Start chat" + "Start over" "Start verification" "Tap to load map" "Take photo" "Tap for options" + "Translate" "Try again" "Unpin" "View" @@ -233,6 +236,7 @@ Reason: %1$s." "Light" "Line copied to clipboard" "Link copied to clipboard" + "Link new device" "Loading…" "Loading more…" @@ -245,10 +249,12 @@ Reason: %1$s." "Message" "Message actions" + "Message failed to send" "Message layout" "Message removed" "Modern" "Mute" + "Name" "%1$s (%2$s)" "No results" "No room name" @@ -323,6 +329,7 @@ Reason: %1$s." "Something went wrong" "We encountered an issue. Please try again." "Space" + "What is this space about?" "%1$d Space" "%1$d Spaces" @@ -367,6 +374,7 @@ Reason: %1$s." "Waiting…" "Waiting for this message" "You" + "This room has been configured so that new members can read history. %1$s" "%1$s\'s identity was reset. %2$s" "%1$s’s %2$s identity was reset. %3$s" "(%1$s)" @@ -387,6 +395,7 @@ Are you sure you want to continue?" "Error" "Success" "Warning" + "You have unsaved changes." "Your changes have not been saved. Are you sure you want to go back?" "Save changes?" "The max file size allowed is: %1$s" @@ -426,6 +435,11 @@ Are you sure you want to continue?" "Options" "Remove %1$s" "Settings" + "Spaces where members can join the room without an invitation." + "Manage spaces" + "(Unknown space)" + "Other spaces you’re not a member of" + "Your spaces" "Failed selecting media, please try again." "Press on a message and choose “%1$s” to include here." "Pin important messages so that they can be easily discovered" @@ -466,6 +480,7 @@ Are you sure you want to continue?" "%1$s • %2$s" "%1$s space" "Spaces" + "View members" "Message not sent because %1$s’s verified identity was reset." "Message not sent because %1$s has not verified all devices." "Message not sent because you have not verified one or more of your devices." diff --git a/libraries/ui-utils/build.gradle.kts b/libraries/ui-utils/build.gradle.kts index 95ce3d21a1..a0144fbc27 100644 --- a/libraries/ui-utils/build.gradle.kts +++ b/libraries/ui-utils/build.gradle.kts @@ -1,9 +1,10 @@ import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,11 +14,11 @@ plugins { android { namespace = "io.element.android.libraries.ui.utils" - - dependencies { - implementation(projects.libraries.androidutils) - implementation(projects.services.toolbox.impl) - - testCommonDependencies(libs) - } +} + +dependencies { + implementation(projects.libraries.androidutils) + implementation(projects.services.toolbox.impl) + + testCommonDependencies(libs) } diff --git a/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/MultipleTapToUnlock.kt b/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/MultipleTapToUnlock.kt index 15a8b82eee..dee131979c 100644 --- a/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/MultipleTapToUnlock.kt +++ b/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/MultipleTapToUnlock.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/formatter/FIleSizeFormatter.kt b/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/formatter/FIleSizeFormatter.kt index 3be7a438f3..da67e48b0e 100644 --- a/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/formatter/FIleSizeFormatter.kt +++ b/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/formatter/FIleSizeFormatter.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/time/DurationExt.kt b/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/time/DurationExt.kt index 4b4a547e4d..0ccbaceb9e 100644 --- a/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/time/DurationExt.kt +++ b/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/time/DurationExt.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/time/IsTalkbackEnabled.kt b/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/time/IsTalkbackEnabled.kt index 40b0a3cc76..60ac1887c6 100644 --- a/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/time/IsTalkbackEnabled.kt +++ b/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/time/IsTalkbackEnabled.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/time/KeyEventExt.kt b/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/time/KeyEventExt.kt index 580807f987..ba69daded7 100644 --- a/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/time/KeyEventExt.kt +++ b/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/time/KeyEventExt.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/version/LocalSdkIntVersionProvider.kt b/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/version/LocalSdkIntVersionProvider.kt index d1dc5a5eb7..88e17c9859 100644 --- a/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/version/LocalSdkIntVersionProvider.kt +++ b/libraries/ui-utils/src/main/kotlin/io/element/android/libraries/ui/utils/version/LocalSdkIntVersionProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/ui-utils/src/test/kotlin/io/element/android/libraries/ui/utils/MultipleTapToUnlockTest.kt b/libraries/ui-utils/src/test/kotlin/io/element/android/libraries/ui/utils/MultipleTapToUnlockTest.kt index 1745f617d8..73b91a84ce 100644 --- a/libraries/ui-utils/src/test/kotlin/io/element/android/libraries/ui/utils/MultipleTapToUnlockTest.kt +++ b/libraries/ui-utils/src/test/kotlin/io/element/android/libraries/ui/utils/MultipleTapToUnlockTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/ui-utils/src/test/kotlin/io/element/android/libraries/ui/utils/time/DurationFormatTest.kt b/libraries/ui-utils/src/test/kotlin/io/element/android/libraries/ui/utils/time/DurationFormatTest.kt index 49022ae0a5..d4c007d7e6 100644 --- a/libraries/ui-utils/src/test/kotlin/io/element/android/libraries/ui/utils/time/DurationFormatTest.kt +++ b/libraries/ui-utils/src/test/kotlin/io/element/android/libraries/ui/utils/time/DurationFormatTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/usersearch/api/build.gradle.kts b/libraries/usersearch/api/build.gradle.kts index 1a6567f0f4..978a5c0bae 100644 --- a/libraries/usersearch/api/build.gradle.kts +++ b/libraries/usersearch/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserListDataSource.kt b/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserListDataSource.kt index 17f2136c1f..0f7e02c7a8 100644 --- a/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserListDataSource.kt +++ b/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserListDataSource.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserRepository.kt b/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserRepository.kt index 3af6bab425..f7e5367a9a 100644 --- a/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserRepository.kt +++ b/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserRepository.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserSearchResult.kt b/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserSearchResult.kt index b60b161b90..30da8cf92e 100644 --- a/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserSearchResult.kt +++ b/libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserSearchResult.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/usersearch/impl/build.gradle.kts b/libraries/usersearch/impl/build.gradle.kts index c7378aa07a..bd8eb47cd1 100644 --- a/libraries/usersearch/impl/build.gradle.kts +++ b/libraries/usersearch/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/usersearch/impl/src/main/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSource.kt b/libraries/usersearch/impl/src/main/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSource.kt index 2d968cfb20..3d4331869e 100644 --- a/libraries/usersearch/impl/src/main/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSource.kt +++ b/libraries/usersearch/impl/src/main/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSource.kt @@ -1,14 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.usersearch.impl import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.UserId @@ -16,7 +16,6 @@ import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.usersearch.api.UserListDataSource @ContributesBinding(SessionScope::class) -@Inject class MatrixUserListDataSource( private val client: MatrixClient ) : UserListDataSource { diff --git a/libraries/usersearch/impl/src/main/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepository.kt b/libraries/usersearch/impl/src/main/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepository.kt index c486c898e1..2b6456b77d 100644 --- a/libraries/usersearch/impl/src/main/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepository.kt +++ b/libraries/usersearch/impl/src/main/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepository.kt @@ -1,14 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.usersearch.impl import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.MatrixPatterns @@ -23,7 +23,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @ContributesBinding(SessionScope::class) -@Inject class MatrixUserRepository( private val client: MatrixClient, private val dataSource: UserListDataSource diff --git a/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSourceTest.kt b/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSourceTest.kt index 43dcb30836..149f7d9e12 100644 --- a/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSourceTest.kt +++ b/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSourceTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepositoryTest.kt b/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepositoryTest.kt index abefa0251f..29ee1cb22f 100644 --- a/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepositoryTest.kt +++ b/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepositoryTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/usersearch/test/build.gradle.kts b/libraries/usersearch/test/build.gradle.kts index 5d8694e918..21cc140a7d 100644 --- a/libraries/usersearch/test/build.gradle.kts +++ b/libraries/usersearch/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/usersearch/test/src/main/kotlin/io/element/android/libraries/usersearch/test/FakeUserListDataSource.kt b/libraries/usersearch/test/src/main/kotlin/io/element/android/libraries/usersearch/test/FakeUserListDataSource.kt index d049c038f3..8838bfcaca 100644 --- a/libraries/usersearch/test/src/main/kotlin/io/element/android/libraries/usersearch/test/FakeUserListDataSource.kt +++ b/libraries/usersearch/test/src/main/kotlin/io/element/android/libraries/usersearch/test/FakeUserListDataSource.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/usersearch/test/src/main/kotlin/io/element/android/libraries/usersearch/test/FakeUserRepository.kt b/libraries/usersearch/test/src/main/kotlin/io/element/android/libraries/usersearch/test/FakeUserRepository.kt index 262a2e4a42..a767e4dc0a 100644 --- a/libraries/usersearch/test/src/main/kotlin/io/element/android/libraries/usersearch/test/FakeUserRepository.kt +++ b/libraries/usersearch/test/src/main/kotlin/io/element/android/libraries/usersearch/test/FakeUserRepository.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voiceplayer/api/build.gradle.kts b/libraries/voiceplayer/api/build.gradle.kts index 4490dbbe11..f37c263d83 100644 --- a/libraries/voiceplayer/api/build.gradle.kts +++ b/libraries/voiceplayer/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageEvents.kt b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageEvents.kt index 7e5430a566..01910ddcd6 100644 --- a/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageEvents.kt +++ b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageEvents.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageException.kt b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageException.kt index a3a610c370..b3ee8e8fd1 100644 --- a/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageException.kt +++ b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageException.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessagePresenterFactory.kt b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessagePresenterFactory.kt index eba0087615..0229ed991f 100644 --- a/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessagePresenterFactory.kt +++ b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessagePresenterFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageState.kt b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageState.kt index 522b5cfbe2..a66209c5db 100644 --- a/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageState.kt +++ b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageStateProvider.kt b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageStateProvider.kt index bfc2f144ab..d8ccb85832 100644 --- a/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageStateProvider.kt +++ b/libraries/voiceplayer/api/src/main/kotlin/io/element/android/libraries/voiceplayer/api/VoiceMessageStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voiceplayer/impl/build.gradle.kts b/libraries/voiceplayer/impl/build.gradle.kts index 053914d86b..679e8206d9 100644 --- a/libraries/voiceplayer/impl/build.gradle.kts +++ b/libraries/voiceplayer/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessagePresenterFactory.kt b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessagePresenterFactory.kt index f109f3d49a..b2518eaca0 100644 --- a/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessagePresenterFactory.kt +++ b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessagePresenterFactory.kt @@ -1,14 +1,14 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.voiceplayer.impl import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.annotations.SessionCoroutineScope @@ -21,7 +21,6 @@ import kotlinx.coroutines.CoroutineScope import kotlin.time.Duration @ContributesBinding(RoomScope::class) -@Inject class DefaultVoiceMessagePresenterFactory( private val analyticsService: AnalyticsService, @SessionCoroutineScope diff --git a/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessageMediaRepo.kt b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessageMediaRepo.kt index a1f4de2087..310120c9fe 100644 --- a/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessageMediaRepo.kt +++ b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessageMediaRepo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePlayer.kt b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePlayer.kt index 51a1163e36..510b80bae6 100644 --- a/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePlayer.kt +++ b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePlayer.kt @@ -1,14 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.voiceplayer.impl import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.extensions.mapCatchingExceptions import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.di.RoomScope @@ -124,8 +124,7 @@ class DefaultVoiceMessagePlayer( filename: String?, ) : VoiceMessagePlayer { @ContributesBinding(RoomScope::class) // Scoped types can't use @Inject. - @Inject -class Factory( + class Factory( private val mediaPlayer: MediaPlayer, private val voiceMessageMediaRepoFactory: VoiceMessageMediaRepo.Factory, ) : VoiceMessagePlayer.Factory { diff --git a/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePresenter.kt b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePresenter.kt index 9637c5e587..089e7fa533 100644 --- a/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePresenter.kt +++ b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -87,7 +88,7 @@ class VoiceMessagePresenter( } } - fun eventSink(event: VoiceMessageEvents) { + fun handleEvent(event: VoiceMessageEvents) { when (event) { is VoiceMessageEvents.PlayPause -> { if (playerState.isPlaying) { @@ -130,7 +131,7 @@ class VoiceMessagePresenter( time = time, showCursor = showCursor, playbackSpeed = playbackSpeed.value, - eventSink = { eventSink(it) }, + eventSink = ::handleEvent, ) } } diff --git a/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessageMediaRepoTest.kt b/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessageMediaRepoTest.kt index 411b9863eb..5df259fb5b 100644 --- a/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessageMediaRepoTest.kt +++ b/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessageMediaRepoTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,8 +12,8 @@ import com.google.common.truth.Truth.assertThat import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.media.MediaSource -import io.element.android.libraries.matrix.api.mxc.MxcTools import io.element.android.libraries.matrix.test.media.FakeMatrixMediaLoader +import io.element.android.libraries.matrix.test.mxc.FakeMxcTools import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -130,7 +131,7 @@ private fun createDefaultVoiceMessageMediaRepo( mxcUri: String = MXC_URI, ) = DefaultVoiceMessageMediaRepo( cacheDir = temporaryFolder.root, - mxcTools = MxcTools(), + mxcTools = FakeMxcTools(), matrixMediaLoader = matrixMediaLoader, mediaSource = MediaSource( url = mxcUri, diff --git a/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessagePlayerTest.kt b/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessagePlayerTest.kt index cd8a3f6c6b..d4005067fe 100644 --- a/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessagePlayerTest.kt +++ b/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/DefaultVoiceMessagePlayerTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -114,7 +115,8 @@ class DefaultVoiceMessagePlayerTest { assertThat(player1.prepare().isSuccess).isTrue() matchReadyState(1_000L) player1.play() - awaitItem().let { // it plays until the end. + awaitItem().let { + // it plays until the end. assertThat(it.isReady).isFalse() assertThat(it.isPlaying).isFalse() assertThat(it.isEnded).isTrue() @@ -127,14 +129,16 @@ class DefaultVoiceMessagePlayerTest { player2.state.test { matchInitialState() assertThat(player2.prepare().isSuccess).isTrue() - awaitItem().let { // Additional spurious state due to MediaPlayer owner change. + awaitItem().let { + // Additional spurious state due to MediaPlayer owner change. assertThat(it.isReady).isFalse() assertThat(it.isPlaying).isFalse() assertThat(it.isEnded).isTrue() assertThat(it.currentPosition).isEqualTo(1000) assertThat(it.duration).isEqualTo(1000) } - awaitItem().let { // Additional spurious state due to MediaPlayer owner change. + awaitItem().let { + // Additional spurious state due to MediaPlayer owner change. assertThat(it.isReady).isFalse() assertThat(it.isPlaying).isFalse() assertThat(it.isEnded).isFalse() @@ -143,7 +147,8 @@ class DefaultVoiceMessagePlayerTest { } matchReadyState(1_000L) player2.play() - awaitItem().let { // it plays until the end. + awaitItem().let { + // it plays until the end. assertThat(it.isReady).isFalse() assertThat(it.isPlaying).isFalse() assertThat(it.isEnded).isTrue() @@ -154,7 +159,8 @@ class DefaultVoiceMessagePlayerTest { // Play player1 again. player1.state.test { - awaitItem().let { // Last previous state/ + awaitItem().let { + // Last previous state/ assertThat(it.isReady).isFalse() assertThat(it.isPlaying).isFalse() assertThat(it.isEnded).isTrue() @@ -162,7 +168,8 @@ class DefaultVoiceMessagePlayerTest { assertThat(it.duration).isEqualTo(1000) } assertThat(player1.prepare().isSuccess).isTrue() - awaitItem().let { // Additional spurious state due to MediaPlayer owner change. + awaitItem().let { + // Additional spurious state due to MediaPlayer owner change. assertThat(it.isReady).isFalse() assertThat(it.isPlaying).isFalse() assertThat(it.isEnded).isFalse() @@ -171,7 +178,8 @@ class DefaultVoiceMessagePlayerTest { } matchReadyState(1_000L) player1.play() - awaitItem().let { // it played again until the end. + awaitItem().let { + // it played again until the end. assertThat(it.isReady).isFalse() assertThat(it.isPlaying).isFalse() assertThat(it.isEnded).isTrue() @@ -189,7 +197,8 @@ class DefaultVoiceMessagePlayerTest { assertThat(player.prepare().isSuccess).isTrue() matchReadyState() player.play() - skipItems(1) // skip play state + // skip play state + skipItems(1) player.pause() awaitItem().let { assertThat(it.isPlaying).isFalse() @@ -206,9 +215,11 @@ class DefaultVoiceMessagePlayerTest { assertThat(player.prepare().isSuccess).isTrue() matchReadyState() player.play() - skipItems(1) // skip play state + // skip play state + skipItems(1) player.pause() - skipItems(1) // skip pause state + // skip pause state + skipItems(1) player.play() awaitItem().let { assertThat(it.isPlaying).isTrue() diff --git a/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/FakeVoiceMessageMediaRepo.kt b/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/FakeVoiceMessageMediaRepo.kt index 78975cf7a6..e94f475fbf 100644 --- a/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/FakeVoiceMessageMediaRepo.kt +++ b/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/FakeVoiceMessageMediaRepo.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePresenterTest.kt b/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePresenterTest.kt index 5f0b34b45e..fe680422f6 100644 --- a/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePresenterTest.kt +++ b/libraries/voiceplayer/impl/src/test/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/api/build.gradle.kts b/libraries/voicerecorder/api/build.gradle.kts index b2be9d5aba..6490762248 100644 --- a/libraries/voicerecorder/api/build.gradle.kts +++ b/libraries/voicerecorder/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/voicerecorder/api/src/main/kotlin/io/element/android/libraries/voicerecorder/api/VoiceRecorder.kt b/libraries/voicerecorder/api/src/main/kotlin/io/element/android/libraries/voicerecorder/api/VoiceRecorder.kt index 0fee2da38d..3427f694b3 100644 --- a/libraries/voicerecorder/api/src/main/kotlin/io/element/android/libraries/voicerecorder/api/VoiceRecorder.kt +++ b/libraries/voicerecorder/api/src/main/kotlin/io/element/android/libraries/voicerecorder/api/VoiceRecorder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/api/src/main/kotlin/io/element/android/libraries/voicerecorder/api/VoiceRecorderState.kt b/libraries/voicerecorder/api/src/main/kotlin/io/element/android/libraries/voicerecorder/api/VoiceRecorderState.kt index cc4dc0238c..0a6840658f 100644 --- a/libraries/voicerecorder/api/src/main/kotlin/io/element/android/libraries/voicerecorder/api/VoiceRecorderState.kt +++ b/libraries/voicerecorder/api/src/main/kotlin/io/element/android/libraries/voicerecorder/api/VoiceRecorderState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -22,16 +23,19 @@ sealed interface VoiceRecorderState { * The recorder is currently recording. * * @property elapsedTime The elapsed time since the recording started. - * @property levels The current audio levels of the recording as a fraction of 1. + * @property levels The current audio levels of the recording as a fraction of 1. All values are between 0 and 1. */ - data class Recording(val elapsedTime: Duration, val levels: List) : VoiceRecorderState + data class Recording( + val elapsedTime: Duration, + val levels: List, + ) : VoiceRecorderState /** * The recorder has finished recording. * * @property file The recorded file. * @property mimeType The mime type of the file. - * @property waveform The waveform of the recording. + * @property waveform The waveform of the recording. All values are between 0 and 1. * @property duration The total time spent recording. */ data class Finished( diff --git a/libraries/voicerecorder/impl/build.gradle.kts b/libraries/voicerecorder/impl/build.gradle.kts index a09106a8c3..f737ca16d9 100644 --- a/libraries/voicerecorder/impl/build.gradle.kts +++ b/libraries/voicerecorder/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/DefaultVoiceRecorder.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/DefaultVoiceRecorder.kt index 624282edef..5e4c9ae132 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/DefaultVoiceRecorder.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/DefaultVoiceRecorder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,7 +11,6 @@ package io.element.android.libraries.voicerecorder.impl import android.Manifest import androidx.annotation.RequiresPermission import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.appconfig.VoiceMessageConfig import io.element.android.libraries.core.coroutine.CoroutineDispatchers @@ -43,7 +43,6 @@ import kotlin.time.TimeSource @SingleIn(RoomScope::class) @ContributesBinding(RoomScope::class) -@Inject class DefaultVoiceRecorder( private val dispatchers: CoroutineDispatchers, private val timeSource: TimeSource, @@ -63,6 +62,8 @@ class DefaultVoiceRecorder( private var outputFile: File? = null private var audioReader: AudioReader? = null private var recordingJob: Job? = null + + // List of Float between 0 and 1 representing the audio levels private val levels: MutableList = mutableListOf() private val lock = Mutex() diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AndroidAudioReader.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AndroidAudioReader.kt index cc8a7bbe29..c4686dc312 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AndroidAudioReader.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AndroidAudioReader.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/Audio.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/Audio.kt index 0677875703..522fe9f59f 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/Audio.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/Audio.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AudioConfig.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AudioConfig.kt index c2b9352936..350e3f94d3 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AudioConfig.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AudioConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AudioLevelCalculator.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AudioLevelCalculator.kt index d58ca3e4e2..2db448acf5 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AudioLevelCalculator.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AudioLevelCalculator.kt @@ -1,12 +1,15 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.voicerecorder.impl.audio +import androidx.annotation.FloatRange + interface AudioLevelCalculator { /** * Calculate the audio level of the audio buffer. @@ -14,5 +17,6 @@ interface AudioLevelCalculator { * @param buffer The audio buffer containing 16bit PCM audio data. * @return A float value between 0 and 1 proportional to the audio level. */ + @FloatRange(from = 0.0, to = 1.0) fun calculateAudioLevel(buffer: ShortArray): Float } diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AudioReader.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AudioReader.kt index 8af3b55252..9f336991d8 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AudioReader.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/AudioReader.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DBovAudioLevelCalculator.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DBovAudioLevelCalculator.kt index 6001e74615..12cd42c652 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DBovAudioLevelCalculator.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DBovAudioLevelCalculator.kt @@ -1,14 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.voicerecorder.impl.audio import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.RoomScope import kotlin.math.log10 import kotlin.math.sqrt @@ -20,7 +20,6 @@ import kotlin.math.sqrt * See: https://en.wikipedia.org/wiki/DBFS */ @ContributesBinding(RoomScope::class) -@Inject class DBovAudioLevelCalculator : AudioLevelCalculator { override fun calculateAudioLevel(buffer: ShortArray): Float { return buffer.rms().dBov().normalize().coerceIn(0f, 1f) diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DefaultEncoder.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DefaultEncoder.kt index ef02a45160..9d471f7a6d 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DefaultEncoder.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DefaultEncoder.kt @@ -1,14 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.voicerecorder.impl.audio import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Provider import io.element.android.libraries.di.RoomScope import io.element.android.opusencoder.OggOpusEncoder @@ -19,7 +19,6 @@ import java.io.File * Safe wrapper for OggOpusEncoder. */ @ContributesBinding(RoomScope::class) -@Inject class DefaultEncoder( private val encoderProvider: Provider, config: AudioConfig, diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/Encoder.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/Encoder.kt index 9a7e11d8f5..0ceb04259c 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/Encoder.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/Encoder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/Resample.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/Resample.kt index e2705865de..a8bc81ed5a 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/Resample.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/Resample.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/SampleRate.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/SampleRate.kt index c055e97ec0..c3bfd7ca6d 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/SampleRate.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/audio/SampleRate.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/di/VoiceRecorderModule.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/di/VoiceRecorderModule.kt index 917ef14415..ee9f7bfa9d 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/di/VoiceRecorderModule.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/di/VoiceRecorderModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/file/DefaultVoiceFileManager.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/file/DefaultVoiceFileManager.kt index 768233cfe8..316d003842 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/file/DefaultVoiceFileManager.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/file/DefaultVoiceFileManager.kt @@ -1,14 +1,14 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.voicerecorder.impl.file import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.hash.md5 import io.element.android.libraries.di.CacheDirectory import io.element.android.libraries.di.RoomScope @@ -18,7 +18,6 @@ import java.io.File import java.util.UUID @ContributesBinding(RoomScope::class) -@Inject class DefaultVoiceFileManager( @CacheDirectory private val cacheDir: File, private val config: VoiceFileConfig, diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/file/VoiceFileConfig.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/file/VoiceFileConfig.kt index 3eb519dab1..8b5d778514 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/file/VoiceFileConfig.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/file/VoiceFileConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/file/VoiceFileManager.kt b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/file/VoiceFileManager.kt index 255cfd809f..81897e0e56 100644 --- a/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/file/VoiceFileManager.kt +++ b/libraries/voicerecorder/impl/src/main/kotlin/io/element/android/libraries/voicerecorder/impl/file/VoiceFileManager.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/DefaultVoiceRecorderTest.kt b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/DefaultVoiceRecorderTest.kt index 6ed540a2f4..e3ff6f9198 100644 --- a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/DefaultVoiceRecorderTest.kt +++ b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/DefaultVoiceRecorderTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DBovAudioLevelCalculatorTest.kt b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DBovAudioLevelCalculatorTest.kt index 50ccc03c8c..7fd8c6ed7b 100644 --- a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DBovAudioLevelCalculatorTest.kt +++ b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/audio/DBovAudioLevelCalculatorTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/audio/ResampleTest.kt b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/audio/ResampleTest.kt index 72f327ec38..91780802d6 100644 --- a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/audio/ResampleTest.kt +++ b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/impl/audio/ResampleTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeAudioLevelCalculator.kt b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeAudioLevelCalculator.kt index 522d17961c..48e12c3192 100644 --- a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeAudioLevelCalculator.kt +++ b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeAudioLevelCalculator.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeAudioReader.kt b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeAudioReader.kt index e18a577184..b14bb400e5 100644 --- a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeAudioReader.kt +++ b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeAudioReader.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeAudioReaderFactory.kt b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeAudioReaderFactory.kt index c899f52d43..d4bd241aaa 100644 --- a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeAudioReaderFactory.kt +++ b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeAudioReaderFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeEncoder.kt b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeEncoder.kt index dcd52d042b..70a6c8b9c0 100644 --- a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeEncoder.kt +++ b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeEncoder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeFileSystem.kt b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeFileSystem.kt index 8f6f0e4815..b69f019626 100644 --- a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeFileSystem.kt +++ b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeFileSystem.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeVoiceFileManager.kt b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeVoiceFileManager.kt index c86305e71f..29bb0aae8b 100644 --- a/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeVoiceFileManager.kt +++ b/libraries/voicerecorder/impl/src/test/kotlin/io/element/android/libraries/voicerecorder/test/FakeVoiceFileManager.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/test/build.gradle.kts b/libraries/voicerecorder/test/build.gradle.kts index b1f9bed3ba..c0f74b71e8 100644 --- a/libraries/voicerecorder/test/build.gradle.kts +++ b/libraries/voicerecorder/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/voicerecorder/test/src/main/kotlin/io/element/android/libraries/voicerecorder/test/FakeVoiceRecorder.kt b/libraries/voicerecorder/test/src/main/kotlin/io/element/android/libraries/voicerecorder/test/FakeVoiceRecorder.kt index 351ab63dd1..ec62227857 100644 --- a/libraries/voicerecorder/test/src/main/kotlin/io/element/android/libraries/voicerecorder/test/FakeVoiceRecorder.kt +++ b/libraries/voicerecorder/test/src/main/kotlin/io/element/android/libraries/voicerecorder/test/FakeVoiceRecorder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,6 +14,7 @@ import io.element.android.libraries.voicerecorder.api.VoiceRecorder import io.element.android.libraries.voicerecorder.api.VoiceRecorderState import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.yield import java.io.File import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -48,6 +50,7 @@ class FakeVoiceRecorder( timeSource += recordingDuration for (i in 1..levels.size) { _state.emit(VoiceRecorderState.Recording(startedAt.elapsedNow(), levels.take(i))) + yield() } } diff --git a/libraries/wellknown/api/build.gradle.kts b/libraries/wellknown/api/build.gradle.kts index 3a805e38c8..768214d874 100644 --- a/libraries/wellknown/api/build.gradle.kts +++ b/libraries/wellknown/api/build.gradle.kts @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnown.kt b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnown.kt index 064416eec1..7dc230d2e1 100644 --- a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnown.kt +++ b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnown.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,4 +12,5 @@ data class ElementWellKnown( val registrationHelperUrl: String?, val enforceElementPro: Boolean?, val rageshakeUrl: String?, + val brandColor: String?, ) diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/SessionWellknownRetriever.kt b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/SessionWellknownRetriever.kt index 7f7b9b983f..b006508fc7 100644 --- a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/SessionWellknownRetriever.kt +++ b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/SessionWellknownRetriever.kt @@ -1,13 +1,13 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.wellknown.api interface SessionWellknownRetriever { - suspend fun getWellKnown(): WellKnown? - suspend fun getElementWellKnown(): ElementWellKnown? + suspend fun getElementWellKnown(): WellknownRetrieverResult } diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellKnown.kt b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellKnown.kt deleted file mode 100644 index 59f63d1655..0000000000 --- a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellKnown.kt +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - * Please see LICENSE files in the repository root for full details. - */ - -package io.element.android.libraries.wellknown.api - -data class WellKnown( - val homeServer: WellKnownBaseConfig?, - val identityServer: WellKnownBaseConfig?, -) - -data class WellKnownBaseConfig( - val baseURL: String? -) diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetriever.kt b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetriever.kt index e617bc8e13..f7b73de8e5 100644 --- a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetriever.kt +++ b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetriever.kt @@ -1,13 +1,13 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.wellknown.api interface WellknownRetriever { - suspend fun getWellKnown(baseUrl: String): WellKnown? - suspend fun getElementWellKnown(baseUrl: String): ElementWellKnown? + suspend fun getElementWellKnown(baseUrl: String): WellknownRetrieverResult } diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetrieverResult.kt b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetrieverResult.kt new file mode 100644 index 0000000000..3236f18660 --- /dev/null +++ b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/WellknownRetrieverResult.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.wellknown.api + +sealed interface WellknownRetrieverResult { + /** + * Well-known data has been successfully retrieved. + */ + data class Success(val data: T) : WellknownRetrieverResult + + /** + * Well-known data is not found (file does not exist server side, we got a 404). + */ + data object NotFound : WellknownRetrieverResult + + /** + * Any other error. + */ + data class Error(val exception: Exception) : WellknownRetrieverResult + + fun dataOrNull(): T? = when (this) { + is Success -> data + is Error -> null + NotFound -> null + } +} diff --git a/libraries/wellknown/impl/build.gradle.kts b/libraries/wellknown/impl/build.gradle.kts index 4de8ecc0fd..f803eeec3c 100644 --- a/libraries/wellknown/impl/build.gradle.kts +++ b/libraries/wellknown/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -27,8 +28,9 @@ dependencies { implementation(platform(libs.network.retrofit.bom)) implementation(libs.network.retrofit) implementation(libs.serialization.json) - implementation(projects.libraries.architecture) implementation(projects.libraries.core) + implementation(projects.libraries.androidutils) + implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) implementation(projects.libraries.network) diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetriever.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetriever.kt index 81bbab5ab8..3bcf9bf573 100644 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetriever.kt +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetriever.kt @@ -1,54 +1,54 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.wellknown.impl import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject +import io.element.android.libraries.androidutils.json.JsonProvider import io.element.android.libraries.core.extensions.mapCatchingExceptions import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.exception.ClientException import io.element.android.libraries.wellknown.api.ElementWellKnown import io.element.android.libraries.wellknown.api.SessionWellknownRetriever -import io.element.android.libraries.wellknown.api.WellKnown -import kotlinx.serialization.json.Json +import io.element.android.libraries.wellknown.api.WellknownRetrieverResult import timber.log.Timber @ContributesBinding(SessionScope::class) -@Inject class DefaultSessionWellknownRetriever( private val matrixClient: MatrixClient, - private val parser: Json, + private val json: JsonProvider, ) : SessionWellknownRetriever { private val domain by lazy { matrixClient.userIdServerName() } - override suspend fun getWellKnown(): WellKnown? { - val url = "https://$domain/.well-known/matrix/client" - return matrixClient - .getUrl(url) - .mapCatchingExceptions { - val data = String(it) - parser.decodeFromString(InternalWellKnown.serializer(), data) - } - .onFailure { Timber.e(it, "Failed to retrieve .well-known from $domain") } - .map { it.map() } - .getOrNull() - } - - override suspend fun getElementWellKnown(): ElementWellKnown? { + override suspend fun getElementWellKnown(): WellknownRetrieverResult { val url = "https://$domain/.well-known/element/element.json" return matrixClient .getUrl(url) .mapCatchingExceptions { val data = String(it) - parser.decodeFromString(InternalElementWellKnown.serializer(), data) + json().decodeFromString(data).map() } - .onFailure { Timber.e(it, "Failed to retrieve Element .well-known from $domain") } - .map { it.map() } - .getOrNull() + .toWellknownRetrieverResult() } + + private fun Result.toWellknownRetrieverResult(): WellknownRetrieverResult = fold( + onSuccess = { + WellknownRetrieverResult.Success(it) + }, + onFailure = { + Timber.e(it, "Failed to retrieve Element .well-known from $domain") + // This check on message value is not ideal but this is what we got from the SDK. + if ((it as? ClientException.Generic)?.message?.contains("404") == true) { + WellknownRetrieverResult.NotFound + } else { + WellknownRetrieverResult.Error(it as Exception) + } + } + ) } diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetriever.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetriever.kt index aa0e28e85a..02dae72a43 100644 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetriever.kt +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetriever.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,47 +10,49 @@ package io.element.android.libraries.wellknown.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.network.RetrofitFactory import io.element.android.libraries.wellknown.api.ElementWellKnown -import io.element.android.libraries.wellknown.api.WellKnown import io.element.android.libraries.wellknown.api.WellknownRetriever +import io.element.android.libraries.wellknown.api.WellknownRetrieverResult +import retrofit2.HttpException import timber.log.Timber +import java.net.HttpURLConnection @ContributesBinding(AppScope::class) -@Inject class DefaultWellknownRetriever( private val retrofitFactory: RetrofitFactory, ) : WellknownRetriever { - override suspend fun getWellKnown(baseUrl: String): WellKnown? { - val wellknownApi = buildWellknownApi(baseUrl) ?: return null - return try { - wellknownApi.getWellKnown().map() - } catch (e: Exception) { - Timber.e(e, "Failed to retrieve well-known data for $baseUrl") - null - } + override suspend fun getElementWellKnown(baseUrl: String): WellknownRetrieverResult { + return buildWellknownApi(baseUrl) + .map { wellknownApi -> + try { + val result = wellknownApi.getElementWellKnown().map() + WellknownRetrieverResult.Success(result) + } catch (e: Exception) { + // Is it a 404? + Timber.e(e, "Failed to retrieve Element well-known data for $baseUrl") + if ((e as? HttpException)?.code() == HttpURLConnection.HTTP_NOT_FOUND) { + WellknownRetrieverResult.NotFound + } else { + WellknownRetrieverResult.Error(e) + } + } + } + .fold( + onSuccess = { it }, + onFailure = { WellknownRetrieverResult.Error(it as Exception) } + ) } - override suspend fun getElementWellKnown(baseUrl: String): ElementWellKnown? { - val wellknownApi = buildWellknownApi(baseUrl) ?: return null - return try { - wellknownApi.getElementWellKnown().map() - } catch (e: Exception) { - Timber.e(e, "Failed to retrieve Element well-known data for $baseUrl") - null - } - } - - private fun buildWellknownApi(accountProviderUrl: String): WellknownAPI? { - return try { + private fun buildWellknownApi(accountProviderUrl: String): Result { + return runCatchingExceptions { retrofitFactory.create(accountProviderUrl.ensureProtocol()) .create(WellknownAPI::class.java) - } catch (e: Exception) { + }.onFailure { e -> // If the base URL is not valid, we cannot retrieve the well-known data Timber.e(e, "Failed to create Retrofit instance for $accountProviderUrl") - null } } } diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalElementWellKnown.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalElementWellKnown.kt index e81d78d498..c79d8fa4c3 100644 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalElementWellKnown.kt +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalElementWellKnown.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -27,4 +28,6 @@ data class InternalElementWellKnown( val enforceElementPro: Boolean? = null, @SerialName("rageshake_url") val rageshakeUrl: String? = null, + @SerialName("brand_color") + val brandColor: String? = null, ) diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnown.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnown.kt index 8915359771..bf43356c45 100644 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnown.kt +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnown.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnownBaseConfig.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnownBaseConfig.kt index 76f1a2af4e..342cd43395 100644 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnownBaseConfig.kt +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalWellKnownBaseConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/Mapper.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/Mapper.kt index 9c1618f699..d3f57806b3 100644 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/Mapper.kt +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/Mapper.kt @@ -1,27 +1,18 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.wellknown.impl import io.element.android.libraries.wellknown.api.ElementWellKnown -import io.element.android.libraries.wellknown.api.WellKnown -import io.element.android.libraries.wellknown.api.WellKnownBaseConfig internal fun InternalElementWellKnown.map() = ElementWellKnown( registrationHelperUrl = registrationHelperUrl, enforceElementPro = enforceElementPro, rageshakeUrl = rageshakeUrl, -) - -internal fun InternalWellKnown.map() = WellKnown( - homeServer = homeServer?.map(), - identityServer = identityServer?.map(), -) - -internal fun InternalWellKnownBaseConfig.map() = WellKnownBaseConfig( - baseURL = baseURL, + brandColor = brandColor, ) diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/WellknownAPI.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/WellknownAPI.kt index d109aa7a43..54528f7c42 100644 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/WellknownAPI.kt +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/WellknownAPI.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetrieverTest.kt b/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetrieverTest.kt index 624370466a..4e384dbc0d 100644 --- a/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetrieverTest.kt +++ b/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetrieverTest.kt @@ -1,153 +1,25 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.libraries.wellknown.impl import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.androidutils.json.DefaultJsonProvider import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.wellknown.api.ElementWellKnown -import io.element.android.libraries.wellknown.api.WellKnown -import io.element.android.libraries.wellknown.api.WellKnownBaseConfig +import io.element.android.libraries.wellknown.api.WellknownRetrieverResult import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import kotlinx.coroutines.test.runTest -import kotlinx.serialization.json.Json import org.junit.Test class DefaultSessionWellknownRetrieverTest { - @Test - fun `get empty wellknown`() = runTest { - val getUrlLambda = lambdaRecorder> { - Result.success("{}".toByteArray()) - } - val sut = createDefaultSessionWellknownRetriever( - getUrlLambda = getUrlLambda, - ) - assertThat(sut.getWellKnown()).isEqualTo( - WellKnown( - homeServer = null, - identityServer = null, - ) - ) - getUrlLambda.assertions().isCalledOnce() - .with(value("https://user.domain.org/.well-known/matrix/client")) - } - - @Test - fun `get wellknown with full content`() = runTest { - val sut = createDefaultSessionWellknownRetriever( - getUrlLambda = { - Result.success( - """{ - "m.homeserver": { - "base_url": "https://example.org" - }, - "m.identity_server": { - "base_url": "https://identity.example.org" - } - }""".trimIndent().toByteArray() - ) - } - ) - assertThat(sut.getWellKnown()).isEqualTo( - WellKnown( - homeServer = WellKnownBaseConfig( - baseURL = "https://example.org", - ), - identityServer = WellKnownBaseConfig( - baseURL = "https://identity.example.org", - ), - ) - ) - } - - @Test - fun `get wellknown with full content empty base_url`() = runTest { - val sut = createDefaultSessionWellknownRetriever( - getUrlLambda = { - Result.success( - """{ - "m.homeserver": { - "base_url": "https://example.org" - }, - "m.identity_server": {} - }""".trimIndent().toByteArray() - ) - } - ) - assertThat(sut.getWellKnown()).isEqualTo( - WellKnown( - homeServer = WellKnownBaseConfig( - baseURL = "https://example.org", - ), - identityServer = WellKnownBaseConfig( - baseURL = null, - ), - ) - ) - } - - @Test - fun `get wellknown with unknown key`() = runTest { - val sut = createDefaultSessionWellknownRetriever( - getUrlLambda = { - Result.success( - """{ - "m.homeserver": { - "base_url": "https://example.org" - }, - "m.identity_server": { - "base_url": "https://identity.example.org" - }, - "other": true - }""".trimIndent().toByteArray() - ) - } - ) - assertThat(sut.getWellKnown()).isEqualTo( - WellKnown( - homeServer = WellKnownBaseConfig( - baseURL = "https://example.org", - ), - identityServer = WellKnownBaseConfig( - baseURL = "https://identity.example.org", - ), - ) - ) - } - - @Test - fun `get wellknown json error`() = runTest { - val sut = createDefaultSessionWellknownRetriever( - getUrlLambda = { - Result.success( - """{ - "m.homeserver": { - "base_url": "https://example.org" - }, - error - }""".trimIndent().toByteArray() - ) - } - ) - assertThat(sut.getWellKnown()).isNull() - } - - @Test - fun `get wellknown network error`() = runTest { - val sut = createDefaultSessionWellknownRetriever( - getUrlLambda = { - Result.failure(AN_EXCEPTION) - } - ) - assertThat(sut.getWellKnown()).isNull() - } - @Test fun `get empty element wellknown`() = runTest { val getUrlLambda = lambdaRecorder> { @@ -157,10 +29,13 @@ class DefaultSessionWellknownRetrieverTest { getUrlLambda = getUrlLambda, ) assertThat(sut.getElementWellKnown()).isEqualTo( - ElementWellKnown( - registrationHelperUrl = null, - enforceElementPro = null, - rageshakeUrl = null, + WellknownRetrieverResult.Success( + ElementWellKnown( + registrationHelperUrl = null, + enforceElementPro = null, + rageshakeUrl = null, + brandColor = null, + ) ) ) getUrlLambda.assertions().isCalledOnce() @@ -175,16 +50,20 @@ class DefaultSessionWellknownRetrieverTest { """{ "registration_helper_url": "a_registration_url", "enforce_element_pro": true, - "rageshake_url": "a_rageshake_url" + "rageshake_url": "a_rageshake_url", + "brand_color": "#FF0000" }""".trimIndent().toByteArray() ) } ) assertThat(sut.getElementWellKnown()).isEqualTo( - ElementWellKnown( - registrationHelperUrl = "a_registration_url", - enforceElementPro = true, - rageshakeUrl = "a_rageshake_url", + WellknownRetrieverResult.Success( + ElementWellKnown( + registrationHelperUrl = "a_registration_url", + enforceElementPro = true, + rageshakeUrl = "a_rageshake_url", + brandColor = "#FF0000", + ) ) ) } @@ -201,13 +80,16 @@ class DefaultSessionWellknownRetrieverTest { "other": true }""".trimIndent().toByteArray() ) - } + }, ) assertThat(sut.getElementWellKnown()).isEqualTo( - ElementWellKnown( - registrationHelperUrl = "a_registration_url", - enforceElementPro = true, - rageshakeUrl = "a_rageshake_url", + WellknownRetrieverResult.Success( + ElementWellKnown( + registrationHelperUrl = "a_registration_url", + enforceElementPro = true, + rageshakeUrl = "a_rageshake_url", + brandColor = null, + ) ) ) } @@ -224,7 +106,7 @@ class DefaultSessionWellknownRetrieverTest { ) } ) - assertThat(sut.getElementWellKnown()).isNull() + assertThat(sut.getElementWellKnown()).isInstanceOf(WellknownRetrieverResult.Error::class.java) } @Test @@ -234,7 +116,7 @@ class DefaultSessionWellknownRetrieverTest { Result.failure(AN_EXCEPTION) } ) - assertThat(sut.getElementWellKnown()).isNull() + assertThat(sut.getElementWellKnown()).isInstanceOf(WellknownRetrieverResult.Error::class.java) } private fun createDefaultSessionWellknownRetriever( @@ -244,6 +126,6 @@ class DefaultSessionWellknownRetrieverTest { userIdServerNameLambda = { "user.domain.org" }, getUrlLambda = getUrlLambda, ), - parser = Json { ignoreUnknownKeys = true } + json = DefaultJsonProvider(), ) } diff --git a/libraries/wellknown/test/build.gradle.kts b/libraries/wellknown/test/build.gradle.kts index 7c7f4a368b..0ce22348b3 100644 --- a/libraries/wellknown/test/build.gradle.kts +++ b/libraries/wellknown/test/build.gradle.kts @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeSessionWellknownRetriever.kt b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeSessionWellknownRetriever.kt index 6c2c141622..3c675beed2 100644 --- a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeSessionWellknownRetriever.kt +++ b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeSessionWellknownRetriever.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,18 +10,13 @@ package io.element.android.features.wellknown.test import io.element.android.libraries.wellknown.api.ElementWellKnown import io.element.android.libraries.wellknown.api.SessionWellknownRetriever -import io.element.android.libraries.wellknown.api.WellKnown +import io.element.android.libraries.wellknown.api.WellknownRetrieverResult import io.element.android.tests.testutils.simulateLongTask class FakeSessionWellknownRetriever( - private val getWellKnownResult: () -> WellKnown? = { null }, - private val getElementWellKnownResult: () -> ElementWellKnown? = { null }, + private val getElementWellKnownResult: () -> WellknownRetrieverResult = { WellknownRetrieverResult.NotFound }, ) : SessionWellknownRetriever { - override suspend fun getWellKnown(): WellKnown? = simulateLongTask { - getWellKnownResult() - } - - override suspend fun getElementWellKnown(): ElementWellKnown? = simulateLongTask { + override suspend fun getElementWellKnown(): WellknownRetrieverResult = simulateLongTask { getElementWellKnownResult() } } diff --git a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetriever.kt b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetriever.kt index c8bbbde26d..139332667e 100644 --- a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetriever.kt +++ b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/FakeWellknownRetriever.kt @@ -1,26 +1,22 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.features.wellknown.test import io.element.android.libraries.wellknown.api.ElementWellKnown -import io.element.android.libraries.wellknown.api.WellKnown import io.element.android.libraries.wellknown.api.WellknownRetriever +import io.element.android.libraries.wellknown.api.WellknownRetrieverResult import io.element.android.tests.testutils.simulateLongTask class FakeWellknownRetriever( - private val getWellKnownResult: (String) -> WellKnown? = { null }, - private val getElementWellKnownResult: (String) -> ElementWellKnown? = { null }, + private val getElementWellKnownResult: (String) -> WellknownRetrieverResult = { WellknownRetrieverResult.NotFound }, ) : WellknownRetriever { - override suspend fun getWellKnown(baseUrl: String): WellKnown? = simulateLongTask { - getWellKnownResult(baseUrl) - } - - override suspend fun getElementWellKnown(baseUrl: String): ElementWellKnown? = simulateLongTask { + override suspend fun getElementWellKnown(baseUrl: String): WellknownRetrieverResult = simulateLongTask { getElementWellKnownResult(baseUrl) } } diff --git a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/Fixtures.kt b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/Fixtures.kt index 686026b78d..b5fd9f6020 100644 --- a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/Fixtures.kt +++ b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/Fixtures.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -13,8 +14,10 @@ fun anElementWellKnown( registrationHelperUrl: String? = null, enforceElementPro: Boolean? = null, rageshakeUrl: String? = null, + brandColor: String? = null, ) = ElementWellKnown( registrationHelperUrl = registrationHelperUrl, enforceElementPro = enforceElementPro, rageshakeUrl = rageshakeUrl, + brandColor = brandColor, ) diff --git a/libraries/workmanager/api/build.gradle.kts b/libraries/workmanager/api/build.gradle.kts new file mode 100644 index 0000000000..b53ed40394 --- /dev/null +++ b/libraries/workmanager/api/build.gradle.kts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.workmanager.api" +} + +dependencies { + api(libs.androidx.workmanager.runtime) + + implementation(projects.libraries.matrix.api) +} diff --git a/libraries/workmanager/api/src/main/kotlin/io/element/android/libraries/workmanager/api/WorkManagerRequest.kt b/libraries/workmanager/api/src/main/kotlin/io/element/android/libraries/workmanager/api/WorkManagerRequest.kt new file mode 100644 index 0000000000..af49c3dc87 --- /dev/null +++ b/libraries/workmanager/api/src/main/kotlin/io/element/android/libraries/workmanager/api/WorkManagerRequest.kt @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.workmanager.api + +import androidx.work.WorkRequest + +interface WorkManagerRequest { + fun build(): Result> +} diff --git a/libraries/workmanager/api/src/main/kotlin/io/element/android/libraries/workmanager/api/WorkManagerScheduler.kt b/libraries/workmanager/api/src/main/kotlin/io/element/android/libraries/workmanager/api/WorkManagerScheduler.kt new file mode 100644 index 0000000000..b538486d35 --- /dev/null +++ b/libraries/workmanager/api/src/main/kotlin/io/element/android/libraries/workmanager/api/WorkManagerScheduler.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.workmanager.api + +import io.element.android.libraries.matrix.api.core.SessionId + +interface WorkManagerScheduler { + fun submit(workManagerRequest: WorkManagerRequest) + fun hasPendingWork(sessionId: SessionId, requestType: WorkManagerRequestType): Boolean + fun cancel(sessionId: SessionId) +} + +fun workManagerTag(sessionId: SessionId, requestType: WorkManagerRequestType): String { + val prefix = when (requestType) { + WorkManagerRequestType.NOTIFICATION_SYNC -> "notifications" + WorkManagerRequestType.DB_VACUUM -> "db_vacuum" + } + return "$prefix-$sessionId" +} + +enum class WorkManagerRequestType { + NOTIFICATION_SYNC, + DB_VACUUM, +} diff --git a/libraries/workmanager/api/src/main/kotlin/io/element/android/libraries/workmanager/api/di/MetroWorkerFactory.kt b/libraries/workmanager/api/src/main/kotlin/io/element/android/libraries/workmanager/api/di/MetroWorkerFactory.kt new file mode 100644 index 0000000000..086d3baafa --- /dev/null +++ b/libraries/workmanager/api/src/main/kotlin/io/element/android/libraries/workmanager/api/di/MetroWorkerFactory.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.workmanager.api.di + +import android.content.Context +import androidx.work.ListenableWorker +import androidx.work.WorkerFactory +import androidx.work.WorkerParameters +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import kotlin.reflect.KClass + +@ContributesBinding(AppScope::class) +class MetroWorkerFactory( + val workerProviders: Map, WorkerInstanceFactory<*>> +) : WorkerFactory() { + override fun createWorker( + appContext: Context, + workerClassName: String, + workerParameters: WorkerParameters, + ): ListenableWorker? { + return workerProviders[Class.forName(workerClassName).kotlin]?.create(workerParameters) + } + + interface WorkerInstanceFactory { + fun create(params: WorkerParameters): T + } +} diff --git a/libraries/workmanager/api/src/main/kotlin/io/element/android/libraries/workmanager/api/di/WorkerKey.kt b/libraries/workmanager/api/src/main/kotlin/io/element/android/libraries/workmanager/api/di/WorkerKey.kt new file mode 100644 index 0000000000..7202408da5 --- /dev/null +++ b/libraries/workmanager/api/src/main/kotlin/io/element/android/libraries/workmanager/api/di/WorkerKey.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.workmanager.api.di + +import androidx.work.ListenableWorker +import dev.zacsweers.metro.MapKey +import kotlin.reflect.KClass + +/** A [MapKey] annotation for binding Worker in a multibinding map. */ +@MapKey +@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.RUNTIME) +annotation class WorkerKey(val value: KClass) diff --git a/libraries/workmanager/impl/build.gradle.kts b/libraries/workmanager/impl/build.gradle.kts new file mode 100644 index 0000000000..878edb6fe2 --- /dev/null +++ b/libraries/workmanager/impl/build.gradle.kts @@ -0,0 +1,29 @@ +import extension.setupDependencyInjection +import extension.testCommonDependencies + +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.workmanager.impl" +} + +setupDependencyInjection() + +dependencies { + api(projects.libraries.workmanager.api) + implementation(projects.libraries.core) + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.di) + + testCommonDependencies(libs, false) + testImplementation(projects.libraries.sessionStorage.test) +} diff --git a/libraries/workmanager/impl/src/main/kotlin/io/element/android/libraries/workmanager/impl/DefaultWorkManagerScheduler.kt b/libraries/workmanager/impl/src/main/kotlin/io/element/android/libraries/workmanager/impl/DefaultWorkManagerScheduler.kt new file mode 100644 index 0000000000..4f3806db62 --- /dev/null +++ b/libraries/workmanager/impl/src/main/kotlin/io/element/android/libraries/workmanager/impl/DefaultWorkManagerScheduler.kt @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.workmanager.impl + +import androidx.work.WorkInfo +import androidx.work.WorkManager +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.SingleIn +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.sessionstorage.api.observer.SessionListener +import io.element.android.libraries.sessionstorage.api.observer.SessionObserver +import io.element.android.libraries.workmanager.api.WorkManagerRequest +import io.element.android.libraries.workmanager.api.WorkManagerRequestType +import io.element.android.libraries.workmanager.api.WorkManagerScheduler +import io.element.android.libraries.workmanager.api.workManagerTag +import timber.log.Timber + +@ContributesBinding(AppScope::class) +@SingleIn(AppScope::class) +class DefaultWorkManagerScheduler( + lazyWorkManager: Lazy, + sessionObserver: SessionObserver, +) : WorkManagerScheduler { + private val workManager by lazyWorkManager + + init { + // Observe session removals to cancel associated work automatically + sessionObserver.addListener(object : SessionListener { + override suspend fun onSessionDeleted(userId: String, wasLastSession: Boolean) { + val sessionId = SessionId(userId) + Timber.d("Session deleted for userId: $userId, cancelling associated workmanager requests") + cancel(sessionId) + } + }) + } + + override fun submit(workManagerRequest: WorkManagerRequest) { + workManagerRequest.build().fold( + onSuccess = { workRequests -> + workManager.enqueue(workRequests) + }, + onFailure = { + Timber.e(it, "Failed to build WorkManager request $workManagerRequest") + } + ) + } + + override fun hasPendingWork(sessionId: SessionId, requestType: WorkManagerRequestType): Boolean { + val workInfos = workManager.getWorkInfosByTag(workManagerTag(sessionId, requestType)).get().orEmpty() + return workInfos.any { info -> + val isPeriodic = info.periodicityInfo != null + val isCancelled = info.state == WorkInfo.State.CANCELLED + // It has pending work if: + // - It's not periodic and is not finished. + // - It's periodic and is not cancelled - since it'll be run again in a next iteration otherwise + !isPeriodic && !info.state.isFinished || isPeriodic && !isCancelled + } + } + + override fun cancel(sessionId: SessionId) { + Timber.d("Cancelling work for sessionId: $sessionId") + for (requestType in WorkManagerRequestType.entries) { + workManager.cancelAllWorkByTag(workManagerTag(sessionId, requestType)) + } + } +} diff --git a/libraries/workmanager/impl/src/main/kotlin/io/element/android/libraries/workmanager/impl/WorkManagerModule.kt b/libraries/workmanager/impl/src/main/kotlin/io/element/android/libraries/workmanager/impl/WorkManagerModule.kt new file mode 100644 index 0000000000..7df9e7f431 --- /dev/null +++ b/libraries/workmanager/impl/src/main/kotlin/io/element/android/libraries/workmanager/impl/WorkManagerModule.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.workmanager.impl + +import android.content.Context +import androidx.work.WorkManager +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.BindingContainer +import dev.zacsweers.metro.ContributesTo +import dev.zacsweers.metro.Provides +import dev.zacsweers.metro.SingleIn +import io.element.android.libraries.di.annotations.ApplicationContext + +@BindingContainer +@ContributesTo(AppScope::class) +object WorkManagerModule { + @Provides + @SingleIn(AppScope::class) + fun providesWorkManager( + @ApplicationContext context: Context, + ): WorkManager { + return WorkManager.getInstance(context) + } +} diff --git a/libraries/workmanager/impl/src/test/kotlin/io/element/android/libraries/workmanager/impl/DefaultWorkManagerSchedulerTest.kt b/libraries/workmanager/impl/src/test/kotlin/io/element/android/libraries/workmanager/impl/DefaultWorkManagerSchedulerTest.kt new file mode 100644 index 0000000000..b4f964ed12 --- /dev/null +++ b/libraries/workmanager/impl/src/test/kotlin/io/element/android/libraries/workmanager/impl/DefaultWorkManagerSchedulerTest.kt @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.workmanager.impl + +import androidx.work.WorkManager +import androidx.work.WorkRequest +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.sessionstorage.test.observer.FakeSessionObserver +import io.element.android.libraries.workmanager.api.WorkManagerRequest +import io.element.android.libraries.workmanager.api.WorkManagerRequestType +import io.element.android.libraries.workmanager.api.workManagerTag +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class DefaultWorkManagerSchedulerTest { + @Test + fun `starts observing sessions on init to remove work for logged out sessions`() = runTest { + val sessionId = "@session1:matrix.org" + val sessionObserver = FakeSessionObserver() + + val workManager = spyk() + + DefaultWorkManagerScheduler( + lazyWorkManager = lazy { workManager }, + sessionObserver = sessionObserver, + ) + + // We remove the session + sessionObserver.onSessionDeleted(sessionId) + + runCurrent() + + // The session is now gone and work associated with the session is cancelled + verify { workManager.cancelAllWorkByTag("notifications-$sessionId") } + } + + @Test + fun `submit builds the request and enqueues it`() = runTest { + val workManager = spyk() + + val scheduler = DefaultWorkManagerScheduler( + lazyWorkManager = lazy { workManager }, + sessionObserver = FakeSessionObserver(), + ) + + scheduler.submit(FakeWorkManagerRequest()) + + verify { workManager.enqueue(any>()) } + } + + @Test + fun `submit won't do anything if building the work request fails`() = runTest { + val workManager = spyk() + + val scheduler = DefaultWorkManagerScheduler( + lazyWorkManager = lazy { workManager }, + sessionObserver = FakeSessionObserver(), + ) + + scheduler.submit(FakeWorkManagerRequest(result = Result.failure(IllegalStateException("Test error")))) + + verify(exactly = 0) { workManager.enqueue(any>()) } + } + + @Test + fun `cancel will cancel all pending work for a session id`() = runTest { + val workManager = spyk() + + val scheduler = DefaultWorkManagerScheduler( + lazyWorkManager = lazy { workManager }, + sessionObserver = FakeSessionObserver(), + ) + + val sessionId = SessionId("@alice:matrix.org") + val tagToRemove = workManagerTag(sessionId, WorkManagerRequestType.NOTIFICATION_SYNC) + val mockSessionA = mockk { + every { tags } returns setOf(tagToRemove) + } + scheduler.submit(FakeWorkManagerRequest(result = Result.success(listOf(mockSessionA)))) + + scheduler.cancel(sessionId) + + verify { workManager.cancelAllWorkByTag(tagToRemove) } + } +} + +private class FakeWorkManagerRequest( + private val result: Result> = Result.success(listOf()), +) : WorkManagerRequest { + override fun build(): Result> { + return result + } +} diff --git a/libraries/workmanager/test/build.gradle.kts b/libraries/workmanager/test/build.gradle.kts new file mode 100644 index 0000000000..9885bdc55e --- /dev/null +++ b/libraries/workmanager/test/build.gradle.kts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.workmanager.test" +} + +dependencies { + api(projects.libraries.workmanager.api) + implementation(projects.libraries.matrix.api) + implementation(projects.tests.testutils) +} diff --git a/libraries/workmanager/test/src/main/kotlin/io/element/android/libraries/workmanager/test/FakeWorkManagerScheduler.kt b/libraries/workmanager/test/src/main/kotlin/io/element/android/libraries/workmanager/test/FakeWorkManagerScheduler.kt new file mode 100644 index 0000000000..f2caa8c743 --- /dev/null +++ b/libraries/workmanager/test/src/main/kotlin/io/element/android/libraries/workmanager/test/FakeWorkManagerScheduler.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.workmanager.test + +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.workmanager.api.WorkManagerRequest +import io.element.android.libraries.workmanager.api.WorkManagerRequestType +import io.element.android.libraries.workmanager.api.WorkManagerScheduler +import io.element.android.tests.testutils.lambda.lambdaError + +class FakeWorkManagerScheduler( + private val submitLambda: (WorkManagerRequest) -> Unit = { lambdaError() }, + private val hasPendingWorkLambda: (SessionId, WorkManagerRequestType) -> Boolean = { _, _ -> false }, + private val cancelLambda: (SessionId) -> Unit = { lambdaError() }, +) : WorkManagerScheduler { + override fun submit(workManagerRequest: WorkManagerRequest) { + submitLambda(workManagerRequest) + } + + override fun hasPendingWork(sessionId: SessionId, requestType: WorkManagerRequestType): Boolean { + return hasPendingWorkLambda(sessionId, requestType) + } + + override fun cancel(sessionId: SessionId) { + cancelLambda(sessionId) + } +} diff --git a/plugins/build.gradle.kts b/plugins/build.gradle.kts index e40d1f5bbd..eb2f56e19e 100644 --- a/plugins/build.gradle.kts +++ b/plugins/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/plugins/settings.gradle.kts b/plugins/settings.gradle.kts index 40327da593..7684f058de 100644 --- a/plugins/settings.gradle.kts +++ b/plugins/settings.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/plugins/src/main/kotlin/Enterprise.kt b/plugins/src/main/kotlin/Enterprise.kt index b006595526..ab0185d07d 100644 --- a/plugins/src/main/kotlin/Enterprise.kt +++ b/plugins/src/main/kotlin/Enterprise.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/plugins/src/main/kotlin/Logger.kt b/plugins/src/main/kotlin/Logger.kt index f449a47dd4..96f6dd6720 100644 --- a/plugins/src/main/kotlin/Logger.kt +++ b/plugins/src/main/kotlin/Logger.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/plugins/src/main/kotlin/ModulesConfig.kt b/plugins/src/main/kotlin/ModulesConfig.kt index 57379a7fd6..b5360b42ca 100644 --- a/plugins/src/main/kotlin/ModulesConfig.kt +++ b/plugins/src/main/kotlin/ModulesConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/plugins/src/main/kotlin/Versions.kt b/plugins/src/main/kotlin/Versions.kt index 52c09434d4..f00099753c 100644 --- a/plugins/src/main/kotlin/Versions.kt +++ b/plugins/src/main/kotlin/Versions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -38,7 +39,7 @@ private const val versionYear = 25 * Month of the version on 2 digits. Value must be in [1,12]. * Do not update this value. it is updated by the release script. */ -private const val versionMonth = 10 +private const val versionMonth = 12 /** * Release number in the month. Value must be in [0,99]. diff --git a/plugins/src/main/kotlin/config/AnalyticsConfig.kt b/plugins/src/main/kotlin/config/AnalyticsConfig.kt index 43f5fd8966..a485a08592 100644 --- a/plugins/src/main/kotlin/config/AnalyticsConfig.kt +++ b/plugins/src/main/kotlin/config/AnalyticsConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/plugins/src/main/kotlin/config/BuildTimeConfig.kt b/plugins/src/main/kotlin/config/BuildTimeConfig.kt index 0f0d05415c..8cfa533609 100644 --- a/plugins/src/main/kotlin/config/BuildTimeConfig.kt +++ b/plugins/src/main/kotlin/config/BuildTimeConfig.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -28,6 +29,7 @@ object BuildTimeConfig { val SERVICES_POSTHOG_HOST: String? = null val SERVICES_POSTHOG_APIKEY: String? = null val SERVICES_SENTRY_DSN: String? = null + val SERVICES_SENTRY_DSN_RUST: String? = null val BUG_REPORT_URL: String? = null val BUG_REPORT_APP_NAME: String? = null diff --git a/plugins/src/main/kotlin/config/PushProvidersConfig.kt b/plugins/src/main/kotlin/config/PushProvidersConfig.kt index e041f94364..ac36c96707 100644 --- a/plugins/src/main/kotlin/config/PushProvidersConfig.kt +++ b/plugins/src/main/kotlin/config/PushProvidersConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/plugins/src/main/kotlin/extension/AssetCopyTask.kt b/plugins/src/main/kotlin/extension/AssetCopyTask.kt index 16401531f3..c443b127ba 100644 --- a/plugins/src/main/kotlin/extension/AssetCopyTask.kt +++ b/plugins/src/main/kotlin/extension/AssetCopyTask.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/plugins/src/main/kotlin/extension/CommonExtension.kt b/plugins/src/main/kotlin/extension/CommonExtension.kt index 251afafc3e..5f243f4319 100644 --- a/plugins/src/main/kotlin/extension/CommonExtension.kt +++ b/plugins/src/main/kotlin/extension/CommonExtension.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index d46c54e1c3..5a4c4f9aeb 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -89,6 +90,7 @@ fun DependencyHandlerScope.allLibrariesImpl() { implementation(project(":libraries:designsystem")) implementation(project(":libraries:matrix:impl")) implementation(project(":libraries:matrixui")) + implementation(project(":libraries:matrixmedia:impl")) implementation(project(":libraries:network")) implementation(project(":libraries:core")) implementation(project(":libraries:eventformatter:impl")) @@ -118,6 +120,8 @@ fun DependencyHandlerScope.allLibrariesImpl() { implementation(project(":libraries:fullscreenintent:impl")) implementation(project(":libraries:wellknown:impl")) implementation(project(":libraries:oidc:impl")) + implementation(project(":libraries:workmanager:impl")) + implementation(project(":libraries:recentemojis:impl")) } fun DependencyHandlerScope.allServicesImpl() { diff --git a/plugins/src/main/kotlin/extension/DependencyInjectionExtensions.kt b/plugins/src/main/kotlin/extension/DependencyInjectionExtensions.kt index 01895c4000..b9cf87e233 100644 --- a/plugins/src/main/kotlin/extension/DependencyInjectionExtensions.kt +++ b/plugins/src/main/kotlin/extension/DependencyInjectionExtensions.kt @@ -1,12 +1,14 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package extension +import dev.zacsweers.metro.gradle.MetroPluginExtension import org.gradle.accessors.dm.LibrariesForLibs import org.gradle.api.Project import org.gradle.api.provider.Provider @@ -20,11 +22,18 @@ import org.gradle.plugin.use.PluginDependency fun Project.setupDependencyInjection( generateNodeFactories: Boolean = shouldApplyAppyxCodegen(), ) { + if (project.path.endsWith(":api")) { + error("api module should not use setupDependencyInjection(). Move the implementation to `:impl` module") + } + val libs = the() // Apply Metro plugin and configure it applyPluginIfNeeded(libs.plugins.metro) + val metroExtension = extensions.getByName("metro") as MetroPluginExtension + metroExtension.contributesAsInject.value(true) + if (generateNodeFactories) { applyPluginIfNeeded(libs.plugins.ksp) diff --git a/plugins/src/main/kotlin/extension/KoverExtension.kt b/plugins/src/main/kotlin/extension/KoverExtension.kt index 04a647321e..4cb8398a39 100644 --- a/plugins/src/main/kotlin/extension/KoverExtension.kt +++ b/plugins/src/main/kotlin/extension/KoverExtension.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -158,7 +159,6 @@ fun Project.setupKover() { "io.element.android.libraries.designsystem.swipe.SwipeableActionsState", "io.element.android.libraries.designsystem.theme.components.bottomsheet.CustomSheetState", "io.element.android.libraries.maplibre.compose.CameraPositionState", - "io.element.android.libraries.maplibre.compose.SaveableCameraPositionState", "io.element.android.libraries.maplibre.compose.SymbolState", "io.element.android.libraries.matrix.api.room.RoomMembershipState", "io.element.android.libraries.matrix.api.room.RoomMembersState", diff --git a/plugins/src/main/kotlin/extension/Utils.kt b/plugins/src/main/kotlin/extension/Utils.kt index 4d19505704..032aba3fac 100644 --- a/plugins/src/main/kotlin/extension/Utils.kt +++ b/plugins/src/main/kotlin/extension/Utils.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/plugins/src/main/kotlin/extension/VariantDimensionExtension.kt b/plugins/src/main/kotlin/extension/VariantDimensionExtension.kt index a7589f02a9..ee43b88154 100644 --- a/plugins/src/main/kotlin/extension/VariantDimensionExtension.kt +++ b/plugins/src/main/kotlin/extension/VariantDimensionExtension.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/plugins/src/main/kotlin/extension/VersionCatalog.kt b/plugins/src/main/kotlin/extension/VersionCatalog.kt index 4e3f2d677b..a2f4484c0d 100644 --- a/plugins/src/main/kotlin/extension/VersionCatalog.kt +++ b/plugins/src/main/kotlin/extension/VersionCatalog.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/plugins/src/main/kotlin/extension/locales.kt b/plugins/src/main/kotlin/extension/locales.kt index 93431455a5..1af7146681 100644 --- a/plugins/src/main/kotlin/extension/locales.kt +++ b/plugins/src/main/kotlin/extension/locales.kt @@ -12,13 +12,13 @@ val locales = setOf( "el", "en", "en-rUS", - "eo", "es", "et", "eu", "fa", "fi", "fr", + "hr", "hu", "in", "it", diff --git a/plugins/src/main/kotlin/io.element.android-compose-application.gradle.kts b/plugins/src/main/kotlin/io.element.android-compose-application.gradle.kts index 060248d110..d8f754e8e6 100644 --- a/plugins/src/main/kotlin/io.element.android-compose-application.gradle.kts +++ b/plugins/src/main/kotlin/io.element.android-compose-application.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/plugins/src/main/kotlin/io.element.android-compose-library.gradle.kts b/plugins/src/main/kotlin/io.element.android-compose-library.gradle.kts index f1edbca9ae..8a1020a5c3 100644 --- a/plugins/src/main/kotlin/io.element.android-compose-library.gradle.kts +++ b/plugins/src/main/kotlin/io.element.android-compose-library.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/plugins/src/main/kotlin/io.element.android-library.gradle.kts b/plugins/src/main/kotlin/io.element.android-library.gradle.kts index f267f85449..3ee5e2b603 100644 --- a/plugins/src/main/kotlin/io.element.android-library.gradle.kts +++ b/plugins/src/main/kotlin/io.element.android-library.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/screenshots/de/appnav.room.joined_LoadingRoomNodeView_Day_1_de.png b/screenshots/de/appnav.room.joined_LoadingRoomNodeView_Day_1_de.png index ec1170a517..f7a5852846 100644 --- a/screenshots/de/appnav.room.joined_LoadingRoomNodeView_Day_1_de.png +++ b/screenshots/de/appnav.room.joined_LoadingRoomNodeView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ced98c61dd5c65e3ea9037dd6f5989fb9fa3626744ffe02afe7ed21c75645717 -size 13009 +oid sha256:173c488c77b3ba1f0b8190c626e51d12d221e4b85aaa83b3fe5caf3e25fe458d +size 10189 diff --git a/screenshots/de/features.analytics.impl_AnalyticsOptInView_Day_0_de.png b/screenshots/de/features.analytics.impl_AnalyticsOptInView_Day_0_de.png index daaa90702b..fc8f13e941 100644 --- a/screenshots/de/features.analytics.impl_AnalyticsOptInView_Day_0_de.png +++ b/screenshots/de/features.analytics.impl_AnalyticsOptInView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d6ca3832f5bfd853eb8a46f229269292b41deea505946972f6c181d3f916487c -size 85831 +oid sha256:04f10a69293c264deb83f19c3adc4b223e7f722f99154d86dac0743d30eebabe +size 85812 diff --git a/screenshots/de/features.analytics.impl_AnalyticsOptInView_Day_1_de.png b/screenshots/de/features.analytics.impl_AnalyticsOptInView_Day_1_de.png index 7fac6ab0b0..53a3abcafa 100644 --- a/screenshots/de/features.analytics.impl_AnalyticsOptInView_Day_1_de.png +++ b/screenshots/de/features.analytics.impl_AnalyticsOptInView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1ae6e7a291307b66aa36e59bb8a0b0e1e06e030d87a21a134e25e60ab1aa78dd -size 87419 +oid sha256:ab07d3cedec320578a200ac7e428b861198a09e54f68971a274c27c399d840c6 +size 87441 diff --git a/screenshots/de/features.announcement.impl.spaces_SpaceAnnouncementView_Day_0_de.png b/screenshots/de/features.announcement.impl.spaces_SpaceAnnouncementView_Day_0_de.png index 6c1f2b970f..2d59285f58 100644 --- a/screenshots/de/features.announcement.impl.spaces_SpaceAnnouncementView_Day_0_de.png +++ b/screenshots/de/features.announcement.impl.spaces_SpaceAnnouncementView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:064b8f0aae8eefbac4b70f7836bbd41d27954f8c7c580f7f306fb8fad2ee1022 -size 69465 +oid sha256:0996d8aed1bd5cb8f99743adb77ea85ade9d65a02aefcc2032714d1b6d1f131d +size 69468 diff --git a/screenshots/de/features.call.impl.ui_IncomingCallScreen_Day_0_de.png b/screenshots/de/features.call.impl.ui_IncomingCallScreen_Day_0_de.png index b7aad0695a..b690bf51da 100644 --- a/screenshots/de/features.call.impl.ui_IncomingCallScreen_Day_0_de.png +++ b/screenshots/de/features.call.impl.ui_IncomingCallScreen_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4589bdb8f0a2e14cc1588acb0f914f50b459bc4395f22d4a9303447773c59cfe -size 68453 +oid sha256:cde6ab80c5e68ad7f9ce6911405f5abdc52994ad0829a6657bd8abdd7b7be22b +size 68451 diff --git a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_0_de.png b/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_0_de.png deleted file mode 100644 index 60e591d166..0000000000 --- a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_0_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0b45f2dd9fa60e7f72b138e7e82d5a8cdc30467cdc5ea9c0afe09aa54c1686a7 -size 16120 diff --git a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_10_de.png b/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_10_de.png deleted file mode 100644 index 0e4b5b1562..0000000000 --- a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_10_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:12634dceaef537d5431771d900e476409d7efd4cca1d278c21ef424d609e0596 -size 53107 diff --git a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_11_de.png b/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_11_de.png deleted file mode 100644 index 7f1cba04c2..0000000000 --- a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_11_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:10ff5140b95d150a0541cc4d05eb415ccceccf82e797af1cd1074ee387def109 -size 55455 diff --git a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_12_de.png b/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_12_de.png deleted file mode 100644 index a4b949a6da..0000000000 --- a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_12_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c50792d349bfd0cb583ba51f754102a25475375c498941a0d113581274c2b3a4 -size 55478 diff --git a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_1_de.png b/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_1_de.png deleted file mode 100644 index caee19be3f..0000000000 --- a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_1_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:dfaee10346f165f2f27723494d1c2506fbd753e6876f98047059346815940ea5 -size 71902 diff --git a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_2_de.png b/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_2_de.png deleted file mode 100644 index 311b2cd71c..0000000000 --- a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_2_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c505ae703366d52fa9e9b0c0ef723c9fc33b073b8bd84882f3fa5aa31f976de0 -size 65504 diff --git a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_3_de.png b/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_3_de.png deleted file mode 100644 index 6aa02e515a..0000000000 --- a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_3_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6b466cab816ec224a7434ec143bbc086941afd01a5370f7d0f8126af603a1634 -size 65339 diff --git a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_4_de.png b/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_4_de.png deleted file mode 100644 index d351bb2ab9..0000000000 --- a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_4_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:64ffef7fbc235b64797dfd89f96b75323f345115b63530aba97e1be8e75b9458 -size 59221 diff --git a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_6_de.png b/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_6_de.png deleted file mode 100644 index 6739289e2e..0000000000 --- a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_6_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c485a47e58864cfa945c609cc53f3b2875623208f25505af4ac9338728e26f81 -size 66039 diff --git a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_7_de.png b/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_7_de.png deleted file mode 100644 index f2a53e604e..0000000000 --- a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_7_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6fce675e4f044f255c306e917d7473bad6a22c244629b9c5729d08ce3ec7dcd7 -size 66276 diff --git a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_8_de.png b/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_8_de.png deleted file mode 100644 index 2b14561f41..0000000000 --- a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_8_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b822c8431d2e90a1c484b78edf3eda9db70485669dea0638c5f74c286f94cd20 -size 55755 diff --git a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_9_de.png b/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_9_de.png deleted file mode 100644 index dfb0b4f039..0000000000 --- a/screenshots/de/features.changeroommemberroles.impl_ChangeRolesView_Day_9_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:65594c19b36303f28c6580aa1d0861f65483f91fc69dfa97a9744895a1c910f9 -size 67794 diff --git a/screenshots/de/features.changeroommemberroles.impl_PendingMemberRowWithLongName_Day_0_de.png b/screenshots/de/features.changeroommemberroles.impl_PendingMemberRowWithLongName_Day_0_de.png deleted file mode 100644 index fc9c3e2365..0000000000 --- a/screenshots/de/features.changeroommemberroles.impl_PendingMemberRowWithLongName_Day_0_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:353deccaf44203d369a40c95032787afd5946b12b76c8fecde6a8b8028df7631 -size 15419 diff --git a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_0_de.png b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_0_de.png index b44c8aff18..bd0e8752b4 100644 --- a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_0_de.png +++ b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c44e406f4576869def5292961e4201f68e9668d17bf79315e313fccacf58bc76 -size 34201 +oid sha256:bba0a8575b618446b61d69372af6b8c9a87aec8dd925066bea543c9bda4de758 +size 34195 diff --git a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_1_de.png b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_1_de.png index dfc55ad0bf..d996235ee9 100644 --- a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_1_de.png +++ b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef7d027bb08c6c74716f588ab90b41cbf21c8eadbdc5cfabc8c843a82e6647cc -size 40227 +oid sha256:0a95826d38ba306dad54a21fea3f0c3b348bb388ce3f734d084fb36bb689678b +size 40246 diff --git a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_2_de.png b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_2_de.png index 91a7ed7f55..dec36c9974 100644 --- a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_2_de.png +++ b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2e3188c26344223653bd7f8aa2b787d3a2fffebc7e4d9bf1c41d8d911dedac90 -size 61456 +oid sha256:caab417458e9798b4c338de667afc8e8fac9ee8a45168ee60e7e31ddf035f8cd +size 61831 diff --git a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_3_de.png b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_3_de.png index e255d8edc1..411a3ed5b8 100644 --- a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_3_de.png +++ b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:07c35a8d865a5902ffb0c382ca4c8daa04e20865dddd669e5c28f49f68223ebc -size 62218 +oid sha256:83e83f59efcff4f7a1d9b01eee242d3384c512f5a1d0623fa9dbfcfdc73a61e0 +size 62570 diff --git a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_4_de.png b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_4_de.png index 9d08205d11..d666f4e02a 100644 --- a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_4_de.png +++ b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36cfb0c3a753eb56e10384cd2228074d3303bb213fec06e654daf79d2dbaa8ae -size 63630 +oid sha256:8f1ced6eb71c9009b54c408e7617d2b945071e64c88caf6eff10a4c8c482aa9a +size 63985 diff --git a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_5_de.png b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_5_de.png index 91a7ed7f55..dec36c9974 100644 --- a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_5_de.png +++ b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewDark_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2e3188c26344223653bd7f8aa2b787d3a2fffebc7e4d9bf1c41d8d911dedac90 -size 61456 +oid sha256:caab417458e9798b4c338de667afc8e8fac9ee8a45168ee60e7e31ddf035f8cd +size 61831 diff --git a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_0_de.png b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_0_de.png index 99e09224b5..74ba252140 100644 --- a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_0_de.png +++ b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0261e3244d154a88d9544821c3ec3517425553d311b06a7a586c225677ae01f -size 35291 +oid sha256:bf37caa5c17b00d268041c4897b9b148f7d7d4e16dd4c0938c05b6e1b9fd0e84 +size 35289 diff --git a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_1_de.png b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_1_de.png index c855175cb1..32acfffcc8 100644 --- a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_1_de.png +++ b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7dbbfaa3f7113abc3d7b3d2c6451f7b6c20bf3a28f64d7407682c88576c62300 -size 41590 +oid sha256:43586d8414cb23ab363491c57b7c4bb22a95d2bb55b8dc1d1507054a67a69f7f +size 41598 diff --git a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_2_de.png b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_2_de.png index 86dfef35db..726a329f48 100644 --- a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_2_de.png +++ b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2cff0e19fb2f6f09a7173fde3612c55cc2cbe3028c5a8c09ac3efc623bd35da1 -size 63502 +oid sha256:27f2ddc3924cea4dfc4f7ee4b077dc2e9a3fb33122a4f0fb1fade3322f305815 +size 63881 diff --git a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_3_de.png b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_3_de.png index 4fc3002681..cdbed88a65 100644 --- a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_3_de.png +++ b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e4ff4eb745d680311927f55512cc24b04a87b28faf3845ab31b50fcc8749c382 -size 64292 +oid sha256:d2fd3ba9c6bfa6de0cb78195fdda1677408281f5c408996ef8d6ae3e1567f83b +size 64670 diff --git a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_4_de.png b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_4_de.png index fc4c6cbc45..99b5863d77 100644 --- a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_4_de.png +++ b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:900c16973cf8d59b6ace320c848e07eb92056be8e4e14b148a852fd46abeaa69 -size 65817 +oid sha256:07e66f22455d1c79851b28807bab15ee114f45bca794c6405358c47d8a313bcc +size 66193 diff --git a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_5_de.png b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_5_de.png index 86dfef35db..726a329f48 100644 --- a/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_5_de.png +++ b/screenshots/de/features.createroom.impl.configureroom_ConfigureRoomViewLight_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2cff0e19fb2f6f09a7173fde3612c55cc2cbe3028c5a8c09ac3efc623bd35da1 -size 63502 +oid sha256:27f2ddc3924cea4dfc4f7ee4b077dc2e9a3fb33122a4f0fb1fade3322f305815 +size 63881 diff --git a/screenshots/de/features.forward.impl_ForwardMessagesView_Day_3_de.png b/screenshots/de/features.forward.impl_ForwardMessagesView_Day_3_de.png new file mode 100644 index 0000000000..c856f0e358 --- /dev/null +++ b/screenshots/de/features.forward.impl_ForwardMessagesView_Day_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:506f037d7cce42ac09efc89bff5d74475a723321af4713ed54a7caf85bfcb297 +size 14382 diff --git a/screenshots/de/features.ftue.impl.notifications_NotificationsOptInView_Day_0_de.png b/screenshots/de/features.ftue.impl.notifications_NotificationsOptInView_Day_0_de.png index 2aaf45271a..84017fc504 100644 --- a/screenshots/de/features.ftue.impl.notifications_NotificationsOptInView_Day_0_de.png +++ b/screenshots/de/features.ftue.impl.notifications_NotificationsOptInView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:72a797ea50aef8b2de09aaa54e9710a77ed7f696075be904d6b95032ccce44b6 -size 71406 +oid sha256:dea8fb3f80552e387fc8a0e1bb196b60a98e9c49e6a0c3e2f0e8fad75bbd44f9 +size 71400 diff --git a/screenshots/de/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_0_de.png b/screenshots/de/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_0_de.png index ea50869a7d..1a8c87d2dd 100644 --- a/screenshots/de/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_0_de.png +++ b/screenshots/de/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8cc57222ee6166b3b415315497191cbc2b90ff4a41f11eb0f19fceb7e097a87a -size 36917 +oid sha256:9838926418f0a3e9ac8f9d90558372f523a0eac63d456bfb711219c37212f7b9 +size 36911 diff --git a/screenshots/de/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_1_de.png b/screenshots/de/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_1_de.png index e8e553710b..94c1100ea5 100644 --- a/screenshots/de/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_1_de.png +++ b/screenshots/de/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:236c8f484745747889248205a566b313b98b35332271cd2c890aaafe2724a30d -size 30272 +oid sha256:1840c34892a469caf8cc794d608b70edf437318b406e2bd6301966455af01515 +size 30266 diff --git a/screenshots/de/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_2_de.png b/screenshots/de/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_2_de.png index 7a1f14f76c..fd3afd132f 100644 --- a/screenshots/de/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_2_de.png +++ b/screenshots/de/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2193e2bab199ad3783eb3b4d524c099c35374ede78adf1ed49d1f04e5bd4d132 -size 43031 +oid sha256:781c324b5ec66937a82b0a59a3b31c8d242adc60c4b5628519ac57bba727d104 +size 43027 diff --git a/screenshots/de/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_3_de.png b/screenshots/de/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_3_de.png index 13be141568..ad0fdc98d8 100644 --- a/screenshots/de/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_3_de.png +++ b/screenshots/de/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23b34487e8c852788102c9bcd5a9eca498a2c4165678d8ae4ea7c21d3a62bde3 -size 36594 +oid sha256:3da672ece75f846bd08ce5ee6290059ac00f407d93123f026a59cb49a62987a9 +size 36588 diff --git a/screenshots/de/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_4_de.png b/screenshots/de/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_4_de.png new file mode 100644 index 0000000000..015381912e --- /dev/null +++ b/screenshots/de/features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:07812a245ea5c9aace355b56086ad386c0cb542f56f4913a8a9b1026fd6f8cec +size 26519 diff --git a/screenshots/de/features.home.impl.components_BatteryOptimizationBanner_Day_0_de.png b/screenshots/de/features.home.impl.components_BatteryOptimizationBanner_Day_0_de.png index 03a5750716..42a7a323cf 100644 --- a/screenshots/de/features.home.impl.components_BatteryOptimizationBanner_Day_0_de.png +++ b/screenshots/de/features.home.impl.components_BatteryOptimizationBanner_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:670d308317209e4a8e64a34d0529ae96f313a7e18fbb3aad6ca85e68b3ed2eb6 -size 33325 +oid sha256:6a77c71e8acd0b1710b22958cfe5cc4f8f67445da635135dabbbd9b7e7871b07 +size 33329 diff --git a/screenshots/de/features.home.impl.components_ConfirmRecoveryKeyBanner_Day_0_de.png b/screenshots/de/features.home.impl.components_ConfirmRecoveryKeyBanner_Day_0_de.png index be25e57899..e18604695f 100644 --- a/screenshots/de/features.home.impl.components_ConfirmRecoveryKeyBanner_Day_0_de.png +++ b/screenshots/de/features.home.impl.components_ConfirmRecoveryKeyBanner_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58cb364d4b03cc76ca2244bd59d844c3bf1133231d92c580229dc69bcb70b40f -size 34442 +oid sha256:85785b8f45de2e378eb31538aaacc83e09ecb9e6284d2832ef32ac59f107761f +size 34461 diff --git a/screenshots/de/features.home.impl.components_DefaultRoomListTopBarMultiAccount_Day_0_de.png b/screenshots/de/features.home.impl.components_DefaultRoomListTopBarMultiAccount_Day_0_de.png deleted file mode 100644 index 6cfe101073..0000000000 --- a/screenshots/de/features.home.impl.components_DefaultRoomListTopBarMultiAccount_Day_0_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:baac8c8251f75553c31d68ab3c56435303fcf918ebdcbe5986f96222112f4b2c -size 26452 diff --git a/screenshots/de/features.home.impl.components_DefaultRoomListTopBarWithIndicator_Day_0_de.png b/screenshots/de/features.home.impl.components_DefaultRoomListTopBarWithIndicator_Day_0_de.png deleted file mode 100644 index 7693af2408..0000000000 --- a/screenshots/de/features.home.impl.components_DefaultRoomListTopBarWithIndicator_Day_0_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b6a48914499aafcd7f65c7f0adc9c9660742308317ed136e2f1a72aa9d7aea8d -size 26794 diff --git a/screenshots/de/features.home.impl.components_DefaultRoomListTopBar_Day_0_de.png b/screenshots/de/features.home.impl.components_DefaultRoomListTopBar_Day_0_de.png deleted file mode 100644 index c0ee6482c6..0000000000 --- a/screenshots/de/features.home.impl.components_DefaultRoomListTopBar_Day_0_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:96aa21d1c28dbfbc328a76c2f3fd0df12692b27905ff80274e04d11f8cfab04a -size 26545 diff --git a/screenshots/de/features.home.impl.components_FullScreenIntentPermissionBanner_Day_0_de.png b/screenshots/de/features.home.impl.components_FullScreenIntentPermissionBanner_Day_0_de.png index e35da5b224..c8ebd7d369 100644 --- a/screenshots/de/features.home.impl.components_FullScreenIntentPermissionBanner_Day_0_de.png +++ b/screenshots/de/features.home.impl.components_FullScreenIntentPermissionBanner_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:00f0ef61028de8ad1c9886987872f1cf3dfe31f80f313a49099ab3017ccbbfd5 -size 33220 +oid sha256:83721f6cb5a98f7e67c62cc9b05dd3cc49f9701ba7ef2aefc6a220cf6b01f30b +size 33237 diff --git a/screenshots/de/features.home.impl.components_HomeTopBarMultiAccount_Day_0_de.png b/screenshots/de/features.home.impl.components_HomeTopBarMultiAccount_Day_0_de.png new file mode 100644 index 0000000000..b40ff588c7 --- /dev/null +++ b/screenshots/de/features.home.impl.components_HomeTopBarMultiAccount_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82d4ec8dfa27a109e3b6e09a989b57540ee24beacc325c32707b3fd2b6310821 +size 21869 diff --git a/screenshots/de/features.home.impl.components_HomeTopBarWithIndicator_Day_0_de.png b/screenshots/de/features.home.impl.components_HomeTopBarWithIndicator_Day_0_de.png new file mode 100644 index 0000000000..81f3d84947 --- /dev/null +++ b/screenshots/de/features.home.impl.components_HomeTopBarWithIndicator_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e1492f5df9092f52dd9ecdc38d03aee7abda581bf7c12401561a6dea5a41f86 +size 22302 diff --git a/screenshots/de/features.home.impl.components_HomeTopBar_Day_0_de.png b/screenshots/de/features.home.impl.components_HomeTopBar_Day_0_de.png new file mode 100644 index 0000000000..f47c2d2770 --- /dev/null +++ b/screenshots/de/features.home.impl.components_HomeTopBar_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec8262321fea4ad6cf9b648e262c3359557fa8dcfd127f70e62bc705d7ec5cd4 +size 21962 diff --git a/screenshots/de/features.home.impl.components_NewNotificationSoundBanner_Day_0_de.png b/screenshots/de/features.home.impl.components_NewNotificationSoundBanner_Day_0_de.png index cbaec95e4e..7bed6a049c 100644 --- a/screenshots/de/features.home.impl.components_NewNotificationSoundBanner_Day_0_de.png +++ b/screenshots/de/features.home.impl.components_NewNotificationSoundBanner_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91a7fc2676cb7271eb85ffe1e8bf42892a0f40fa4b8310f3385afd790f28c17f -size 25724 +oid sha256:f93ac9e09873a777bbfb1de4124b276c1a836af500500f0ff0d94c1a13707e48 +size 25734 diff --git a/screenshots/de/features.home.impl.components_RoomListContentView_Day_0_de.png b/screenshots/de/features.home.impl.components_RoomListContentView_Day_0_de.png index 718868cb85..ebed766b40 100644 --- a/screenshots/de/features.home.impl.components_RoomListContentView_Day_0_de.png +++ b/screenshots/de/features.home.impl.components_RoomListContentView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc274e63a8428f432ae8ddb7d91cfabffef8d43f130c91c8927427194defb40b -size 43970 +oid sha256:cd5a8bb187b96200fb85a79a57c6ad555324df7fd825615053adb15f4b7a07db +size 44076 diff --git a/screenshots/de/features.home.impl.components_RoomListContentView_Day_3_de.png b/screenshots/de/features.home.impl.components_RoomListContentView_Day_3_de.png index de59c20f97..73e75b9f21 100644 --- a/screenshots/de/features.home.impl.components_RoomListContentView_Day_3_de.png +++ b/screenshots/de/features.home.impl.components_RoomListContentView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5520723fc98d5c041c5f13c445f8fe5ce456119ddda8c2eb6b5cdac036aeaa3f -size 21532 +oid sha256:26aa60982538d671935f47c815ef13b55d36b1dc62b6bbd1cd18c09f5684d7cf +size 21544 diff --git a/screenshots/de/features.home.impl.components_RoomListContentView_Day_4_de.png b/screenshots/de/features.home.impl.components_RoomListContentView_Day_4_de.png index c2c5f858d7..3d3f7a3cd7 100644 --- a/screenshots/de/features.home.impl.components_RoomListContentView_Day_4_de.png +++ b/screenshots/de/features.home.impl.components_RoomListContentView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:329befa1d030a76d8a772a69cea1296b31b7d2715c0d74553b26a63845a39caf -size 56164 +oid sha256:92df0dc469ebbe7d631de2034588d5f19190519949d446965a34c90bfa4ff2d3 +size 56182 diff --git a/screenshots/de/features.home.impl.components_RoomListContentView_Day_5_de.png b/screenshots/de/features.home.impl.components_RoomListContentView_Day_5_de.png index d1173d8bb1..537e7cd715 100644 --- a/screenshots/de/features.home.impl.components_RoomListContentView_Day_5_de.png +++ b/screenshots/de/features.home.impl.components_RoomListContentView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ecfd2bb5f3fa6d1828dd63ffb6b97169b138d1899f999b3a40f8f45c6b74297 -size 64057 +oid sha256:59cf2e0b4094609c7748ca389c9afd0e84fcbeba22a9e435c890e94f164c8fe2 +size 64367 diff --git a/screenshots/de/features.home.impl.components_SetUpRecoveryKeyBanner_Day_0_de.png b/screenshots/de/features.home.impl.components_SetUpRecoveryKeyBanner_Day_0_de.png index 101ebd6709..b0a2c81647 100644 --- a/screenshots/de/features.home.impl.components_SetUpRecoveryKeyBanner_Day_0_de.png +++ b/screenshots/de/features.home.impl.components_SetUpRecoveryKeyBanner_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4095db1fc771634666d8a741094154f0199057cc4f4826e834fa7979d9695b04 -size 39140 +oid sha256:b65bedce0b61249766d7e75ff74a8113a6bc5905f10b475118e924b9eb5a72ce +size 39155 diff --git a/screenshots/de/features.home.impl.filters_RoomListFiltersView_Day_0_de.png b/screenshots/de/features.home.impl.filters_RoomListFiltersView_Day_0_de.png index 60ffa078a5..5a0cedf904 100644 --- a/screenshots/de/features.home.impl.filters_RoomListFiltersView_Day_0_de.png +++ b/screenshots/de/features.home.impl.filters_RoomListFiltersView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0faba67716fb88929d38d0148e23612d1a530b3af1824590506c65feee87ca83 -size 14364 +oid sha256:a93c8b953f4be3d369dde578889cd548a94fdd11403f720abfb926d7596be540 +size 13361 diff --git a/screenshots/de/features.home.impl.filters_RoomListFiltersView_Day_1_de.png b/screenshots/de/features.home.impl.filters_RoomListFiltersView_Day_1_de.png index 1316133253..801882b9a0 100644 --- a/screenshots/de/features.home.impl.filters_RoomListFiltersView_Day_1_de.png +++ b/screenshots/de/features.home.impl.filters_RoomListFiltersView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b59a077729a19c882de527df5d72c82e68d7a4afc0dfe1c29032b93ea7e23ab -size 12342 +oid sha256:b374f82e8d12b325e775f5d35a779ab1a955155d8b626ce13e3363260be837f3 +size 12124 diff --git a/screenshots/de/features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_0_de.png b/screenshots/de/features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_0_de.png index f37924b4b0..c4742e5739 100644 --- a/screenshots/de/features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_0_de.png +++ b/screenshots/de/features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d368c5876bb66ee82422e0a896660d44f6106a1b81b06c0fe4af6a5475602f94 -size 23644 +oid sha256:1e4bca9f466aa446dca37659f33f1f31f3d98bff1fae33243ea00e987f050886 +size 23622 diff --git a/screenshots/de/features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_1_de.png b/screenshots/de/features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_1_de.png index 6b9d582870..1bb3353e61 100644 --- a/screenshots/de/features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_1_de.png +++ b/screenshots/de/features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29ce8b1546df7121cf3f28218b6c6d4c91a7888c22c02ebfe1aceb58b35dad2a -size 23844 +oid sha256:7d9aaa7142e8d3d36be461f00e7d80e14c9960168199190208b54bd49dafccf3 +size 23838 diff --git a/screenshots/de/features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_2_de.png b/screenshots/de/features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_2_de.png index 908db1df3e..00e402cc2d 100644 --- a/screenshots/de/features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_2_de.png +++ b/screenshots/de/features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da9a37c55c9f569d29c5b28f6d7d59974296f69e6fb4d40f47f8cd1afc205118 -size 26032 +oid sha256:e322b890b936bebab3e5e2590aeffb7b4bcf82cc42e08b071ea6036b883dea15 +size 26028 diff --git a/screenshots/de/features.home.impl.search_RoomListSearchContent_Day_1_de.png b/screenshots/de/features.home.impl.search_RoomListSearchContent_Day_1_de.png index 9edf04bc2a..1c4fb92e9c 100644 --- a/screenshots/de/features.home.impl.search_RoomListSearchContent_Day_1_de.png +++ b/screenshots/de/features.home.impl.search_RoomListSearchContent_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c028b77f53da31957dcb04ba9201c2a9f7345994f628081569bbd0601cd689f -size 46029 +oid sha256:f5ee7f6355bab9a287fdafcc62463798c82a4fd41a08b00a6cc7feb6e47de9a2 +size 46251 diff --git a/screenshots/de/features.home.impl.spaces_HomeSpacesView_Day_0_de.png b/screenshots/de/features.home.impl.spaces_HomeSpacesView_Day_0_de.png index a6e961cacb..a75fe56c05 100644 --- a/screenshots/de/features.home.impl.spaces_HomeSpacesView_Day_0_de.png +++ b/screenshots/de/features.home.impl.spaces_HomeSpacesView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f5dc2dfed0bc7951d5ddc45215fa7798157d5d807574ca4724549d706c56bea -size 90925 +oid sha256:14e930dacc7ad385a0401556eb6be4fb59ba7801a5cb3b6f1222cab68938c1c6 +size 90986 diff --git a/screenshots/de/features.home.impl.spaces_HomeSpacesView_Day_1_de.png b/screenshots/de/features.home.impl.spaces_HomeSpacesView_Day_1_de.png index 5521655e1a..11761bef37 100644 --- a/screenshots/de/features.home.impl.spaces_HomeSpacesView_Day_1_de.png +++ b/screenshots/de/features.home.impl.spaces_HomeSpacesView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5a5878f4483056c91981bdaed225a5dfabb78ac3f55243c2f7323f259c00542e -size 42026 +oid sha256:7a93222cebbc0bc6ba382b10bef1118edf67fdf520b37038d0c47725e3c9e831 +size 42115 diff --git a/screenshots/de/features.home.impl_HomeView_Day_0_de.png b/screenshots/de/features.home.impl_HomeView_Day_0_de.png index 5453ea83cc..d6e6402fea 100644 --- a/screenshots/de/features.home.impl_HomeView_Day_0_de.png +++ b/screenshots/de/features.home.impl_HomeView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd4033b18eacdf206e2b9d39006626c9eac96b3dc57b8a8200454f4b8b544c5e -size 68319 +oid sha256:bb38da7cb17bf60d3f65657d66a59cae1c282dea3a5855694eeb65486a0b89a9 +size 63760 diff --git a/screenshots/de/features.home.impl_HomeView_Day_10_de.png b/screenshots/de/features.home.impl_HomeView_Day_10_de.png index eb7a71d593..069dd23b63 100644 --- a/screenshots/de/features.home.impl_HomeView_Day_10_de.png +++ b/screenshots/de/features.home.impl_HomeView_Day_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:159c14c2f3aceed0993cda75af75d53cdcb7fe58b04a4412c768163328f95df5 -size 36852 +oid sha256:62d1ad64d8d096a34458c7845fb800fa0cd605461f3ef4fe0699d30ca03c7925 +size 33284 diff --git a/screenshots/de/features.home.impl_HomeView_Day_13_de.png b/screenshots/de/features.home.impl_HomeView_Day_13_de.png index 2d7a61f711..5b21661b2a 100644 --- a/screenshots/de/features.home.impl_HomeView_Day_13_de.png +++ b/screenshots/de/features.home.impl_HomeView_Day_13_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cdc61baac9b833fab3823e31910df8ef828848d673436c402113da92e4baf615 -size 92898 +oid sha256:66dc64fb12b3cc6e2e3953eef03b1615473fd58c9476bd2beec5f72753011076 +size 95190 diff --git a/screenshots/de/features.home.impl_HomeView_Day_14_de.png b/screenshots/de/features.home.impl_HomeView_Day_14_de.png index 5d5a764e7b..8a5d19579b 100644 --- a/screenshots/de/features.home.impl_HomeView_Day_14_de.png +++ b/screenshots/de/features.home.impl_HomeView_Day_14_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3c1fc65c761d7fa0b44ce916aee2ce4af5725877620de2f33b6e03b2c54789e -size 90356 +oid sha256:07b0d56ddeacc0c5d28ae7cfabb5ebe69fa96cc33207945a57c5f146d0d5879a +size 89944 diff --git a/screenshots/de/features.home.impl_HomeView_Day_15_de.png b/screenshots/de/features.home.impl_HomeView_Day_15_de.png index 040ac63243..6ac7326cdf 100644 --- a/screenshots/de/features.home.impl_HomeView_Day_15_de.png +++ b/screenshots/de/features.home.impl_HomeView_Day_15_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c360d5a74cb9cdf48066d18fc68ab4f12caca7d4de079fae7a12f7e99fc7e513 -size 54846 +oid sha256:b7ab0fb505552504a2b069118818f82738f1c364e54f4a7298fecbfcee067f53 +size 55052 diff --git a/screenshots/de/features.home.impl_HomeView_Day_1_de.png b/screenshots/de/features.home.impl_HomeView_Day_1_de.png index bfec21f772..d6e6402fea 100644 --- a/screenshots/de/features.home.impl_HomeView_Day_1_de.png +++ b/screenshots/de/features.home.impl_HomeView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed1428c748522383c7f5c58cda95df6398a487d826188c53e285aa6ad3e9a16d -size 69704 +oid sha256:bb38da7cb17bf60d3f65657d66a59cae1c282dea3a5855694eeb65486a0b89a9 +size 63760 diff --git a/screenshots/de/features.home.impl_HomeView_Day_2_de.png b/screenshots/de/features.home.impl_HomeView_Day_2_de.png index 5453ea83cc..d6e6402fea 100644 --- a/screenshots/de/features.home.impl_HomeView_Day_2_de.png +++ b/screenshots/de/features.home.impl_HomeView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd4033b18eacdf206e2b9d39006626c9eac96b3dc57b8a8200454f4b8b544c5e -size 68319 +oid sha256:bb38da7cb17bf60d3f65657d66a59cae1c282dea3a5855694eeb65486a0b89a9 +size 63760 diff --git a/screenshots/de/features.home.impl_HomeView_Day_3_de.png b/screenshots/de/features.home.impl_HomeView_Day_3_de.png index c06b6ccbe6..0be04e92be 100644 --- a/screenshots/de/features.home.impl_HomeView_Day_3_de.png +++ b/screenshots/de/features.home.impl_HomeView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a9632d63be6dae2ccff6463cbc984b871223cab0cf044f10e453d871b6106d94 -size 63047 +oid sha256:79c556ec87626ccd24be0af20caa14593e526edd82e64b2a26e504618fbf008e +size 62166 diff --git a/screenshots/de/features.home.impl_HomeView_Day_4_de.png b/screenshots/de/features.home.impl_HomeView_Day_4_de.png index 1c1734ceee..ffba89ca4f 100644 --- a/screenshots/de/features.home.impl_HomeView_Day_4_de.png +++ b/screenshots/de/features.home.impl_HomeView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2c6870cbb8fcca652d06ff492a900ef01f0272129ac6777e6f207c5c97dd5bfd -size 56530 +oid sha256:8902154fddfa359e605cf95c715e080a5b027a79bac4ff0026464c20dd29311a +size 55725 diff --git a/screenshots/de/features.home.impl_HomeView_Day_5_de.png b/screenshots/de/features.home.impl_HomeView_Day_5_de.png index 5453ea83cc..d6e6402fea 100644 --- a/screenshots/de/features.home.impl_HomeView_Day_5_de.png +++ b/screenshots/de/features.home.impl_HomeView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd4033b18eacdf206e2b9d39006626c9eac96b3dc57b8a8200454f4b8b544c5e -size 68319 +oid sha256:bb38da7cb17bf60d3f65657d66a59cae1c282dea3a5855694eeb65486a0b89a9 +size 63760 diff --git a/screenshots/de/features.home.impl_HomeView_Day_6_de.png b/screenshots/de/features.home.impl_HomeView_Day_6_de.png index 5dfec9dbe3..c0b27ca7bf 100644 --- a/screenshots/de/features.home.impl_HomeView_Day_6_de.png +++ b/screenshots/de/features.home.impl_HomeView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f12c3c149d1aaaee451bc771accdaf5af6825bd5b2d0203af66aa0138dd8514b -size 55834 +oid sha256:0d1580df361ba18c688f51fbb60060ccc90699919d4bffd37b0e1bba710d69c7 +size 58347 diff --git a/screenshots/de/features.home.impl_HomeView_Day_7_de.png b/screenshots/de/features.home.impl_HomeView_Day_7_de.png index b1566559b0..33bc050b3f 100644 --- a/screenshots/de/features.home.impl_HomeView_Day_7_de.png +++ b/screenshots/de/features.home.impl_HomeView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2faaaec56e952f4e50517073fc023736740b5ea6f50fce6554de3d2ac2b6871 -size 55158 +oid sha256:f2cc760e43b919592b11023d054fbd3a2c767940b5990fb21857e9d3efbf84ce +size 57691 diff --git a/screenshots/de/features.home.impl_HomeView_Day_8_de.png b/screenshots/de/features.home.impl_HomeView_Day_8_de.png index fb430bd3b2..94e0c7a247 100644 --- a/screenshots/de/features.home.impl_HomeView_Day_8_de.png +++ b/screenshots/de/features.home.impl_HomeView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a9dd1a22036e65045c84979c6623d4fa8d7548963dc1958434586a30bf3b2687 -size 53369 +oid sha256:11f9e09290a4a4e078087f6eac764ae3cceb4052c9d75ee33f9036c83fdcb8c2 +size 55926 diff --git a/screenshots/de/features.home.impl_HomeView_Day_9_de.png b/screenshots/de/features.home.impl_HomeView_Day_9_de.png index 73d2cf5470..9c81fb4fb1 100644 --- a/screenshots/de/features.home.impl_HomeView_Day_9_de.png +++ b/screenshots/de/features.home.impl_HomeView_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:549c7644f0a20617bf8777175858ab08488d74871b8f027b44a34645a28148bc -size 88335 +oid sha256:1790960b72437802d90f756b037565e0466e9088ca0205f3ebb269d56d22fd37 +size 90881 diff --git a/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_0_de.png b/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_0_de.png index 06e1e4d73d..16603e1ad5 100644 --- a/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_0_de.png +++ b/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:14d48a5de0ed3ea22231f63e0c9505fa45b0ed49be9711ade65c3e971fdb90af -size 38115 +oid sha256:4125ed409129b21fbd135be095406ef6067796ab51af14f0a03e4e976a12ef40 +size 38129 diff --git a/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_1_de.png b/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_1_de.png index 84308ef475..a80a2921b4 100644 --- a/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_1_de.png +++ b/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2663031de6e0be83865e6008f898ba06603132a03136c7f5c49959d883581828 -size 43019 +oid sha256:f117efb1ae1be195266ff14faf0223cf0e45ddfb881c2231aa166184145ec0e9 +size 43032 diff --git a/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_2_de.png b/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_2_de.png index 32ad3592b3..57660288f1 100644 --- a/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_2_de.png +++ b/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2e479455f6c45247609af474edf795e40298614aae7b0d91d28829ca47eaab41 -size 38313 +oid sha256:f70a4e76d086ce61717c84da62781fb167366e0e1ce516c1d5fcf59ede2aecbf +size 38333 diff --git a/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_3_de.png b/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_3_de.png index 389639d215..96a1e1c954 100644 --- a/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_3_de.png +++ b/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a8c6ea3cb57beacb3ca3ebdfccb0dfc945861a75cedc74e3fd37f7306fd39f2 -size 33609 +oid sha256:1d14ff5d63ee27149dc21ae1c2cedb58244237b5303bbf7f45c299e61cefaea3 +size 33573 diff --git a/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_4_de.png b/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_4_de.png index 06c106589a..530e34a0cf 100644 --- a/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_4_de.png +++ b/screenshots/de/features.invite.impl.declineandblock_DeclineAndBlockView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:47ea6d76991c1a9d4dcd017186fc226150fe7f6fb0c3f772d9fe81c655f671fd -size 38622 +oid sha256:0b3568df86493ca9470dc99aa4f516f42d827fcadd1f9088d24749f777dd3193 +size 38583 diff --git a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_0_de.png b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_0_de.png index 9f56a2e6a3..0402613534 100644 --- a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_0_de.png +++ b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:32511b8f5a32774defea03b7c9765ff08322a2a59083c14f3a8d9b8e5807340c -size 9405 +oid sha256:2a5b7c5d7fa9a6f9156f25aa565944028149a7d2ab9f418a0b02a17d948a6768 +size 9415 diff --git a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_1_de.png b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_1_de.png index 15bfd49357..999bb266e8 100644 --- a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_1_de.png +++ b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8590b5fd61453580add2dc93262f621b1dbefcff45ed9ef1004d0c5606c35dad -size 21730 +oid sha256:53643845f3b082175ed3a33e0d8fd6edc4006cbe74a0c9db7a7100c2351acc4d +size 21744 diff --git a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_4_de.png b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_4_de.png index 89363fb05e..5472de64d6 100644 --- a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_4_de.png +++ b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:656f746cc9dc58566372c0a958d13307d2dc8b5a3c28da0c66f84acbb64489fd -size 9953 +oid sha256:51b14d3633dd614fcad706c714e93447dc3ac6039ef0cec6213f77a3c24cf2b3 +size 9984 diff --git a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_5_de.png b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_5_de.png index 37e477d72d..ba1c5b663c 100644 --- a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_5_de.png +++ b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f289590cc23cf31013b55d95245d076bb37e6907a8b4409b57e0798847522859 -size 39288 +oid sha256:7402d0fb1b714f733d3929955fcbf0b096187cf70e3503acc21ffa089142610f +size 38851 diff --git a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_6_de.png b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_6_de.png index 472cd1d139..d46d8971aa 100644 --- a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_6_de.png +++ b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d531da522778d5692a2f15f05aa4d75e412c8afe8e64aadeacd34b53517d4f8f -size 38254 +oid sha256:2457c74365a98c64974eeaf607879461d2cd9a1757096f330d913fb034816b31 +size 38734 diff --git a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_7_de.png b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_7_de.png index eea89a228e..988c8dd318 100644 --- a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_7_de.png +++ b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:361ffdc21a6bbe6d1e4d460c74e9fd8a2058d489f5d0e716a71a6dd970d7d8fa -size 30254 +oid sha256:045a5183f88a998968af1f1f8a02e864697add38b1259b6e40fda702dce82b48 +size 30661 diff --git a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_9_de.png b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_9_de.png index 15bfd49357..999bb266e8 100644 --- a/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_9_de.png +++ b/screenshots/de/features.invitepeople.impl_InvitePeopleView_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8590b5fd61453580add2dc93262f621b1dbefcff45ed9ef1004d0c5606c35dad -size 21730 +oid sha256:53643845f3b082175ed3a33e0d8fd6edc4006cbe74a0c9db7a7100c2351acc4d +size 21744 diff --git a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_10_de.png b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_10_de.png index f0f67e6a31..af401c96b2 100644 --- a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_10_de.png +++ b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dcf5e51d91e309bfe97f696d45a0a3ef19c9139de30f3bbd0c1cd5cbb2aee22e -size 46807 +oid sha256:ae3bd5c35597acdd08b98069a29988cb640b5e18f88987d494235417d542cc5f +size 46833 diff --git a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_11_de.png b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_11_de.png index 4ae703b23e..7daf68489a 100644 --- a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_11_de.png +++ b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9399942211a13c9d871692dfd1b421fcd923ddde9f5922979ebfff07c0824060 -size 41067 +oid sha256:89eae936d074d8dd4b85b1bf2cea79cdc2e836c86c1af0666f6f99ba5958a761 +size 41109 diff --git a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_12_de.png b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_12_de.png index 5dcd4a2ba3..08262a007a 100644 --- a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_12_de.png +++ b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_12_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:296d74f3a3198f98599fd2c428268f67b4b70e09fdfbeead9f7c7436e9cf8758 -size 42259 +oid sha256:49e56a96b1484728cc32bbbf72aa1afb9a2832b9cc383b2f1c6196eaa6c106ba +size 42303 diff --git a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_13_de.png b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_13_de.png index f38dfd26a6..53905be788 100644 --- a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_13_de.png +++ b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_13_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:81ae149fcf6f1a6a52bb90618b2bd6b79192221d2decadd3d78f34ac227a0e86 -size 34158 +oid sha256:8020ec9606c7abb83beea3655c0093ea031dc73cb4999689c6351cec41774b72 +size 34186 diff --git a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_14_de.png b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_14_de.png index 5637757b98..b8e3885756 100644 --- a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_14_de.png +++ b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_14_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:26c22c76a3a13cdda38f9069adef8d6f17e2b5bf6bfa952812bde3575f62281f +oid sha256:40cc7f22caec6e0cea9d167d737f5320d68d628746283c928d4d8754d9fe8434 size 31974 diff --git a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_15_de.png b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_15_de.png index 06bb84c272..5a8abf5f49 100644 --- a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_15_de.png +++ b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_15_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe3ea3e63387d40e0314b702a0e08bba8bee8b63dac6758fec713a929715b92c -size 35827 +oid sha256:969707f3c5e0989b2abf017f42887da3aed72393f0367ca4bb9b9ca713892100 +size 35839 diff --git a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_16_de.png b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_16_de.png index 5cd8ca5e77..2daa670c8e 100644 --- a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_16_de.png +++ b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_16_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6367c2cb513ab5e26d3e16a1a7554251cd5051b55328cae99019eac84e6ef2bd -size 49735 +oid sha256:1169fceea83ba87555d57e1b46e7638033415dfe327ccb4d6d62baea28b9263e +size 49739 diff --git a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_1_de.png b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_1_de.png index 4cadf347c5..9193157c9d 100644 --- a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_1_de.png +++ b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff18fe6020711f81385f8e9e54fb685d49f95dbb720fdab37de2acc35896647b -size 41101 +oid sha256:1208ae724a5b274922aca200adca9245e5bcd8e8655f8cb47feacbc4d06e51f9 +size 41128 diff --git a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_2_de.png b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_2_de.png index 013b1aa259..ae737b2a00 100644 --- a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_2_de.png +++ b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f96ada2da82b7265fc655e4826069b395e07dedb28ad86761f956e398ced79cf -size 39667 +oid sha256:603d9de02688eda3d7de1c7af80473699656cb0b5e5e2f794c57acaa78946d46 +size 39701 diff --git a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_3_de.png b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_3_de.png index d822cfb9f7..19c210a15c 100644 --- a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_3_de.png +++ b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a6338d8c9baed45e73eb8eab8f9b097a708f1b2c0864de80911d8ddc3f9c361 -size 29854 +oid sha256:13d148d82875b231dc056d23bff5851df6c00a5f98d708a8a0329f66ef38c2a2 +size 29877 diff --git a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_4_de.png b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_4_de.png index 6744cd39a9..e7bd9cc356 100644 --- a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_4_de.png +++ b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f0fb1a5fc06cd64bcdb5b75e56fdaeccad017bf17b11c8cb8f9714e9c7eaab2c -size 46214 +oid sha256:292558f746830bbb4a4fe00282d2e3f8d6c99e57dd209568003a86af273759b7 +size 46246 diff --git a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_5_de.png b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_5_de.png index b3a4eeb0d5..5311b37ec0 100644 --- a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_5_de.png +++ b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:428dfda0652172cc9e9a2392db535fc51de5280b060d969863d0299142c1f35d -size 34643 +oid sha256:3f52fb45df883d18499a79d9c079b7eae379ebf358d71a507c9a97ea02191f2b +size 34623 diff --git a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_6_de.png b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_6_de.png index 02b18e602d..c46327c62f 100644 --- a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_6_de.png +++ b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1292c78624d48944ba94da85c8e56f2636d059458bbc330298dcca89131c143f -size 34720 +oid sha256:c997939cd44bf4532c43156848f6ea1d5623fba2e20cfda978be96d26add8c16 +size 34723 diff --git a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_7_de.png b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_7_de.png index 5b96ed77a8..cdef83f98d 100644 --- a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_7_de.png +++ b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3d40fe4aa6b06f8b52ec5b4f94549c35613872b3c84843e814e400139c1ba1cd -size 41872 +oid sha256:3b171d116d25cdaf87e3437c71d94e0792654f91cb5f39ac34d83a2a1c4c946d +size 41874 diff --git a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_8_de.png b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_8_de.png index 799892693d..456f06de2b 100644 --- a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_8_de.png +++ b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:287dc0a75daeafebb50f79ec327a02eb019d3016b2e52e28613edf9c6e252315 -size 35560 +oid sha256:01f6142803fdf3e4adc9ecd640dca111baece2099899482776c11bfa98747673 +size 35544 diff --git a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_9_de.png b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_9_de.png index b7606cbfb3..c00fccb6a4 100644 --- a/screenshots/de/features.joinroom.impl_JoinRoomView_Day_9_de.png +++ b/screenshots/de/features.joinroom.impl_JoinRoomView_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d097d473593e6cc6bd0cc7bc13ea40faaad312dde2450170c0bfaa7a96e7d2bd -size 43139 +oid sha256:878c10e2f1ca1bdbf15c09d9b46eb329f54d115f58a96eec4384036beb9c8223 +size 43166 diff --git a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_de.png b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_de.png index afd3136705..6864e7af2d 100644 --- a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_de.png +++ b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:175810c0046dfd28a9273ae6dbb9d4f89dc5bbfe719bf4a9dd4938b7e03d2e29 -size 32154 +oid sha256:d7320b17b7d09aedfb0af1d7a8cc63813cd11e9cbf94e6e3fc690f3d355ef24e +size 32142 diff --git a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_de.png b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_de.png index ed3160dcf1..03ac3d6204 100644 --- a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_de.png +++ b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:815272dff296fde43ba3cc8aaf6f7c8e763c8434a400ed2a9b09150f24614f1d -size 37721 +oid sha256:740d9ef181124f879df48739f96c84e734094e0d98b8e3e03b88fff392ce4cf6 +size 37713 diff --git a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_de.png b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_de.png index cf38da1b6e..9bb9a4bb78 100644 --- a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_de.png +++ b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:30743c42e137b63960d18a76df1b488b055b62384e4d984320606ecf0f591e1c -size 21452 +oid sha256:d1469dc66914be97971ea643eeb70ab20a902bea9eb9da5effb8e9c3b7219d5a +size 21460 diff --git a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_de.png b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_de.png index bba069deb0..40941f17e0 100644 --- a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_de.png +++ b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f921e04c544b858f02165d15b1bdc48e71fb9572a6717e52e7c7dcd556e5f64 -size 22488 +oid sha256:20fdd043d471b0ba14bd8809ff9769b10e02da8b9f00f30295385a86eaee2f0c +size 22517 diff --git a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_de.png b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_de.png index de8a818e1a..6e6cfccb1a 100644 --- a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_de.png +++ b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:183f893c3b7ff13533f6130c85b293547dd505ec2ac223ff5cf4c0c1407171b6 -size 28889 +oid sha256:9eb82a3189364f26aad7d96e8efc79e8baf08844c34fd40e53659c632f48a3ec +size 28882 diff --git a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_de.png b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_de.png index 1fadd8530d..f3493f3cd9 100644 --- a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_de.png +++ b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef454c29137f961510691af29ba49066eb25a59655cb565243a74a08b0678d8a -size 35021 +oid sha256:c835567802dc08e802560d3740a7d6ce4e52ea74dc3bfd19a9a7a41b0b6069ca +size 35028 diff --git a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_de.png b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_de.png index 32dc93c205..034e4ee0f9 100644 --- a/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_de.png +++ b/screenshots/de/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c01028c357b014744faa022ea93801a44d7f938b869a4db1c14992ea732665c -size 42277 +oid sha256:94834b539f3bce5590d87f4a2ae2290984cbd50298dbe4e0f877616d3d7ff074 +size 42278 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_0_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_0_de.png index 37057b18f4..c86d36691f 100644 --- a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_0_de.png +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fbf0fa64351f2710f026b438753f426e4473bcdd3a71e8438013a1a1ee970699 -size 16357 +oid sha256:4e640f3696ef9ff12f1ce4cbdd4eb22294c3d97dcca419967df17d2ae8fb4631 +size 16370 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_10_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_10_de.png index 90deee1a55..e4a1ea49f9 100644 --- a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_10_de.png +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:208cca1b73135011b969b6b1c7ef30497a4f645fb531f7d666f223233d766791 -size 32066 +oid sha256:cbd9d5be0a2b6bbc69cd1ee2f82e5d6ee024d523acbdc552effaa8217b35c4c5 +size 32077 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_1_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_1_de.png index 05644c04d6..4acc94ba2f 100644 --- a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_1_de.png +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c76f2ea8f90917abce153e75fc162cd41856d122bf8958bc7b71bdc8107b167c -size 28133 +oid sha256:ce88197751c51e1c354a0fd35ab6b885782f2b88242e56d2707ddc8c97466edb +size 28114 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_2_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_2_de.png index b5e2221a4b..08335e8580 100644 --- a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_2_de.png +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2265efa921451e40b9959e4fa2724978eb118eba074e0d039c6a1c66aee97a41 -size 35666 +oid sha256:2523831ad6a9774c5ccc3884a66daa8db842772b81963c518efb323a2066ac6d +size 35677 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_3_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_3_de.png index 1dee560633..139f9d1ab5 100644 --- a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_3_de.png +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a3f77248ac32c2a6a86f47b8b5d40a57d205949e4d8f673c4735d0af833c47a -size 44390 +oid sha256:a8b78ec6109746d2ad7bc2e1fe6075bf1772e6ecd51cde4c445067fbc085e94e +size 44401 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_4_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_4_de.png index f3891ed3d4..332e7fce13 100644 --- a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_4_de.png +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77e42fe00de27f72afd6b7d2670f4dc72293eabfa800153052351d7cda20bbbf -size 57917 +oid sha256:96c9b9bc65a75419b3100138c3761b3729ead674793bc147047a2d295009ec57 +size 57926 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_5_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_5_de.png index f739c629e1..6776e58586 100644 --- a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_5_de.png +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb4b0c5968cf1859485f0a88e836017ce640e9e7ecc5a8db31df0cd51012da99 -size 48479 +oid sha256:9cd29f7afbbdbdb26a31156549fec3c969966076b35228c1777de7bfe060c09b +size 48468 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_6_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_6_de.png index fb5adf5e5f..15e8bb5ea6 100644 --- a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_6_de.png +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0948a04ca82c9826163898b5998e2a1e7e19a3e212a7718f4888b618aff0fad2 -size 37931 +oid sha256:b84f820edfb2283057f8e7d7fa2f7b1fda88dc166644d32a8b252b5eb6543330 +size 37921 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_7_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_7_de.png index 60d8909107..c9945d8f52 100644 --- a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_7_de.png +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a7f9908bb1a05de0c4e97e830f9ee543deb29d8664cad5ae5a29a5a6cb5284d1 -size 44367 +oid sha256:10017c00cf317eda5996aea05dca42a654a5f63c3394d7a44172640cf704a4f2 +size 44355 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_8_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_8_de.png index 87ad6ef300..96bc96c8cd 100644 --- a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_8_de.png +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e961c29fe0c5fc1bb0fa8b15645f5ab4a48769b36a263261b1fff8ccbb141bba -size 32506 +oid sha256:2497c033e5671544adb216393b0289115ae423552428259195b7eb7fde155344 +size 32516 diff --git a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_9_de.png b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_9_de.png index 0f2f3b1442..a4ec2367d6 100644 --- a/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_9_de.png +++ b/screenshots/de/features.knockrequests.impl.list_KnockRequestsListView_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3271c2b087197cecd7efa3cf8b5f4c4a2190a1b41551004d2a9466d3b5b8cc1c -size 28676 +oid sha256:42cef1b3e2ea03ee7fed2366b92b3c27ace1f404eb2538663ffbbfe3decc0c73 +size 28684 diff --git a/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_0_de.png b/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_0_de.png index 7b4740fadf..23ba7638ff 100644 --- a/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_0_de.png +++ b/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70176fa3a368a15308a8c22bcdda99b1670260b2ca7e6a5ffcc0595ae62178eb -size 10737 +oid sha256:66752c0c42fb8f5d7f4a8b1c30fae0a74915ba10c9dacf3e105385d16723061b +size 10746 diff --git a/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_1_de.png b/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_1_de.png index 4764b6c076..19d2b6837d 100644 --- a/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_1_de.png +++ b/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:12761bf881ecc1787f48834d36f950d38d8dddbcb8fef8f21553cb695ba57e8c -size 10222 +oid sha256:8c28b57b1c32f82d34ff5182bd4b38159009189f35da96286bb7b7161cdd3e6f +size 10230 diff --git a/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_2_de.png b/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_2_de.png index 4d332c73df..ef0c4e20a6 100644 --- a/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_2_de.png +++ b/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:04ad3a3f5f6e09d7f0e22509a22cbe0b7d252be9ec8bfcfc7208a6d7df1c76f5 +oid sha256:efde81467c111a977ce0bb7b5b170094e033eb12d258809bde0ae46f789069e5 size 29235 diff --git a/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_3_de.png b/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_3_de.png index efff44f56e..e7e8a3926c 100644 --- a/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_3_de.png +++ b/screenshots/de/features.licenses.impl.list_DependencyLicensesListView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2230c0dc8f812a9eb06e54fa08e3f3299090a8f2073b8b589a641b7eccca54fb -size 30494 +oid sha256:17f907c0005d8e818f74fd204799380350ec0a508c85365c763ba0195363746e +size 30490 diff --git a/screenshots/de/features.linknewdevice.impl.screens.desktop_DesktopNoticeView_Day_0_de.png b/screenshots/de/features.linknewdevice.impl.screens.desktop_DesktopNoticeView_Day_0_de.png new file mode 100644 index 0000000000..1e1846298a --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.desktop_DesktopNoticeView_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c039b7577f65380d5c54ae7d9a82d62f8a43f4888c86c799fb5b982ba6817d8 +size 45019 diff --git a/screenshots/de/features.linknewdevice.impl.screens.desktop_DesktopNoticeView_Day_1_de.png b/screenshots/de/features.linknewdevice.impl.screens.desktop_DesktopNoticeView_Day_1_de.png new file mode 100644 index 0000000000..8dca9fed7d --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.desktop_DesktopNoticeView_Day_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:48e2d8fffea5b23d7894fc546dac33bb87d7df5267aa58d3f79a14f7f93ee85a +size 44823 diff --git a/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_0_de.png b/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_0_de.png new file mode 100644 index 0000000000..65573d908f --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3fc70436b6ccb1afd77bed07598c020796341aa100b1eab6dc5b00868297dca6 +size 24990 diff --git a/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_1_de.png b/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_1_de.png new file mode 100644 index 0000000000..c7cf5a7efa --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50c28eb8481b1101f75bf8341481ead0e77daf116207d27ddc5c95316daf8e41 +size 22035 diff --git a/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_2_de.png b/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_2_de.png new file mode 100644 index 0000000000..4468511356 --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:20558650fc6a3859569d52baaab1c8af2a68e2944fd772f0d62ced5c1aa44208 +size 29397 diff --git a/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_3_de.png b/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_3_de.png new file mode 100644 index 0000000000..3603b23b24 --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba73d59bab19bc1263174c632f6ca3bfed98d11b1bb949e9340b02bb5c666580 +size 38741 diff --git a/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_4_de.png b/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_4_de.png new file mode 100644 index 0000000000..a11f674a7a --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14e81841b45d0400c65a3a052e8c0c15d48ce88f7f4b50d4e453f92bdb82bbb4 +size 33097 diff --git a/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_5_de.png b/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_5_de.png new file mode 100644 index 0000000000..33122e6dea --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_5_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b39882c9c6d9920d0f76f3b80599b7b41cc6eab4480c6a10f09cf0992acbf203 +size 68940 diff --git a/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_6_de.png b/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_6_de.png new file mode 100644 index 0000000000..8d47b730a2 --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_6_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c9bc94377c776271128fd411397d698539fdf74870b074988b8de1c86a4ecec0 +size 21798 diff --git a/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_7_de.png b/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_7_de.png new file mode 100644 index 0000000000..5f79162d1b --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.error_ErrorView_Day_7_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c184323fa1b1d87f46be6f49bf983f7d4048b5547906531b452924ee89110d30 +size 23962 diff --git a/screenshots/de/features.linknewdevice.impl.screens.number_EnterNumberView_Day_0_de.png b/screenshots/de/features.linknewdevice.impl.screens.number_EnterNumberView_Day_0_de.png new file mode 100644 index 0000000000..bad5f73502 --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.number_EnterNumberView_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:754f8340cc0f39b0c2e63065bf6d03b3716d74176973b0380f348a866c0b233f +size 30062 diff --git a/screenshots/de/features.linknewdevice.impl.screens.number_EnterNumberView_Day_1_de.png b/screenshots/de/features.linknewdevice.impl.screens.number_EnterNumberView_Day_1_de.png new file mode 100644 index 0000000000..7021172235 --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.number_EnterNumberView_Day_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8884d4a3a61239a030dac21660f0cb6d9ed54d3db08c7b2a48e6b18894a0bbf +size 30043 diff --git a/screenshots/de/features.linknewdevice.impl.screens.number_EnterNumberView_Day_2_de.png b/screenshots/de/features.linknewdevice.impl.screens.number_EnterNumberView_Day_2_de.png new file mode 100644 index 0000000000..97e470d460 --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.number_EnterNumberView_Day_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:101ae89accacc3707b71a091eaacb7676ffc24a92d4cfd48edcf077a414130fa +size 30642 diff --git a/screenshots/de/features.linknewdevice.impl.screens.number_EnterNumberView_Day_3_de.png b/screenshots/de/features.linknewdevice.impl.screens.number_EnterNumberView_Day_3_de.png new file mode 100644 index 0000000000..3c9eff4eb0 --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.number_EnterNumberView_Day_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40b967dec26c18a3122b3a80f8e29bb0e7b3d16452f4f8061c729134ac5f9f53 +size 30819 diff --git a/screenshots/de/features.linknewdevice.impl.screens.number_EnterNumberView_Day_4_de.png b/screenshots/de/features.linknewdevice.impl.screens.number_EnterNumberView_Day_4_de.png new file mode 100644 index 0000000000..152fb7112b --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.number_EnterNumberView_Day_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50a6ffd3a59bcf661d5e666736d1189c3c5eaa4bee573644f0a9255d0ffe3a3d +size 34079 diff --git a/screenshots/de/features.linknewdevice.impl.screens.number_EnterNumberView_Day_5_de.png b/screenshots/de/features.linknewdevice.impl.screens.number_EnterNumberView_Day_5_de.png new file mode 100644 index 0000000000..da8006965a --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.number_EnterNumberView_Day_5_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1f9958366956cc133eed94df188a51dacfdb3de4195d91cfdc16cf6cbdf412e +size 33794 diff --git a/screenshots/de/features.linknewdevice.impl.screens.qrcode_ShowQrCodeView_Day_0_de.png b/screenshots/de/features.linknewdevice.impl.screens.qrcode_ShowQrCodeView_Day_0_de.png new file mode 100644 index 0000000000..9cd8daefdb --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.qrcode_ShowQrCodeView_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57f7c6b4d39222cf3e8e92777a0aa319f2654687dd298ce1f60cecdb3e2ce0c1 +size 32149 diff --git a/screenshots/de/features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_0_de.png b/screenshots/de/features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_0_de.png new file mode 100644 index 0000000000..2def92650e --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5fca6a90b7a667ed9d4d5c0cf2616664468b3ff6dd59d6460651c3e3aa3fae08 +size 17847 diff --git a/screenshots/de/features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_2_de.png b/screenshots/de/features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_2_de.png new file mode 100644 index 0000000000..8582608925 --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb7142589d7c830c72fa5b7bc853027b83a2b864e43c849107d35c0a849c4f02 +size 27470 diff --git a/screenshots/de/features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_3_de.png b/screenshots/de/features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_3_de.png new file mode 100644 index 0000000000..442f156ff0 --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34243ec18f06c11de6103bf7787d80b366f12890f01ffe31674ae77ee113dbf1 +size 24679 diff --git a/screenshots/de/features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_5_de.png b/screenshots/de/features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_5_de.png new file mode 100644 index 0000000000..67228c7c86 --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_5_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:437eda5bc2439120a5bca6b88e307d5f0ba1cdce4683e3bb946e7f174419c6e5 +size 34336 diff --git a/screenshots/de/features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_0_de.png b/screenshots/de/features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_0_de.png new file mode 100644 index 0000000000..153df13caa --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:adda1a1a7c20d0eb54528f5f8c60e13c2e8428afc92d849f882f25d727136687 +size 15861 diff --git a/screenshots/de/features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_1_de.png b/screenshots/de/features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_1_de.png new file mode 100644 index 0000000000..d50a67ef38 --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a12dbc4f2fb3d12b1848e2ac4e079522613df2493d0a416f2f47827eb0daa322 +size 16502 diff --git a/screenshots/de/features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_2_de.png b/screenshots/de/features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_2_de.png new file mode 100644 index 0000000000..b786292896 --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e19add1c5894aa7dbad4bc1fff8f3789584946ca1069c3b39b17ed826f4a06c +size 16673 diff --git a/screenshots/de/features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_3_de.png b/screenshots/de/features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_3_de.png new file mode 100644 index 0000000000..305f24b9d7 --- /dev/null +++ b/screenshots/de/features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6a773294a47272bcc0333e7a325e0609ef53d576eca1d40422b0855cc48dae7 +size 31739 diff --git a/screenshots/de/features.location.api.internal_StaticMapPlaceholder_Day_0_de.png b/screenshots/de/features.location.api.internal_StaticMapPlaceholder_Day_0_de.png index 2bea7fe6e7..897ab6acae 100644 --- a/screenshots/de/features.location.api.internal_StaticMapPlaceholder_Day_0_de.png +++ b/screenshots/de/features.location.api.internal_StaticMapPlaceholder_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8096f14cd637ca3042c54d17c6201b633e3f631e637fa5f4060b5c4e56d4faaf -size 440276 +oid sha256:819c1529fda6e1ccb661de3938ba24206164d92c7da38d186fe3c82eb7b66874 +size 440267 diff --git a/screenshots/de/features.location.impl.send_SendLocationView_Day_0_de.png b/screenshots/de/features.location.impl.send_SendLocationView_Day_0_de.png index e3b1a4d8f6..9423b39f9c 100644 --- a/screenshots/de/features.location.impl.send_SendLocationView_Day_0_de.png +++ b/screenshots/de/features.location.impl.send_SendLocationView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:afd961db19f26c40b6811193b0f97130fd83cb7b06a4073666394c8d272c3d6c -size 19312 +oid sha256:b6f1f50d040889a65379d67fce0c5613b898d6cb094acb1aaa4dc0b92a7f657e +size 19347 diff --git a/screenshots/de/features.location.impl.send_SendLocationView_Day_1_de.png b/screenshots/de/features.location.impl.send_SendLocationView_Day_1_de.png index 3496a6c996..b3b8b971fa 100644 --- a/screenshots/de/features.location.impl.send_SendLocationView_Day_1_de.png +++ b/screenshots/de/features.location.impl.send_SendLocationView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4ec028bee93f35e79e812ae3b6319c29bbb49e3f22184db437c9ae05546b9ace -size 38189 +oid sha256:e7853a7f3e4df8122bda7f95a8f5c813b01ca4fc670e5529bee2df05d9f85ce1 +size 38178 diff --git a/screenshots/de/features.location.impl.send_SendLocationView_Day_2_de.png b/screenshots/de/features.location.impl.send_SendLocationView_Day_2_de.png index 864d79e548..00182b3b1c 100644 --- a/screenshots/de/features.location.impl.send_SendLocationView_Day_2_de.png +++ b/screenshots/de/features.location.impl.send_SendLocationView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:057899a5bd1686949708242e9b96ca798cf2d8710c65347934216f7f99e67f15 -size 34558 +oid sha256:5a76e4f8f281da0ffe84d87ca3020ec6c55bf44d0dade2fe0c7fa0873e454b12 +size 34548 diff --git a/screenshots/de/features.location.impl.send_SendLocationView_Day_3_de.png b/screenshots/de/features.location.impl.send_SendLocationView_Day_3_de.png index e3b1a4d8f6..9423b39f9c 100644 --- a/screenshots/de/features.location.impl.send_SendLocationView_Day_3_de.png +++ b/screenshots/de/features.location.impl.send_SendLocationView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:afd961db19f26c40b6811193b0f97130fd83cb7b06a4073666394c8d272c3d6c -size 19312 +oid sha256:b6f1f50d040889a65379d67fce0c5613b898d6cb094acb1aaa4dc0b92a7f657e +size 19347 diff --git a/screenshots/de/features.location.impl.send_SendLocationView_Day_4_de.png b/screenshots/de/features.location.impl.send_SendLocationView_Day_4_de.png index d2f67e9133..e827e499df 100644 --- a/screenshots/de/features.location.impl.send_SendLocationView_Day_4_de.png +++ b/screenshots/de/features.location.impl.send_SendLocationView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:104c572c42f9a3c02939446d74202f04fa7fe7f1319b906381051090b450ca35 -size 19509 +oid sha256:05358d74019ef180be62dab139701eb94e562108b56015b09f044a89f418ea7e +size 19508 diff --git a/screenshots/de/features.location.impl.show_ShowLocationView_Day_0_de.png b/screenshots/de/features.location.impl.show_ShowLocationView_Day_0_de.png index d2b37fa62d..a65b0a6525 100644 --- a/screenshots/de/features.location.impl.show_ShowLocationView_Day_0_de.png +++ b/screenshots/de/features.location.impl.show_ShowLocationView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a74ec7c80ca76d340b5677a43b54eb2f37366fb5fedbdb0309200f85817a46f0 -size 11185 +oid sha256:ac0a0c4a5f23f042f67689ddf63f9206e727fd59970589a82c5f07546646002c +size 11188 diff --git a/screenshots/de/features.location.impl.show_ShowLocationView_Day_1_de.png b/screenshots/de/features.location.impl.show_ShowLocationView_Day_1_de.png index b8dd2b8613..a801df51c3 100644 --- a/screenshots/de/features.location.impl.show_ShowLocationView_Day_1_de.png +++ b/screenshots/de/features.location.impl.show_ShowLocationView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7e861fcd4411820176481f0286ef2761d315a4e360d1e11060c0ff81b79441d -size 32748 +oid sha256:bccf267283f170756cd967a55c393c18cc5c74bd8bd0eabe2a238a3f32ff68f6 +size 32723 diff --git a/screenshots/de/features.location.impl.show_ShowLocationView_Day_2_de.png b/screenshots/de/features.location.impl.show_ShowLocationView_Day_2_de.png index 142072f53f..eeaf10476a 100644 --- a/screenshots/de/features.location.impl.show_ShowLocationView_Day_2_de.png +++ b/screenshots/de/features.location.impl.show_ShowLocationView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28ea4c839cdadaac893336a2a4d010049a16318ade2ada6bd47b836813d1549d -size 29133 +oid sha256:bbe8b535438d0625174537e66afb33ec2448358d2de9aa3d34ec050471fbdf4a +size 29110 diff --git a/screenshots/de/features.location.impl.show_ShowLocationView_Day_3_de.png b/screenshots/de/features.location.impl.show_ShowLocationView_Day_3_de.png index d2b37fa62d..a65b0a6525 100644 --- a/screenshots/de/features.location.impl.show_ShowLocationView_Day_3_de.png +++ b/screenshots/de/features.location.impl.show_ShowLocationView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a74ec7c80ca76d340b5677a43b54eb2f37366fb5fedbdb0309200f85817a46f0 -size 11185 +oid sha256:ac0a0c4a5f23f042f67689ddf63f9206e727fd59970589a82c5f07546646002c +size 11188 diff --git a/screenshots/de/features.location.impl.show_ShowLocationView_Day_4_de.png b/screenshots/de/features.location.impl.show_ShowLocationView_Day_4_de.png index 0fb5a74d8f..449afd5194 100644 --- a/screenshots/de/features.location.impl.show_ShowLocationView_Day_4_de.png +++ b/screenshots/de/features.location.impl.show_ShowLocationView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ba7c229b92eff8bfb53cf729a8dfef6ea5287e83cd6037d1f66fbd9bcf0ad76 -size 11319 +oid sha256:f6732dc9ac50b55af934890c3d3c10bb028700926ce14a8cdfcbc667975a1e85 +size 11321 diff --git a/screenshots/de/features.location.impl.show_ShowLocationView_Day_5_de.png b/screenshots/de/features.location.impl.show_ShowLocationView_Day_5_de.png index 067f9b76a8..62c2b50868 100644 --- a/screenshots/de/features.location.impl.show_ShowLocationView_Day_5_de.png +++ b/screenshots/de/features.location.impl.show_ShowLocationView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fdc4c2ee2bcb430249a629cb53f0685c0aba380cc32e127bd2fc3c48bdf69d2e -size 14918 +oid sha256:016b0b4fda7c09023987d6729eb426313d761ada684f3c2afad66eec0621618d +size 14920 diff --git a/screenshots/de/features.location.impl.show_ShowLocationView_Day_6_de.png b/screenshots/de/features.location.impl.show_ShowLocationView_Day_6_de.png index 246df83592..ddd6b6584e 100644 --- a/screenshots/de/features.location.impl.show_ShowLocationView_Day_6_de.png +++ b/screenshots/de/features.location.impl.show_ShowLocationView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6dc9fe8e6ce13b5752b36507c583f75069eabbd8c27e06e53bee1579cb0cf2b9 -size 23540 +oid sha256:f3854cf1fde4705b74af01d2e4bd69a0a545bfa6972b6f29f7ed4823c0ede6f0 +size 23548 diff --git a/screenshots/de/features.location.impl.show_ShowLocationView_Day_7_de.png b/screenshots/de/features.location.impl.show_ShowLocationView_Day_7_de.png index bee47c76cd..ac5be030e9 100644 --- a/screenshots/de/features.location.impl.show_ShowLocationView_Day_7_de.png +++ b/screenshots/de/features.location.impl.show_ShowLocationView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff97bdc5defa5188b66fec26e4693aacae40d05c7a0f50b829a187383bce8cf1 -size 26315 +oid sha256:8a975bd93798de9600bb9053dbfaaf3da9d93d288d6ba06322b184a56b75b8fd +size 26320 diff --git a/screenshots/de/features.lockscreen.impl.settings_LockScreenSettingsView_Day_0_de.png b/screenshots/de/features.lockscreen.impl.settings_LockScreenSettingsView_Day_0_de.png index b00e3db6b2..f4d77a6957 100644 --- a/screenshots/de/features.lockscreen.impl.settings_LockScreenSettingsView_Day_0_de.png +++ b/screenshots/de/features.lockscreen.impl.settings_LockScreenSettingsView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ed749c9e3b21e56071e611fb75aed1292a9b35e36e52fd86d2798b8d4271924 -size 19294 +oid sha256:34026e4f96c633b50a0a89e227f6067a21691973625db8bfb0acdd895c6dd354 +size 19304 diff --git a/screenshots/de/features.lockscreen.impl.settings_LockScreenSettingsView_Day_1_de.png b/screenshots/de/features.lockscreen.impl.settings_LockScreenSettingsView_Day_1_de.png index 1e7f9e3026..610d2a91af 100644 --- a/screenshots/de/features.lockscreen.impl.settings_LockScreenSettingsView_Day_1_de.png +++ b/screenshots/de/features.lockscreen.impl.settings_LockScreenSettingsView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2fad10b820bfc20ea81d2c33c756d8acebbf2b2e44f952145bbd21ba24dce633 -size 21629 +oid sha256:120d9d3783d3b127e42afd559523487bf467442ddf26e4d0b8503a0ccd69c0ac +size 21638 diff --git a/screenshots/de/features.lockscreen.impl.settings_LockScreenSettingsView_Day_2_de.png b/screenshots/de/features.lockscreen.impl.settings_LockScreenSettingsView_Day_2_de.png index 73d3587019..254a8ca813 100644 --- a/screenshots/de/features.lockscreen.impl.settings_LockScreenSettingsView_Day_2_de.png +++ b/screenshots/de/features.lockscreen.impl.settings_LockScreenSettingsView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7415217e713fd3f3828942be2fefa87c9f743c484d50ec60ea1c0a04949bfb65 -size 32895 +oid sha256:c1a9677e805c288bdf53400a985fd6b627088b503ea19ad741bd02973263f167 +size 32875 diff --git a/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_0_de.png b/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_0_de.png index 81d00f84b0..c79f74d6d7 100644 --- a/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_0_de.png +++ b/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:417247c7f2485fb6f30cb3182b008d7f7c924c52930ad0db3b1113e5e641803a -size 30799 +oid sha256:e4c89b14ceb38ef06608b66930004f79964f61f73c0ee1cd15897f96c6918a2b +size 30819 diff --git a/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_1_de.png b/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_1_de.png index c292b6e7c9..47a6946820 100644 --- a/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_1_de.png +++ b/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8258316042dfcb5f43fdcecf484a0c920a5b5269baf29ba4f00103c89d5308e8 -size 30385 +oid sha256:676b3fd586f5e7fc23feafea118a5e6f32d8c864d75676ca7667e87402da8dcf +size 30404 diff --git a/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_2_de.png b/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_2_de.png index 3650e95272..490fdfc456 100644 --- a/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_2_de.png +++ b/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ccc829c98481c9af5680b23b50dec0c8abd5ba7fa4bb13c2713822438a7d0bc -size 32019 +oid sha256:5733b7e9ec399adf8ee44bff0766d15637b3570254fcc696cdaac93119dd62f9 +size 32038 diff --git a/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_3_de.png b/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_3_de.png index e67751b3c7..99599de08f 100644 --- a/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_3_de.png +++ b/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:339e4dc13f6074ca938d6752878b54752f6c9002c92ebf291136079579f85bd5 -size 27776 +oid sha256:5063256beed2a629b85311e76383b5ffd443f29493e35e3a3c5c4b08a921a506 +size 27754 diff --git a/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_4_de.png b/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_4_de.png index a7a98fafb1..92b90f6150 100644 --- a/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_4_de.png +++ b/screenshots/de/features.lockscreen.impl.setup.pin_SetupPinView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa5cf8e3bbd7116f1c8c27ac29d956929ed6a52e7b2b9a0cc425709ed83c3c1f -size 32583 +oid sha256:dcfd554e8853810fe9d6cf9acfea199e997f2ebd918b03cbb0994831b34848b1 +size 32563 diff --git a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_0_de.png b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_0_de.png index a41d3d7552..9017f4f468 100644 --- a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_0_de.png +++ b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e5b386b0228838abb900fa711998208c05f77dc87f760c43397058fcb3d02c19 -size 22679 +oid sha256:32025dce1538727710caf62b4b9bb2eb027eb639157ff09b7c478616ef38aac9 +size 22674 diff --git a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_1_de.png b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_1_de.png index 91bb9f0d72..4794d08d63 100644 --- a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_1_de.png +++ b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee063246ef3d9f34f75806a5b6e772aaa9a60259d9d5fa19b1be5c0a932880f8 -size 22308 +oid sha256:6cbe5f05c4293284c0668baa8ede841abcbcdfbaf79227040e9a341ab95f3f55 +size 22302 diff --git a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_2_de.png b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_2_de.png index 1a29ac6b2f..8ec35e8d73 100644 --- a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_2_de.png +++ b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dce56693ed4a6473f477e85ff548630bb72a0c1aa5f063d02abbb7c6c151013d -size 23375 +oid sha256:261596f177bf0e1dec85a351faa140cccd08225fffb7e4418c3809929b561330 +size 23371 diff --git a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_3_de.png b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_3_de.png index 2c73a48c43..46a40b50a2 100644 --- a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_3_de.png +++ b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d5c7d1a7aef1ccceaa1837fd3483bf1067158a9edd0fe7a03aac74d313c3874 -size 37439 +oid sha256:79d4ee84ccc1ecd11b1c3f7653bda15aaf27d572a2000936027a718b71675337 +size 37438 diff --git a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_4_de.png b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_4_de.png index 78c17a0adb..c4b3ee574a 100644 --- a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_4_de.png +++ b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91e32f6186bf16d4880b31b0648b211b9ea228cfc4fc74e0cdfdb6e66d3d32bf -size 20083 +oid sha256:ee69cce0a6a7e8a65f1c9d5901cbb73be21d23a0b6600312f0ae0b42218cb3b2 +size 20080 diff --git a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_5_de.png b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_5_de.png index 15bfc89256..7872dd1e63 100644 --- a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_5_de.png +++ b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9706510016738e2b74b143c97deba9c7cfe3ecf4735b9f033c0cf4d0522005b0 +oid sha256:9840735ba05fa48377151a0613a1951f0058be4ed7f471ed63dd1dac11f2b7c0 size 34216 diff --git a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_6_de.png b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_6_de.png index 8c25e8ea0a..2e73dc9653 100644 --- a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_6_de.png +++ b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ccaa6e9d59294afe6dbb65c2bb5e207bf81d3f14e71752518df78316434d706 +oid sha256:922c9e382f6cd0cda24766089320405d13a946c1f77052f088022128d7fb1d17 size 23541 diff --git a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_7_de.png b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_7_de.png index 9e2974f594..ff67c21880 100644 --- a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_7_de.png +++ b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:02cbbc16f07b45feeb638195be476483d68dd652462f8b069a6e1492f66d0089 +oid sha256:240850064c8c692a722b08292c47746b6709c6b7a2e88ed720599c76e1044f54 size 25068 diff --git a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_0_de.png b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_0_de.png index 019536128b..e09362dd03 100644 --- a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_0_de.png +++ b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:007d4fbd2d36927ac9b27063b08cfbded0085b58bc4e5d9fbf1e4e1f84d2dd62 -size 38394 +oid sha256:fa4cf17898cfd78c295bcacb9df84a30e2bde4a3d819cce2a737c887976d1a7e +size 38448 diff --git a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_1_de.png b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_1_de.png index 4cf461ffff..ed1ba7c97d 100644 --- a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_1_de.png +++ b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:38dbf516ba28c75aa91b45e3e4f4e3afb1a6959038177bbbe5b0650aa42d1fd3 -size 38798 +oid sha256:8066d263a899ca57f633bd2d6792c249f6a5674ca523fd1afec337b074f854b3 +size 38849 diff --git a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_2_de.png b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_2_de.png index 35c9c8c261..0045d8f0c3 100644 --- a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_2_de.png +++ b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e77abcfd3e6c9214efe1ef6fa40cb0dbc19a4eb7da184d1b4084479fe7d0b57e -size 39081 +oid sha256:b002741ad82c48b8b8e48303cf7473d1da596437809119093e004588a50e4bf7 +size 39136 diff --git a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_3_de.png b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_3_de.png index f12bab73c8..f63895de69 100644 --- a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_3_de.png +++ b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:727b36b17214000ed929f94b6bcbeb0da66278031e1ffee2fdf547201f542f2f -size 44275 +oid sha256:60b61665c7356642f4775acb9968d01d43817dd67801afca9ad5b60d6b570b0f +size 44321 diff --git a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_4_de.png b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_4_de.png index 35649b1c72..b2a0b0915b 100644 --- a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_4_de.png +++ b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1be354458cb21172f8d2e93aacc98228a074536f8dc3dce734fa81446f7f9c47 -size 35793 +oid sha256:27b58735f417e5883f45ff9a40fdffc8250f1db728ac0de7410d0f8d7021e5e0 +size 35846 diff --git a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_5_de.png b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_5_de.png index 78da42de5f..81789e7884 100644 --- a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_5_de.png +++ b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:acc9e12a77d389bbcb17595e5d50f7efd99140eed5935f5c90d9ad82693446df -size 41195 +oid sha256:ebe316bccee2ef6045a8a3e77d48c633ad6855dcbec3f7fd3f69071abddde135 +size 41246 diff --git a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_6_de.png b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_6_de.png index 7d8f9e851d..5405dde38b 100644 --- a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_6_de.png +++ b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:63a908bfa15be2c537c5d1a20d3066fcfca4b5a60256696ad819fb45b59d7a76 -size 32088 +oid sha256:807ced3cff6d0a0f70738bd4723be16df848f61fb7a8c594c3c5261a4b9267a2 +size 32133 diff --git a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_7_de.png b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_7_de.png index 9f22132175..3368a795a0 100644 --- a/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_7_de.png +++ b/screenshots/de/features.lockscreen.impl.unlock_PinUnlockView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88c5747df737c777ca3e1fb039e6507489d2db418d46d970f25b7380ff0dfb40 -size 32282 +oid sha256:014e8208a369fcc39f331a752f4c1f7039ef966d8af12cefb46fded6d7c07d28 +size 32326 diff --git a/screenshots/de/features.login.impl.accountprovider_AccountProviderOtherView_Day_0_de.png b/screenshots/de/features.login.impl.accountprovider_AccountProviderOtherView_Day_0_de.png index c1f8569265..b25483cf6e 100644 --- a/screenshots/de/features.login.impl.accountprovider_AccountProviderOtherView_Day_0_de.png +++ b/screenshots/de/features.login.impl.accountprovider_AccountProviderOtherView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90228e7dcbd071862d9a57bf5b2701630327fc93303587b61abe61b55fde86fb -size 6965 +oid sha256:2589bf106f1ded927f3c802e514af09d738f611f5eaa90f583e29efa17d11f87 +size 6974 diff --git a/screenshots/de/features.login.impl.changeserver_ChangeServerView_Day_5_de.png b/screenshots/de/features.login.impl.changeserver_ChangeServerView_Day_5_de.png new file mode 100644 index 0000000000..6777ba1d9d --- /dev/null +++ b/screenshots/de/features.login.impl.changeserver_ChangeServerView_Day_5_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2a1a1fd4c356ab8b1ec5f98a81d9ef40a033b694378f03730a05341b4bde2b2 +size 30879 diff --git a/screenshots/de/features.login.impl.login_LoginModeView_Day_5_de.png b/screenshots/de/features.login.impl.login_LoginModeView_Day_5_de.png index d8ed4858b8..6777ba1d9d 100644 --- a/screenshots/de/features.login.impl.login_LoginModeView_Day_5_de.png +++ b/screenshots/de/features.login.impl.login_LoginModeView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c0e1ea6e6a244f7b1329aaa4ef699d059e9fd137c58a8e78c0e5fc43f2d45832 -size 18149 +oid sha256:c2a1a1fd4c356ab8b1ec5f98a81d9ef40a033b694378f03730a05341b4bde2b2 +size 30879 diff --git a/screenshots/de/features.login.impl.login_LoginModeView_Day_6_de.png b/screenshots/de/features.login.impl.login_LoginModeView_Day_6_de.png new file mode 100644 index 0000000000..d8ed4858b8 --- /dev/null +++ b/screenshots/de/features.login.impl.login_LoginModeView_Day_6_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c0e1ea6e6a244f7b1329aaa4ef699d059e9fd137c58a8e78c0e5fc43f2d45832 +size 18149 diff --git a/screenshots/de/features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_0_de.png b/screenshots/de/features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_0_de.png index 1c90dfe136..f5e194b762 100644 --- a/screenshots/de/features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_0_de.png +++ b/screenshots/de/features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce9f67bc654d9248b1aa456ec2593c59b6a310ed946a665bce70b8b637004054 -size 55679 +oid sha256:5b4d9121e27dadf76263ecfeb97d799db3e96ed7f2b3bdafa45e6b2a4775514e +size 55706 diff --git a/screenshots/de/features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_1_de.png b/screenshots/de/features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_1_de.png index e85c13a22f..96a34d853b 100644 --- a/screenshots/de/features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_1_de.png +++ b/screenshots/de/features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd69df898ead13a5db4c057bbdbf27ceaaea8666952f464ae75bd4c98e9f036e -size 52760 +oid sha256:6f800532b259209d2ced3a0f7bc0ca7df3acfd9481ecc4982f93610ec7caccd7 +size 52790 diff --git a/screenshots/de/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_0_de.png b/screenshots/de/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_0_de.png index 1d0be7b05e..e6c059af42 100644 --- a/screenshots/de/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_0_de.png +++ b/screenshots/de/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:02959fe7fd9afda6b339165eae70971bd007b9b3c5f3fb5a042c6a03b28b5571 -size 22391 +oid sha256:5427c0d2def74282d09a97ecf1c1f0d110a520d4defb5caa26e24c45a9cd62d6 +size 22399 diff --git a/screenshots/de/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_1_de.png b/screenshots/de/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_1_de.png index c4f94e01a0..55531fbea5 100644 --- a/screenshots/de/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_1_de.png +++ b/screenshots/de/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:89323d975ec3f4962317bb1763cc58e4e548f38538b6869cdd14b26f55b0698d -size 23293 +oid sha256:3e67cdb2ec597cfd13554de0bc654c701606f489f01aeb6447db6b9f51b4a167 +size 23298 diff --git a/screenshots/de/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_2_de.png b/screenshots/de/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_2_de.png index 8b3a874a51..818e373c09 100644 --- a/screenshots/de/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_2_de.png +++ b/screenshots/de/features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f267018e1a5eb31f728a724b46a086b1117d28e71792364b2a9e90d5093c421 -size 23947 +oid sha256:63de22015cc395b9429ce03cf0f487520f39b294f267f873b11d7651c8dda4ee +size 23952 diff --git a/screenshots/de/features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_0_de.png b/screenshots/de/features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_0_de.png index d1f5a7496b..be6b282de4 100644 --- a/screenshots/de/features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_0_de.png +++ b/screenshots/de/features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a9d211cfab821ec51870368af03d20cfbaa30c6a439bbfb27e1ce37c52533fb -size 39101 +oid sha256:9af932ad48f8c3f79b993e6c08a4b86711fc50cdd970368ffa0c45d83fc67f6d +size 39092 diff --git a/screenshots/de/features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_1_de.png b/screenshots/de/features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_1_de.png index 804bc0e00d..1dee6b69ea 100644 --- a/screenshots/de/features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_1_de.png +++ b/screenshots/de/features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d0a901c662f5595c50485e40c212b66528f7e1ecf19ce844c8360a6b22d0572 -size 39965 +oid sha256:0c2b946fddee79fa6c0bf6de966e8bfcfdbaa1b5d2827dd597e7355f7cb8eb5d +size 39950 diff --git a/screenshots/de/features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_2_de.png b/screenshots/de/features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_2_de.png index 49eb1c3e50..aa1357c481 100644 --- a/screenshots/de/features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_2_de.png +++ b/screenshots/de/features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61d0f8e341e76fee533117b9724aec1e299519e7cff827d43b0977e7d6845ab9 -size 41284 +oid sha256:8f4994f2ebdf2f17efe55bad43cc3e5eb11596f7c70cbd06dc8314053b62b8e9 +size 41281 diff --git a/screenshots/de/features.login.impl.screens.createaccount_CreateAccountView_Day_0_de.png b/screenshots/de/features.login.impl.screens.createaccount_CreateAccountView_Day_0_de.png index ecce48b330..4c3049a595 100644 --- a/screenshots/de/features.login.impl.screens.createaccount_CreateAccountView_Day_0_de.png +++ b/screenshots/de/features.login.impl.screens.createaccount_CreateAccountView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c60958307eff3de5780e439eb7cc4251b3995629253a94fae00bd8a4c444fcc -size 13398 +oid sha256:3dbb21dbcd474756d2f6ae7d8dafc76638d38758059d11224050484db5d563b3 +size 13421 diff --git a/screenshots/de/features.login.impl.screens.createaccount_CreateAccountView_Day_1_de.png b/screenshots/de/features.login.impl.screens.createaccount_CreateAccountView_Day_1_de.png index fa83d7cd5f..e9df8b4415 100644 --- a/screenshots/de/features.login.impl.screens.createaccount_CreateAccountView_Day_1_de.png +++ b/screenshots/de/features.login.impl.screens.createaccount_CreateAccountView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8904cd72552ae43c390aa8d488398aa6c5d5df6e3b4420bf10b217d0587ecb4f -size 13513 +oid sha256:b454c821bd0825c76629230b4089c9b39d4f4b0239ed6236671eec226fe1b9f0 +size 13538 diff --git a/screenshots/de/features.login.impl.screens.createaccount_CreateAccountView_Day_2_de.png b/screenshots/de/features.login.impl.screens.createaccount_CreateAccountView_Day_2_de.png index 640b102480..96589b8f00 100644 --- a/screenshots/de/features.login.impl.screens.createaccount_CreateAccountView_Day_2_de.png +++ b/screenshots/de/features.login.impl.screens.createaccount_CreateAccountView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5cbd1f46ac606ff3608b805c86651a676a8ebff6a74e946c6d6912417c572f69 -size 13484 +oid sha256:66fefc519b20374d3cf7e4bbee045733c2eb15df79a0cf54ffd0cc926d134679 +size 13465 diff --git a/screenshots/de/features.login.impl.screens.createaccount_CreateAccountView_Day_3_de.png b/screenshots/de/features.login.impl.screens.createaccount_CreateAccountView_Day_3_de.png index 61d358271f..e8812b49cc 100644 --- a/screenshots/de/features.login.impl.screens.createaccount_CreateAccountView_Day_3_de.png +++ b/screenshots/de/features.login.impl.screens.createaccount_CreateAccountView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f55612c2279875c0d128d124097f6ed36378e16bf4d0a2ce27126e3f76b57147 -size 15587 +oid sha256:573bd2192b81bcd2c3d52f6367db452d2e3c10de55be107752b03cc1a199f58d +size 15570 diff --git a/screenshots/de/features.login.impl.screens.loginpassword_LoginPasswordView_Day_0_de.png b/screenshots/de/features.login.impl.screens.loginpassword_LoginPasswordView_Day_0_de.png index 5411eb86e6..f718eb02bc 100644 --- a/screenshots/de/features.login.impl.screens.loginpassword_LoginPasswordView_Day_0_de.png +++ b/screenshots/de/features.login.impl.screens.loginpassword_LoginPasswordView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c5e226015f1d493ec4cbfb1b11d7112cd4826190a60df0f9286a0f9c0f51cfad -size 38621 +oid sha256:23a64ef6fbb4578b07b7d50a9e78170343377d58484ce55491fb12ab8ee4fa38 +size 38637 diff --git a/screenshots/de/features.login.impl.screens.loginpassword_LoginPasswordView_Day_1_de.png b/screenshots/de/features.login.impl.screens.loginpassword_LoginPasswordView_Day_1_de.png index aee1a55cd2..a7abfb9d84 100644 --- a/screenshots/de/features.login.impl.screens.loginpassword_LoginPasswordView_Day_1_de.png +++ b/screenshots/de/features.login.impl.screens.loginpassword_LoginPasswordView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92864d613ce5a68ed305bd0b1e337ae2b3fd9aa1c3ad1bdf625f062a25e50824 -size 39815 +oid sha256:1cf7ab528c61c3498d139eaaaa24c84a33b12279f27debd9ef0092ff95c6b010 +size 39825 diff --git a/screenshots/de/features.login.impl.screens.loginpassword_LoginPasswordView_Day_2_de.png b/screenshots/de/features.login.impl.screens.loginpassword_LoginPasswordView_Day_2_de.png index 8c48a80d25..6b106a70e7 100644 --- a/screenshots/de/features.login.impl.screens.loginpassword_LoginPasswordView_Day_2_de.png +++ b/screenshots/de/features.login.impl.screens.loginpassword_LoginPasswordView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e73952f62b9f4addbf6c66e6929da91539a451660a6617ac53f995534726f52d -size 30406 +oid sha256:83ca19990a58cd0859f5dd1aa9c2b8bb898870479fc021fa7d88c5e64c7809d2 +size 30370 diff --git a/screenshots/de/features.login.impl.screens.onboarding_OnBoardingView_Day_7_de.png b/screenshots/de/features.login.impl.screens.onboarding_OnBoardingView_Day_7_de.png index 247acb90dc..6c0221d4ca 100644 --- a/screenshots/de/features.login.impl.screens.onboarding_OnBoardingView_Day_7_de.png +++ b/screenshots/de/features.login.impl.screens.onboarding_OnBoardingView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5dd50e66c81b2cdd21c7d3ebe9fa1ae1c56c43c271a9ffa57a6868b9e19d230e -size 22756 +oid sha256:8049495bd9db420baa3303f0fbc69e97e2f35f1078f5e55c846e95ce5c960a96 +size 22768 diff --git a/screenshots/de/features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_0_de.png b/screenshots/de/features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_0_de.png index 7a4dd6c1b5..ace1448a65 100644 --- a/screenshots/de/features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_0_de.png +++ b/screenshots/de/features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36a937b32ea014cc94dc00b14da82549ab4d8cc2c2de89f5eb490170c87cd5d4 -size 38356 +oid sha256:ba54da623ad58165e81d7becf04a0214ea599cac9a2d4156a0badfbff591bd6f +size 38354 diff --git a/screenshots/de/features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_1_de.png b/screenshots/de/features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_1_de.png index d78df8c9e6..54d9eacc08 100644 --- a/screenshots/de/features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_1_de.png +++ b/screenshots/de/features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e9bf45b8d4d39d6b5fc70852b4da0b2e2d7db438303ce4471c396449f0317c46 -size 35373 +oid sha256:640f78e1a5164099db2310b47225a13ebfafab3805e9596f5aa42636d153bb84 +size 35372 diff --git a/screenshots/de/features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_2_de.png b/screenshots/de/features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_2_de.png index 106d8548d6..beaaad553f 100644 --- a/screenshots/de/features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_2_de.png +++ b/screenshots/de/features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db2df4d775b8b14a54e41e874d93d242d0e72fae3014e5c73b92fde96264e153 -size 37393 +oid sha256:8523e758a62b02b54a2d61cb1501c198ec47a21a1a89249433d6a6769c6b5dd0 +size 37387 diff --git a/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_0_de.png b/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_0_de.png index 8ae128b8bf..65573d908f 100644 --- a/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_0_de.png +++ b/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de48b52dabae33b439d732cd6ffa5eda5013ff5118cdf12392565cbbe1d7b469 -size 24998 +oid sha256:3fc70436b6ccb1afd77bed07598c020796341aa100b1eab6dc5b00868297dca6 +size 24990 diff --git a/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_1_de.png b/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_1_de.png index f3855d5d23..c7cf5a7efa 100644 --- a/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_1_de.png +++ b/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:93a0d0551046745aa02e1529fe4f27fade32853e3ec7f8ed7573b0bde864feb5 -size 22041 +oid sha256:50c28eb8481b1101f75bf8341481ead0e77daf116207d27ddc5c95316daf8e41 +size 22035 diff --git a/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_2_de.png b/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_2_de.png index 031d4b02f8..4468511356 100644 --- a/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_2_de.png +++ b/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ca8caeac8dc4d9e4e405daa0377aaab0cf3acaa9f3a744c284450e9f63f9deb -size 29400 +oid sha256:20558650fc6a3859569d52baaab1c8af2a68e2944fd772f0d62ced5c1aa44208 +size 29397 diff --git a/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_3_de.png b/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_3_de.png index ec0f4bbe6e..cc41086b2a 100644 --- a/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_3_de.png +++ b/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5e20cccecd67e059b0f685f1716ac5f575b7d392ddb1fc752a61a04133565c7 -size 38714 +oid sha256:8b827b26498db3df6bf73b4a66b50be634d7c484c7078791c7a9caaf2f61eb9e +size 38702 diff --git a/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_4_de.png b/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_4_de.png index 6cf5423d67..33122e6dea 100644 --- a/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_4_de.png +++ b/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a2b7da8d3fca41ce7908dcca66bc8ed5b4a00865e5999698267fcaa15f72ab41 -size 68949 +oid sha256:b39882c9c6d9920d0f76f3b80599b7b41cc6eab4480c6a10f09cf0992acbf203 +size 68940 diff --git a/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_5_de.png b/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_5_de.png index c7b6fe4608..a5eae154ec 100644 --- a/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_5_de.png +++ b/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a9f40df5c43f367b5851000f67a33093eb7d88772a8c0f7e698222aaec816d9 -size 21736 +oid sha256:dfa5cb9c32488940567a9db89a31b056095377a66a3c699e9137b42a5638b44f +size 21727 diff --git a/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_6_de.png b/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_6_de.png index 4cba75915b..5f79162d1b 100644 --- a/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_6_de.png +++ b/screenshots/de/features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df30cbbc9165d7b181d8b436a3922a63ded70642ea9e83a2027bd6425496d28d -size 23972 +oid sha256:c184323fa1b1d87f46be6f49bf983f7d4048b5547906531b452924ee89110d30 +size 23962 diff --git a/screenshots/de/features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_0_de.png b/screenshots/de/features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_0_de.png index 80c19bbd5e..cda0b53b57 100644 --- a/screenshots/de/features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_0_de.png +++ b/screenshots/de/features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:405463cb5f8c7a1fa1d05be06a392455644425a65656596c2dc804f1aa003a5a -size 57424 +oid sha256:a1b930e7b4ebb1e55e812a851c62994b846fcfc204df812e24982205f7386f2a +size 57447 diff --git a/screenshots/de/features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_1_de.png b/screenshots/de/features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_1_de.png index 1c97360bff..fd58392211 100644 --- a/screenshots/de/features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_1_de.png +++ b/screenshots/de/features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ae87ae2b64c271d0e43e9b51502eae24e5f3d917f765761e6840f04b3a417b5 -size 50945 +oid sha256:f4f08261d3e9dd275b271ab07cb9015547abd0cad6b90ed1e11e0c9d02fde4b2 +size 50926 diff --git a/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_0_de.png b/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_0_de.png index 1aaea102cf..d50a67ef38 100644 --- a/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_0_de.png +++ b/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23c87cc1e2c91d5fc1795006e59930beec147ae40a63a93431f3910ef09cf58c -size 14509 +oid sha256:a12dbc4f2fb3d12b1848e2ac4e079522613df2493d0a416f2f47827eb0daa322 +size 16502 diff --git a/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_1_de.png b/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_1_de.png index 9ad95d1102..c98fa292ed 100644 --- a/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_1_de.png +++ b/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62ea7144b8df68b9af8671c96d8d726e7fdcf00a6187911bdc4d092dfc80534a -size 19385 +oid sha256:1e6e8cd76fdbb3835b0622593e099ed61d526de35c6d20b6547cea83d6aa8e19 +size 20741 diff --git a/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_2_de.png b/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_2_de.png index 03b1051f57..305f24b9d7 100644 --- a/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_2_de.png +++ b/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1fd74994f9ea6cf24a6e05ebf2e11b5b0bf3c13d33d43b4c923d428cf5761230 -size 30477 +oid sha256:a6a773294a47272bcc0333e7a325e0609ef53d576eca1d40422b0855cc48dae7 +size 31739 diff --git a/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_3_de.png b/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_3_de.png index b81d99193a..877598f1e1 100644 --- a/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_3_de.png +++ b/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e724cbb9eef1073e6dbbe542da5455cf1991f2520f85516ad5294d2ee2eb9e27 -size 38989 +oid sha256:500c0857431f0604b828e50751b6250e8349559a39b73f201ff5e7eb4fe0239c +size 40262 diff --git a/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_4_de.png b/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_4_de.png index 31bcf59ca2..46cb868856 100644 --- a/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_4_de.png +++ b/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:464b9722b90078cbc7391592ed372655c9da484b1c7376f8a65587d79ef750fe -size 34092 +oid sha256:60ee229ac4cc0712b8afba1d678cebc88bc1f0abfe133bf4e4311f05b0fa6c28 +size 35288 diff --git a/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_5_de.png b/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_5_de.png index 653caec071..cef37f8936 100644 --- a/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_5_de.png +++ b/screenshots/de/features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce738d099663f704b97259ac841dda2750f98d11670228a5e3b851a409d6499e -size 32198 +oid sha256:848b24931a1da8f4e2b3dfdbb6b8e3f09d71dee356507eb5f46e08e74ffe94a5 +size 33464 diff --git a/screenshots/de/features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_0_de.png b/screenshots/de/features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_0_de.png index e720188b16..2cedb5846a 100644 --- a/screenshots/de/features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_0_de.png +++ b/screenshots/de/features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:340b8755836b80fa0a0e19c01b1c454f0b5d3252deab7a21bac7426b94d041cf -size 27400 +oid sha256:cc98a0c12fcbc1d672ea731ff6b219d11349f4c52d2faca460e7e6925b81169d +size 27440 diff --git a/screenshots/de/features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_1_de.png b/screenshots/de/features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_1_de.png index 22192e8b92..d1025cd434 100644 --- a/screenshots/de/features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_1_de.png +++ b/screenshots/de/features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:49001b1985590e67ee3db2fae8c221b4f11016d9a7806ed68eb48ce04c30ee75 -size 59981 +oid sha256:ae928b98d163bb95921fda90ea37df1bd5ab15b02216f98ef5b43d772648a4d0 +size 60034 diff --git a/screenshots/de/features.logout.impl_AccountDeactivationView_Day_0_de.png b/screenshots/de/features.logout.impl_AccountDeactivationView_Day_0_de.png index 6e765c4d2b..cd21f9ed18 100644 --- a/screenshots/de/features.logout.impl_AccountDeactivationView_Day_0_de.png +++ b/screenshots/de/features.logout.impl_AccountDeactivationView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f80d0892014051edb5e84dfe11c6ee389c7fe168143b209b8272c6426582063b -size 91934 +oid sha256:d4749341fab25d56ceb80a838ae7769f2bc65f7640e39533c88ed1bdd480d824 +size 91956 diff --git a/screenshots/de/features.logout.impl_AccountDeactivationView_Day_1_de.png b/screenshots/de/features.logout.impl_AccountDeactivationView_Day_1_de.png index 978885749c..0e5669f7a2 100644 --- a/screenshots/de/features.logout.impl_AccountDeactivationView_Day_1_de.png +++ b/screenshots/de/features.logout.impl_AccountDeactivationView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58b9692e7ca3fe21d0b7d99d29a57d5561f0132cc8a051e58a15164f7d840f22 -size 91634 +oid sha256:ff0fd10d253cd4f21870541d2ac4738180267bb5a2ef4e96cded9e47b0923200 +size 91663 diff --git a/screenshots/de/features.logout.impl_AccountDeactivationView_Day_2_de.png b/screenshots/de/features.logout.impl_AccountDeactivationView_Day_2_de.png index 52bed8eca4..b111f01643 100644 --- a/screenshots/de/features.logout.impl_AccountDeactivationView_Day_2_de.png +++ b/screenshots/de/features.logout.impl_AccountDeactivationView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a9978fc6a551e92d64880cebac69380fa57cd793e790529db023111ea1f407c -size 79754 +oid sha256:2b2fec41e0b4a6f1e8bd8d4d4cdc6a0923c2df9f74e65f83b6ddab58fde6136a +size 79733 diff --git a/screenshots/de/features.logout.impl_AccountDeactivationView_Day_3_de.png b/screenshots/de/features.logout.impl_AccountDeactivationView_Day_3_de.png index 1f348e63fb..58a213486c 100644 --- a/screenshots/de/features.logout.impl_AccountDeactivationView_Day_3_de.png +++ b/screenshots/de/features.logout.impl_AccountDeactivationView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11460a94377563f3d31bd808e562ced36f87768d2849624fc4f98fe2ab1ae46f -size 70252 +oid sha256:164f3d77fb45dab07038149c6a7082e2fc6a81b4c445720cf66fc0b30e8c474f +size 70219 diff --git a/screenshots/de/features.logout.impl_AccountDeactivationView_Day_4_de.png b/screenshots/de/features.logout.impl_AccountDeactivationView_Day_4_de.png index f53bb77866..1abf890078 100644 --- a/screenshots/de/features.logout.impl_AccountDeactivationView_Day_4_de.png +++ b/screenshots/de/features.logout.impl_AccountDeactivationView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df52787c1b88af53738293ee058b67ddd08f78c857c2e3af3f006d12626cd250 -size 61807 +oid sha256:80eb9b831176e211b8bbf7651ef76111ae11bd34e6c71eb41727c46a3d6c17a9 +size 61787 diff --git a/screenshots/de/features.logout.impl_LogoutView_Day_0_de.png b/screenshots/de/features.logout.impl_LogoutView_Day_0_de.png index f881738b4d..b8d08c2ade 100644 --- a/screenshots/de/features.logout.impl_LogoutView_Day_0_de.png +++ b/screenshots/de/features.logout.impl_LogoutView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3cff9a788308620d142f7feda91903d256520c7b86a8ff30e52fdb08454b2787 -size 11483 +oid sha256:53348dcb9da01db38dec34fc8b11893276a867aa84e18d5b0c1c87f0ab282be3 +size 11509 diff --git a/screenshots/de/features.logout.impl_LogoutView_Day_10_de.png b/screenshots/de/features.logout.impl_LogoutView_Day_10_de.png index 767dc73fe4..ee55736313 100644 --- a/screenshots/de/features.logout.impl_LogoutView_Day_10_de.png +++ b/screenshots/de/features.logout.impl_LogoutView_Day_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3faff786fbadbc9d1fb919c0c9bb079b035d2719eb9719801cd075509b06bfce -size 31228 +oid sha256:85cfeed43ec40bb849282f25c63740903a4e373e26f7da97fe5e5df03b934065 +size 31252 diff --git a/screenshots/de/features.logout.impl_LogoutView_Day_11_de.png b/screenshots/de/features.logout.impl_LogoutView_Day_11_de.png index ba34786195..ae73822529 100644 --- a/screenshots/de/features.logout.impl_LogoutView_Day_11_de.png +++ b/screenshots/de/features.logout.impl_LogoutView_Day_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b71f67aac5314063bed0e3cff4661a68fef37383df2525f67ccaef1aa28b45e2 -size 36602 +oid sha256:7b7487fceb9ad5cfc1625720cbcd651b123ea6639ff1d287cebc50bf7b2c1582 +size 36626 diff --git a/screenshots/de/features.logout.impl_LogoutView_Day_1_de.png b/screenshots/de/features.logout.impl_LogoutView_Day_1_de.png index 7a80f64d0b..d4906f3b2a 100644 --- a/screenshots/de/features.logout.impl_LogoutView_Day_1_de.png +++ b/screenshots/de/features.logout.impl_LogoutView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:76aee99a5482ab0036faa55dfae992f219e41be53096266677aa65dd9cfce719 -size 47491 +oid sha256:58cc7cb5b8bd73fe4e27d50f9655f61b588e85ae8bc6c4475c98b65ca623f80e +size 47519 diff --git a/screenshots/de/features.logout.impl_LogoutView_Day_2_de.png b/screenshots/de/features.logout.impl_LogoutView_Day_2_de.png index 7deccb7a51..d50789da96 100644 --- a/screenshots/de/features.logout.impl_LogoutView_Day_2_de.png +++ b/screenshots/de/features.logout.impl_LogoutView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c58d212eeb59ed0a17a8a38e886192a519ed6ea3d868d5894175a3ecb004ca72 -size 32851 +oid sha256:23eb7945c08addad25570f5429d8822d21a27a96f9e9dc36d319eae6e5286d69 +size 32875 diff --git a/screenshots/de/features.logout.impl_LogoutView_Day_3_de.png b/screenshots/de/features.logout.impl_LogoutView_Day_3_de.png index 7a80f64d0b..d4906f3b2a 100644 --- a/screenshots/de/features.logout.impl_LogoutView_Day_3_de.png +++ b/screenshots/de/features.logout.impl_LogoutView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:76aee99a5482ab0036faa55dfae992f219e41be53096266677aa65dd9cfce719 -size 47491 +oid sha256:58cc7cb5b8bd73fe4e27d50f9655f61b588e85ae8bc6c4475c98b65ca623f80e +size 47519 diff --git a/screenshots/de/features.logout.impl_LogoutView_Day_4_de.png b/screenshots/de/features.logout.impl_LogoutView_Day_4_de.png index b3e323af1f..17d01e6734 100644 --- a/screenshots/de/features.logout.impl_LogoutView_Day_4_de.png +++ b/screenshots/de/features.logout.impl_LogoutView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a7eeac5aedd7c6de167cb96908a7b62f8dd066faca3ce33b6132df8c48dd1a97 -size 26021 +oid sha256:e485917141e850090ed9429510e83d31c532c14afce03d88bff5582f67d48c53 +size 26002 diff --git a/screenshots/de/features.logout.impl_LogoutView_Day_5_de.png b/screenshots/de/features.logout.impl_LogoutView_Day_5_de.png index 3fa995c42c..7a4db3321a 100644 --- a/screenshots/de/features.logout.impl_LogoutView_Day_5_de.png +++ b/screenshots/de/features.logout.impl_LogoutView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06e895ab0f6e19dfca98efdc6f2c32f8bba1b7c71f57b44eae9e5970528172cb -size 17009 +oid sha256:89fdefeb662bdc04365a8a641764adc7e01c381b6b39de28f7a4b33cb4ba81c2 +size 16987 diff --git a/screenshots/de/features.logout.impl_LogoutView_Day_6_de.png b/screenshots/de/features.logout.impl_LogoutView_Day_6_de.png index d97f248616..da900ef8ba 100644 --- a/screenshots/de/features.logout.impl_LogoutView_Day_6_de.png +++ b/screenshots/de/features.logout.impl_LogoutView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dbb085f18507ad5448a6614bf2823879e7e6497a52eb9feb4265a3e717030b8f -size 27420 +oid sha256:6739f58e3cd8565be730d1e7a4c7afc470b1ae9dccd31f882cd9700c63623e79 +size 27404 diff --git a/screenshots/de/features.logout.impl_LogoutView_Day_7_de.png b/screenshots/de/features.logout.impl_LogoutView_Day_7_de.png index b85e5272e8..515a75d8e5 100644 --- a/screenshots/de/features.logout.impl_LogoutView_Day_7_de.png +++ b/screenshots/de/features.logout.impl_LogoutView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c0d4b1c29b4130c1301634cda4ac0c9bcf6ec8f7ca4703b3265bd51a93e58a47 -size 41402 +oid sha256:2b90aa079163cc02c1718083e06370e35aacd83af23d62b24ded7953ab1ac67b +size 41432 diff --git a/screenshots/de/features.logout.impl_LogoutView_Day_8_de.png b/screenshots/de/features.logout.impl_LogoutView_Day_8_de.png index 787d064a81..f6ec6c02f6 100644 --- a/screenshots/de/features.logout.impl_LogoutView_Day_8_de.png +++ b/screenshots/de/features.logout.impl_LogoutView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01fd6bc9a856e78ed09690d895527ade133e6efaabe98122be281644904c81ca -size 42096 +oid sha256:41df9e71c5d0a8de4d6965e1b4dc353606c17b1c9d6ade7fec21cc2dc2cdab8f +size 42125 diff --git a/screenshots/de/features.logout.impl_LogoutView_Day_9_de.png b/screenshots/de/features.logout.impl_LogoutView_Day_9_de.png index c2375b4770..b058594a53 100644 --- a/screenshots/de/features.logout.impl_LogoutView_Day_9_de.png +++ b/screenshots/de/features.logout.impl_LogoutView_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf89f910c2d56965e0705b15f502e14073e569a0931e7ea5a2398a663b21ad57 -size 39801 +oid sha256:12a4f5d877a8a53b1e8e58b52c842aebca4c605e2c712d3b25bcff2dd398ac7d +size 39849 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_10_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_10_de.png index 8881e29e2b..a47381b147 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_10_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16d6e109bee5e1e9aa989191d95ace9d9142ead6f529e7bdb02ad8d180bd6435 -size 35399 +oid sha256:27561f13f31accde09568df3918aa603e3b030b542534216f85729ba3e85a713 +size 35401 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_11_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_11_de.png index 4632f2d972..c0bd9a510e 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_11_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:37b929271600ddab0836c758faeabf3a810fab4fc93b6fee1821cfd627aaf145 -size 55370 +oid sha256:d90a66dfe145cfa4d2ca46e0d82bb535b3c96381bffd9c5d3a56f04822718fab +size 55353 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_12_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_12_de.png index bb7b2152e4..314e801bde 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_12_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_12_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5c0cf0fe1a0dbeb9808fac60cae7a9303254f8004bd8809242a372a468a0a749 -size 55912 +oid sha256:73111984ac544ed3a33d63f4ec40935593d6f39b3b4c9df4221e1378454f87c6 +size 55872 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_2_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_2_de.png index d53e510d4f..f371592e9b 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_2_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0231fe2d6f4d97ff35dea16c2cefbd19bde7e1a9d7814cb1bac5d43cb647ded6 -size 47281 +oid sha256:d12adb58ed0eb49922c0f6ad018efeadd58e2d288803e970b79422ae5aa9819e +size 47266 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_3_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_3_de.png index 698df60f9a..438a6a393b 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_3_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1b29b1f2e1f8b339e608af09b9f49a6cb664c9a37130c312c75ca9ab29041665 -size 51544 +oid sha256:609ab3a3a4eaaf1d0b514e063f7182021b9a755452380ce5a3276a677048face +size 51534 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_4_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_4_de.png index 4d396b0f86..2917763d00 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_4_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0f11362ebac1674b839f8adb9d749d9486b1bbd59c71a2ddae6c333cb0cc0a84 -size 49963 +oid sha256:d0232738667fda5ef6cf8cf6a29a3f975ca7e7d828c1fe0f4b8eaa3fdbeea6b6 +size 49943 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_5_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_5_de.png index 7759741a6f..0117d650b7 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_5_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18862f4d1303abde8f155f38d9057db7b9d990b288e7587dc58f94636f5927e0 -size 45504 +oid sha256:5c159a119a0b147139e08a0196f08b1e27386204bb6edb34fd41a5f5a9fbfc64 +size 45520 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_6_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_6_de.png index 4b978064ec..572dab2bec 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_6_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1882aaff8f678953c313b70ecc9d52d7b0e46ef29162b3cc8a24ced0704a7069 -size 50221 +oid sha256:241d8c77cf4c3f1176361a8b390dcad41998f0ae66ab298fa8fdd8c4c4a55edb +size 50206 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_7_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_7_de.png index 667c230b2a..52aed73f0c 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_7_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17c547e8a275eefac0de72ca58f6ec3a81e80c8878900a63723c5ccc738270a6 -size 46267 +oid sha256:181294efa5385141906d780d2b2620a47cdb798609c68092e9798c6ceb70e6be +size 46280 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_8_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_8_de.png index 68a9eeb68f..764efc0d93 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_8_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2299b225ffd05ed2e69e5f5c46c0623a0e54014cd1419ef1ce371b87b803e347 -size 49293 +oid sha256:a4f8b829c9cdf8aab81af22ce197e4540403f37505197857eed432f18a12250c +size 49277 diff --git a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_9_de.png b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_9_de.png index bb55ab2e0b..33b5a4b538 100644 --- a/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_9_de.png +++ b/screenshots/de/features.messages.impl.actionlist_ActionListViewContent_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed45f00f99eecca857ff85f235362b92a23a7aefcab2a3de5f9ee87bd4dfbc0c -size 38619 +oid sha256:5e3caf4a808432fce325e11964c55ec220a1fa220253e822c0f55ef375be06b4 +size 38573 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_0_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_0_de.png new file mode 100644 index 0000000000..0e811c2b60 --- /dev/null +++ b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:013275dda1ccbcfea82e99623bc7b6558504711178e44a3b8c0dca7ff525f59e +size 401898 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_1_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_1_de.png new file mode 100644 index 0000000000..7e355c5e65 --- /dev/null +++ b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50b9506be5b2110a67fb499be4d3e06d3745dc9eeb164e90d8c7c9aced9e971d +size 400248 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_2_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_2_de.png new file mode 100644 index 0000000000..fd4159921d --- /dev/null +++ b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a962547b7d7d4c8bde5e895328ee5b86ecc60828667e4c880443d9d0687f70f4 +size 63069 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_3_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_3_de.png new file mode 100644 index 0000000000..0e811c2b60 --- /dev/null +++ b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:013275dda1ccbcfea82e99623bc7b6558504711178e44a3b8c0dca7ff525f59e +size 401898 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_4_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_4_de.png new file mode 100644 index 0000000000..ba27dd1aa3 --- /dev/null +++ b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab35fb3afeafba22816aa3f97f29b7e644994aa3d6e2b1e65fbf6f7e00cc8ea2 +size 62877 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_5_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_5_de.png new file mode 100644 index 0000000000..6bebc241b9 --- /dev/null +++ b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_5_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7bc279cec0778e772ec7eeaf679e2c1a7d781fb229fe4e7992f20a56cffe37b +size 68251 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_6_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_6_de.png new file mode 100644 index 0000000000..5a467d20db --- /dev/null +++ b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_6_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4824c960aeced8a1745c0beac52b406e83d24079eeac21bca78aa7ef0698a4f0 +size 72357 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_7_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_7_de.png new file mode 100644 index 0000000000..f0c2fff715 --- /dev/null +++ b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_7_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56182cde7b4d120ad6ee91943a38de5efff74d9a4c8fb457d47ad153906aa2ef +size 409133 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_8_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_8_de.png new file mode 100644 index 0000000000..9c80295727 --- /dev/null +++ b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsPreviewView_8_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0afbea1b4ee5db426d45293cc3260cef6ebf7cbcf7aa387452b6433120e5c5e0 +size 89922 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_0_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_0_de.png deleted file mode 100644 index 6db978f3e3..0000000000 --- a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_0_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:34c898bbf9bb08717b446664f39a1afdda1aaba0527a92e75555db89e1e7032c -size 401940 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_1_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_1_de.png deleted file mode 100644 index ff47dd3148..0000000000 --- a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_1_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6fa13a9ae160f9b22eb2a9d5117090759798b93cb8fd5a005c7acc2319a76e2b -size 400282 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_2_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_2_de.png deleted file mode 100644 index 89b6beb4b1..0000000000 --- a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_2_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fa105ee550380e5ab6f78cb32211d25009717698c98b8de3dec21bc5beeea6f9 -size 63108 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_3_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_3_de.png deleted file mode 100644 index 6db978f3e3..0000000000 --- a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_3_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:34c898bbf9bb08717b446664f39a1afdda1aaba0527a92e75555db89e1e7032c -size 401940 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_4_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_4_de.png deleted file mode 100644 index 4561823c1e..0000000000 --- a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_4_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:80b76fc36c84e48de46877503614cd6a6c289ee4defe1d57f3a4381a20560a6a -size 62914 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_5_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_5_de.png deleted file mode 100644 index f2979cc741..0000000000 --- a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_5_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c06481e9cc1787ac90268137a145790eab9929690e162ef22e7e0b1d4796d368 -size 68291 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_6_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_6_de.png deleted file mode 100644 index 6d0ad528db..0000000000 --- a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_6_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5a1c8211e71e899ee932c6016e1d44eceb340a55727b72f5055a7a57ade3e6e5 -size 72017 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_7_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_7_de.png deleted file mode 100644 index 73fff5485d..0000000000 --- a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_7_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e696c95ee71094650c0008ea618d4688796e8c093c4878ba4735b70487f4e36d -size 409171 diff --git a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_8_de.png b/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_8_de.png deleted file mode 100644 index 0366e56484..0000000000 --- a/screenshots/de/features.messages.impl.attachments.preview_AttachmentsView_8_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c356dd8df9ef8b414c68458828b20a709d55c3ffcd9010482c07e5e0ad501461 -size 89714 diff --git a/screenshots/de/features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Day_0_de.png b/screenshots/de/features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Day_0_de.png index f614e6eac7..8f9bad96f6 100644 --- a/screenshots/de/features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Day_0_de.png +++ b/screenshots/de/features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c71d15c6788611ecc0be5f75d64696d7bf68673f9c75c91d692358fedf219ba6 -size 71872 +oid sha256:0346829dcefb0fa299a8ef834ca56e76fa258e49fa36aea425e0a2f074fde401 +size 71943 diff --git a/screenshots/de/features.messages.impl.crypto.historyvisible_HistoryVisibleStateView_Day_0_de.png b/screenshots/de/features.messages.impl.crypto.historyvisible_HistoryVisibleStateView_Day_0_de.png new file mode 100644 index 0000000000..4bf92d9c4d --- /dev/null +++ b/screenshots/de/features.messages.impl.crypto.historyvisible_HistoryVisibleStateView_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae1141874f7bfefc4d2ed324f9606a2f68e35894cb7e34be28a299283e45fc6d +size 26294 diff --git a/screenshots/de/features.messages.impl.crypto.historyvisible_MessagesViewWithHistoryVisible_Day_0_de.png b/screenshots/de/features.messages.impl.crypto.historyvisible_MessagesViewWithHistoryVisible_Day_0_de.png new file mode 100644 index 0000000000..1d308c6908 --- /dev/null +++ b/screenshots/de/features.messages.impl.crypto.historyvisible_MessagesViewWithHistoryVisible_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e5009998aeb9113c7572494c77b5c619a6f8ef31236eddda3e91ecfc5a34b5c +size 67431 diff --git a/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_de.png b/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_de.png index dca73ab6a1..12dbce8d1b 100644 --- a/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_de.png +++ b/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cddbda7a38afd3ba433487c88955402a6c69cff0ce04cb1be397ded7b0eb712e -size 56147 +oid sha256:ab181e050382cd4dae2dea1c7ba8e93bf1d6ac6886de4cbbfc688f6f9e20ddf9 +size 56193 diff --git a/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_de.png b/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_de.png index d533e50925..bdae0831d1 100644 --- a/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_de.png +++ b/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3757f005f6a53a56d76d73fd0f93050c8d8e7832ae1d79877a1ebfaa4c26263b -size 66511 +oid sha256:8486cc5209df52dc0f5007f3d89dfbbe4e3d345a8aaf5be1585ef70d004afcc0 +size 66524 diff --git a/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_de.png b/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_de.png index 9c480b595c..d3700f9aee 100644 --- a/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_de.png +++ b/screenshots/de/features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28e84336963ef72fea96d5ddcb63534016af8ed918362b102323670c3c008166 -size 68121 +oid sha256:5b21056029f2b5d68a7dbfb4a027f4ee0a5f7f24b68ec12f13ae89769e25e1af +size 68091 diff --git a/screenshots/de/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_de.png b/screenshots/de/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_de.png index 62b61c2581..b216023c2f 100644 --- a/screenshots/de/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_de.png +++ b/screenshots/de/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba1b73e19da856bde24114be1d5429b4cd3cf32367dc168c93105221e550b580 -size 64379 +oid sha256:dcc4a38424e400b23cdf3718eeb1997764587973faafa973fdbcf347a3e9161c +size 64389 diff --git a/screenshots/de/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_de.png b/screenshots/de/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_de.png index 2f53d77bbf..03a2e0b878 100644 --- a/screenshots/de/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_de.png +++ b/screenshots/de/features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e667e7a1db26d4f158930aedff0cf8c3215aeab6627ee6decc9785d5812f32af -size 67868 +oid sha256:9266f42168bb0bf8972b12a5e84e790992b4a8ff66f94e0a19d2b8cca4ef5b73 +size 67873 diff --git a/screenshots/de/features.messages.impl.forward_ForwardMessagesView_Day_3_de.png b/screenshots/de/features.messages.impl.forward_ForwardMessagesView_Day_3_de.png deleted file mode 100644 index 9f006b50ca..0000000000 --- a/screenshots/de/features.messages.impl.forward_ForwardMessagesView_Day_3_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:384eb7f0964c98a0f9f78085e7523086818f76c1d56e6750424cff515971e357 -size 8681 diff --git a/screenshots/de/features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Day_0_de.png b/screenshots/de/features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Day_0_de.png index fb59d26ce5..0f061d5ac6 100644 --- a/screenshots/de/features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Day_0_de.png +++ b/screenshots/de/features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:734c8911e1c50f039c90ada55915ca651c1ec1b54ad221f1fefcacf9f5dee9bb -size 26423 +oid sha256:c634bc40fa4169c85e426078d2ad104f939ec6825257a41a785c35025a57d654 +size 26396 diff --git a/screenshots/de/features.messages.impl.messagecomposer_MessageComposerView_Day_0_de.png b/screenshots/de/features.messages.impl.messagecomposer_MessageComposerView_Day_0_de.png index b35ec70009..39770412c3 100644 --- a/screenshots/de/features.messages.impl.messagecomposer_MessageComposerView_Day_0_de.png +++ b/screenshots/de/features.messages.impl.messagecomposer_MessageComposerView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c2fe9f97097034e54463d868244747f6970fbb739715330f147341dcaecb6a9 +oid sha256:7eacaee5806d3c4813aa44d5812a1b8e69ef6f4a0a1618fbe0a7e06d22de7829 size 18215 diff --git a/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_0_de.png b/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_0_de.png index 358c0dff18..82445cbee1 100644 --- a/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_0_de.png +++ b/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dd115b1d8b6b3d5c302d45110947cee2f9e5b1df9c6c6c7f24f750e1850e04af -size 24976 +oid sha256:206e41045fa6bd767590f4639119b390c775915dcbbed8e59e4f3e36858519ae +size 24950 diff --git a/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_1_de.png b/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_1_de.png index 6b3c204ffe..eb287b98f6 100644 --- a/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_1_de.png +++ b/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:25cd5cb052d0a1c5ce508f03cb9617972850fdf07750c4b28c5409bbf56bb721 -size 10017 +oid sha256:d6d9e9b3820c234abdbcc727eafdbf0104470a1067a0861c1ab72470499c613b +size 10035 diff --git a/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_2_de.png b/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_2_de.png index 85c1ed7adc..0132a7017d 100644 --- a/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_2_de.png +++ b/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4cceda9f53d1b6a8885bed10ec80b19474e5a587a4e1886553a8ae478ed919b -size 35785 +oid sha256:8a6dfb3b0201d05ea5df163032f06a6480149d102543553df1fbcecca41a2a5c +size 35811 diff --git a/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_de.png b/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_de.png index 8163d35770..414ab3fe1f 100644 --- a/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_de.png +++ b/screenshots/de/features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d375fc47b82ea9d857b3600957e5253ee67821f38a7f73b5a61fc1d94caf9993 -size 43879 +oid sha256:52f47d4d42f6ed7b8dbdde5cd2733828e91caa581c4a5010d06be742c6922edc +size 43884 diff --git a/screenshots/de/features.messages.impl.report_ReportMessageView_Day_0_de.png b/screenshots/de/features.messages.impl.report_ReportMessageView_Day_0_de.png index 2887180911..1460417a1e 100644 --- a/screenshots/de/features.messages.impl.report_ReportMessageView_Day_0_de.png +++ b/screenshots/de/features.messages.impl.report_ReportMessageView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8429bd284e65e21590d48de7aef78a812aa825142e05768513e4111e2a6236f6 -size 48860 +oid sha256:bf7252eb8a333d7e5337c7cb14f993c4e262b4c4bc9a20d7d06ef104cc393725 +size 48867 diff --git a/screenshots/de/features.messages.impl.report_ReportMessageView_Day_1_de.png b/screenshots/de/features.messages.impl.report_ReportMessageView_Day_1_de.png index 24e6f6293c..9a30f0e8bc 100644 --- a/screenshots/de/features.messages.impl.report_ReportMessageView_Day_1_de.png +++ b/screenshots/de/features.messages.impl.report_ReportMessageView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b28017b6359af7fad86ed48ad680fe6db7f0369b21f4ea580769dd0f9e773fa -size 49736 +oid sha256:36cc7438ba6ee69bd2d652580896d1c11f7822008c0c3d88627bc5d24255d217 +size 49750 diff --git a/screenshots/de/features.messages.impl.report_ReportMessageView_Day_2_de.png b/screenshots/de/features.messages.impl.report_ReportMessageView_Day_2_de.png index c1641803c2..6d5d2b5e9f 100644 --- a/screenshots/de/features.messages.impl.report_ReportMessageView_Day_2_de.png +++ b/screenshots/de/features.messages.impl.report_ReportMessageView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1bc44ba7e3990ed13c1ba3bb546a32dbd32d87bbee9cb8de9e058619de541ba9 -size 49240 +oid sha256:923e6c9779583fd0ac073cbab61dc2dc386848c549a20bcfabcae7a8cc53bc59 +size 49255 diff --git a/screenshots/de/features.messages.impl.report_ReportMessageView_Day_3_de.png b/screenshots/de/features.messages.impl.report_ReportMessageView_Day_3_de.png index 28018be9d5..fd5e9797c8 100644 --- a/screenshots/de/features.messages.impl.report_ReportMessageView_Day_3_de.png +++ b/screenshots/de/features.messages.impl.report_ReportMessageView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17c1b20d0c2d94225d52bc95fc8ad2ee87f7e614fb69abc3320aa0443e585a33 -size 48925 +oid sha256:8202a96b5d29d1819d39f814f910fde41ea34aa837606f7bff27ee3a6d64c55e +size 48944 diff --git a/screenshots/de/features.messages.impl.report_ReportMessageView_Day_4_de.png b/screenshots/de/features.messages.impl.report_ReportMessageView_Day_4_de.png index 636abf3e9e..dc08c80114 100644 --- a/screenshots/de/features.messages.impl.report_ReportMessageView_Day_4_de.png +++ b/screenshots/de/features.messages.impl.report_ReportMessageView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4ff29d01ede74dd4bb73eaaf95969867ff27d25fc9123c81aa7746b4c660694c -size 28941 +oid sha256:835a9fcef474419949eb3fbe275da2dcc45283e405ac69c97378c939c6c60246 +size 28912 diff --git a/screenshots/de/features.messages.impl.report_ReportMessageView_Day_5_de.png b/screenshots/de/features.messages.impl.report_ReportMessageView_Day_5_de.png index c1641803c2..6d5d2b5e9f 100644 --- a/screenshots/de/features.messages.impl.report_ReportMessageView_Day_5_de.png +++ b/screenshots/de/features.messages.impl.report_ReportMessageView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1bc44ba7e3990ed13c1ba3bb546a32dbd32d87bbee9cb8de9e058619de541ba9 -size 49240 +oid sha256:923e6c9779583fd0ac073cbab61dc2dc386848c549a20bcfabcae7a8cc53bc59 +size 49255 diff --git a/screenshots/de/features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_0_de.png index 49936a3ea5..35761fbc07 100644 --- a/screenshots/de/features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ef8f25ec439986622c3ad9bf9a30ece6d3f7e17f264b7260203ee73da4051e7 -size 22243 +oid sha256:ad8fc597fb7c6674d5c9402f659ef163296d2131e80f7d0f89c6ac8a2e7201f9 +size 22266 diff --git a/screenshots/de/features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_1_de.png b/screenshots/de/features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_1_de.png index 4a867f7cd5..c1409acb77 100644 --- a/screenshots/de/features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_1_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5fa8acc69378e1d8b354b14c8c4c4ed2d6e85e931d7f03be81b0e2453654a5c5 -size 6534 +oid sha256:6d907836e749f5d74713e58b7427cac9c3972919ccccb2c66f41a688e9367c67 +size 6653 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_0_de.png index bbb46320a2..c01e42cc66 100644 --- a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:08cd52b175bc76ea6e51571e24cc5287c14bd1b29128b83dba6b1e6c132b4944 -size 9112 +oid sha256:9b7d10fbfa59990a848a1c70cc56f55c60ee0ab9d5c3fc5e09ebcbebe50be062 +size 9102 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_1_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_1_de.png index bbea5c1dd8..de792e6011 100644 --- a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_1_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a7f2af2e1a184964c69e6c01199820f0fbf7d36734b0a76cb6e842c2027fd07a -size 11469 +oid sha256:9a0e71b53b18d68898bc0ba544f5742015bd07ff87eb0d9b5c0ffcff2ec5138a +size 11456 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_de.png index 6926d126b0..b2867a5167 100644 --- a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9da344db7e22f80be0ae181e92804d053fe1c120ce4cae2bbbad4f8700ee6746 -size 13097 +oid sha256:eb38fad2c7d507a34d8adaea9ddb9179dc7310d262337c3400aeac052e670f32 +size 13070 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_3_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_3_de.png index 293ccdf251..afea4f205d 100644 --- a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_3_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3838c4a01a76b2eab8004332f31f71409b9667f72aa84535565f39895a72e03 -size 11242 +oid sha256:de76cb249605c4bc7abc62ab5533f3a9ce8b670cf39d63093784ea155c25a3bd +size 11226 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_4_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_4_de.png index 66ba06d334..e7feb40d17 100644 --- a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_4_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:346573be091144ec677f2bd3e26a19dbc0cac48b0b76c4509f0cf2644c7f5dcc -size 14611 +oid sha256:6e307e6040dee0aa33943ea053b3c4e9252eacc646b4b1c0bb345467a496e959 +size 14594 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_de.png index 0236701aee..a0ab545682 100644 --- a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cbea44bca597ecdb6598dc082faba460bf339f0070024187e0f376b791421af8 -size 17045 +oid sha256:e0b44a50f701d43676d0c28c1c07cd10d561c965b0018f97e3c0ae774856d1ff +size 17034 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_de.png index c8f4f21234..303c2f7f63 100644 --- a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0fc6105449a2b5bca7085ba8ebd4766b0aeca4b812439a3bb6031876ec528a7d -size 26267 +oid sha256:dd25812b220f8e046aa4549c0aa3c4d0ef7593519efdbc786f145158a086a930 +size 26271 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_de.png index 4f74c87565..8fa50c1f04 100644 --- a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ef0cbaf695486ec46a9d451e2620557e6e9c536307e7e65e2298e4b053bb98b -size 11464 +oid sha256:8d2d1b102c198c0e5ec8530d04c639acd45b0512a51a6d4d40408ae7afd248d6 +size 11459 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_8_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_8_de.png index bbb46320a2..c01e42cc66 100644 --- a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_8_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:08cd52b175bc76ea6e51571e24cc5287c14bd1b29128b83dba6b1e6c132b4944 -size 9112 +oid sha256:9b7d10fbfa59990a848a1c70cc56f55c60ee0ab9d5c3fc5e09ebcbebe50be062 +size 9102 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Day_0_de.png index 5c6b0c1317..ad4a84509c 100644 --- a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19823a48ab2deb3d575b076f684d99a4aa22cafc514d8618d728be3066ea4c2a -size 14939 +oid sha256:ba6bc62e27999afd28630ca606bdffdf15d745161d21539c60aefb786b8a49e3 +size 14938 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemPollView_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemPollView_Day_0_de.png index 7e3c3f8843..92397afa8b 100644 --- a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemPollView_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemPollView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6f822ac721fa7c0e129c787852e5d085713d4fdbc93d8475b48c50436fe58410 -size 48798 +oid sha256:f637dcf16dac07842061a0c3705527ddf481dddae403e76fa75094d690edbeae +size 48784 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemPollView_Day_1_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemPollView_Day_1_de.png index f8ad513f05..7b028c084a 100644 --- a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemPollView_Day_1_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemPollView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4276d64e4e150ef04080d1d7c2bbe372ccdd9a50cf67118163f9df630e5dad14 -size 51015 +oid sha256:09297602e9949d21f32aa92ff3a7709d7a6a0e4ca5837b63e1718e9757b681b1 +size 51000 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemPollView_Day_2_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemPollView_Day_2_de.png index 7b454e20eb..f0c19d5a63 100644 --- a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemPollView_Day_2_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemPollView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9de2142007e9b29f4418b2cd2e109ad696d397553dbe8ee720ab9e32fee21f05 -size 52612 +oid sha256:37ec62ff5de2fa0a8199c45eaee20339a203db7f767a587635e3a01dfbd2e007 +size 52601 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemPollView_Day_3_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemPollView_Day_3_de.png index f152e3821d..30846cb826 100644 --- a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemPollView_Day_3_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemPollView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dd43b12b36a6a4e3e467ca8455316e347290e1b03b3f760877bd90d18c366ebc -size 52991 +oid sha256:d26bf6994fb0f8b4757fca70dc9793da3b475fe465e1698361ab69fa3c656b8b +size 52981 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemRedactedView_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemRedactedView_Day_0_de.png index d215d43fdf..5e74701d3a 100644 --- a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemRedactedView_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemRedactedView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff5f2498323c6f0cb2c068ca23bff4e5af0b3bbde57c3cb8982284e2b8503fa9 -size 7553 +oid sha256:1a25765b56eef74392eea83fa6279950dc875a8158a18e7d1d58595f794d5c4f +size 7548 diff --git a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemUnknownView_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemUnknownView_Day_0_de.png index 0ce0c3f3aa..650a6535c6 100644 --- a/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemUnknownView_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.event_TimelineItemUnknownView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:826b030fbbaa23b05cd36ca3b072204f7b2cce6af0e2ef1398074d53e5bc6a6f -size 9188 +oid sha256:c46b6ccf3331487e0cf73c1055c0831ac56943adae6160defd702b49c12ec822 +size 9177 diff --git a/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_1_de.png b/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_1_de.png index 2e4b967586..dbe86d6685 100644 --- a/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_1_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a07d10aba2d12a4e664b4ff122f10434b5af4bb90fdaf57fc05a52c67bd47ff -size 12207 +oid sha256:82f339bf796b1dd88bb2eb7bc20ea999402b83764c267fe70d91e69aacfd8365 +size 12199 diff --git a/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_2_de.png b/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_2_de.png index d1d9065005..8f400cf087 100644 --- a/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_2_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:212650482a277d51efb218af0485aa0e5d7c72a33bd5e8c482dd704299c975c6 -size 17684 +oid sha256:2267052cc5675747b74f096b308431c77356d1c8937d827fd1d0f29dafc39096 +size 17718 diff --git a/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_3_de.png b/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_3_de.png index 3a19f3bf86..1a0298e410 100644 --- a/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_3_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:240798176e52148a905fb4beecd34e9ae962c5da9ba03324d3f3cf48ef9544ad -size 22605 +oid sha256:a366fe4261a7a35cdcd357814d56361b3fe30d96b94984866e78b0fac0dec742 +size 22667 diff --git a/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_4_de.png b/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_4_de.png index 828befb6d1..4029a678d4 100644 --- a/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_4_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f875b3189dd12e945dd7128e1c826e4b17463d098d790f52a4932cf27b878b5b -size 27015 +oid sha256:34d0a6f5093359b0f233d11152b1cb17905b11bfb71be11ce9ed3f41cd50e76e +size 27084 diff --git a/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_5_de.png b/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_5_de.png index edbda381af..95153ad95a 100644 --- a/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_5_de.png +++ b/screenshots/de/features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1cd78cd18060a2799221b3e6f3eb517bf6d597927fe4900d8c49349ca3a545d1 -size 32333 +oid sha256:836b4253c1846802ee4000455f91ba9a55195ab04f875cbece2bb57054456783 +size 32496 diff --git a/screenshots/de/features.messages.impl.timeline.components_CallMenuItem_Day_2_de.png b/screenshots/de/features.messages.impl.timeline.components_CallMenuItem_Day_2_de.png index 58d6f9bc01..7a2830deae 100644 --- a/screenshots/de/features.messages.impl.timeline.components_CallMenuItem_Day_2_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_CallMenuItem_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0578cfc7d5459104f4c1027cd95f0155e8ddcd93d1436a748167a6bf3db02c64 -size 6746 +oid sha256:c686ca52ae5175226e12262fb8f4adc70e1403d8d755a14a37f7adf1534f5ebc +size 6739 diff --git a/screenshots/de/features.messages.impl.timeline.components_CallMenuItem_Day_3_de.png b/screenshots/de/features.messages.impl.timeline.components_CallMenuItem_Day_3_de.png index c1df5aed64..7aebedf5a4 100644 --- a/screenshots/de/features.messages.impl.timeline.components_CallMenuItem_Day_3_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_CallMenuItem_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:533d2603d3b0c01a3960d575ef5cf551423e78364946f569549734afd8d5ee6a -size 6275 +oid sha256:f34a79713b48c14bc96c3b1371c74cdf3703339af04bfa3cc90b022101b30abf +size 6114 diff --git a/screenshots/de/features.messages.impl.timeline.components_MessageShieldView_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components_MessageShieldView_Day_0_de.png index 4eccdf182f..a9a792c0df 100644 --- a/screenshots/de/features.messages.impl.timeline.components_MessageShieldView_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_MessageShieldView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a93acf48045d16060cb04a02cd86c1fb474dc55c61d6f56e3fc35abc56d0736e -size 57920 +oid sha256:7880e4b1e3dc8e2dc15b5f07ae82f5902145f35a9045af0f39feca4e1ce8cb00 +size 57904 diff --git a/screenshots/de/features.messages.impl.timeline.components_ThreadSummaryView_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components_ThreadSummaryView_Day_0_de.png index 9f240daeae..958bd5275f 100644 --- a/screenshots/de/features.messages.impl.timeline.components_ThreadSummaryView_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_ThreadSummaryView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18a7f4dbf3a56c49e5b1edbd0f419620dbfcd4485bfe25db5347968b8844973b -size 9933 +oid sha256:aadadd01c5a31342a1d6eae9506c560a8710f1bc000f43dbe50fbce229da442a +size 9926 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineEventTimestampView_Day_4_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineEventTimestampView_Day_4_de.png index 92b543d8d7..8fc1fbb334 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineEventTimestampView_Day_4_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineEventTimestampView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:891730a3f18f34234fd27619c0a9bb711806ca4387c4280d4e43dfed343c8be9 -size 6788 +oid sha256:960fce914c44861940f72589da3a1a8280e890875b59e6e4b4a81248ee423c07 +size 6771 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemCallNotifyView_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemCallNotifyView_Day_0_de.png index 14a0ecd842..7522320c11 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemCallNotifyView_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemCallNotifyView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2fdad68b06bdb7af5da9f94561b5f5e3bff45bd3d8737d1404665951f7cbfb34 -size 42170 +oid sha256:37907f4f9f4f18857605e4e524793c0282100f07cf6e44fe8c8e4467c71bfca1 +size 42078 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_de.png index 347dfa0fb6..202f5ac23d 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:43b2ba805b9f810bdbb19b42f7a441a4b4e153013acdad2074bfbd4d3c832806 -size 35276 +oid sha256:526b8fe671e86c92f5477a425ddb02813bf8a7c8c2409df3785984b1ce7d8240 +size 35299 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_7_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_7_de.png index 45e3897765..1f2564c932 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_7_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c69ef0b37148981bac622f3b5f0c7a6d19d8fc3ab25d0434921829e7ea47f129 -size 11436 +oid sha256:29af35960ea591304710cc3915ca7a97547122f6114f6b0fdb0013b8b936ffda +size 11435 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_de.png index 35a6ba8efe..2d74d8679d 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3b75ff102cdeaf138ae9b5574391a348c0542c486a1e36f2bb83ef9e8c47ed30 -size 38097 +oid sha256:2ff3784acff9b538864301c13722291aa0b7fb957c124bc662fbff423d6be577 +size 38075 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_de.png index 674871a9f9..7b24ad1475 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0465a9b4861a1d6e1f720089ebb975e7a01f112bad28ed5ebc2e3738d10ce7fc -size 81087 +oid sha256:2583811fcad1f4960c040be87227714c94a1311c5dedb763e8abad5f0b737ebc +size 81075 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_de.png index 9950e567cd..78a683e9dd 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a65dbd7a45d6304f6445060d2aeeb767791211f8a1eaaf86bb6f5079d1001663 -size 364885 +oid sha256:d4e206a9a8836609ec40e98f9d95b7b6d68e47523e243097228aeeba35f04be2 +size 364866 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_de.png index fc6e96c29d..7e69b98c75 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18543bb2230f1e603be2a07eae29ab632698b8aa8d328b1363542a89ec5b15ed +oid sha256:636f49cd6431b01fa72e92879db35522d35a7260a52d1d320efcf74fe5765358 size 370534 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_de.png index 120e53f129..107d88db38 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5803e8615839f09e341de9cf167057cfcf26e5fc1e51e14b3da0e5024b741214 -size 364076 +oid sha256:fe3daf2cf97dee0cc6b67994ce7d3178d5f16fe50fe7a9e9ff5bf0d47d282b22 +size 364064 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_de.png index 15b5693fed..5a62ac8096 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:985b04b170d3e13dabc86ab4ab0f496974403f84d4a550c971634da59756633a +oid sha256:18d42f29dc43e05079e6964b8a97a2201898a8bff3ed1772cec5c18b137d87e3 size 366116 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithThreadSummary_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithThreadSummary_Day_0_de.png index 0de6e5f19e..4a48c95806 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithThreadSummary_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventRowWithThreadSummary_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e8b14f1fba1b0faa17b331d442e780456f7d0968e50d46d4c4352ff56d2a7bda -size 68148 +oid sha256:19ab48b39fe02b510edded16f680e11c8f2ebbd6d1455d4b3c99d33c3e6e7842 +size 68132 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_de.png index c2458bec09..263cd017fc 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d67fdbb9f411c1d4edf572a4cc9b10601081265adb69471fbfbf931cbade68f3 -size 57213 +oid sha256:54226aa0e9cbd3e3a4c43041178cd3dcd7587a3a6d6bd8eb7e2189b626543230 +size 57250 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Day_0_de.png index 01dae86c2e..0fe736c756 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:57d4ef7e08ddf1121f50c4840c074c7d0c6482697e0ad3a8528dc902e5716f54 -size 8788 +oid sha256:48aac469263e9d83a4b9b05411b247a2cb539cc08ec2e5cc25ebf4b562b76b64 +size 8782 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Day_0_de.png index 18c7589459..2ef239d254 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4928b731a26c8119c34cd1d8423b1c91eacd6ae1eb3b6d50f928f3fd42ba31b3 +oid sha256:6f58e1cac3dede747ff20f0c3f52ccdebc780184bb615fc62dd629945b9aa567 size 18222 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemReactionsLayout_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemReactionsLayout_Day_0_de.png index dd8dbc3529..1ef62bbc5f 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemReactionsLayout_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemReactionsLayout_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:34e1ee1ae196702f6ab9c500b9b110a32fc84445a0bc42c8e8f92bb6e73ff952 -size 26047 +oid sha256:9eb35e5a76d5b83d4389ac7f30dca8cd2ff080b56286f9c80bbdba90da47c162 +size 26053 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Day_0_de.png index 9e22bb90f2..efe8e7d212 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:69eae7faacbdce88f13844359884f24bc8159c13752ca1ff65aa8aaf2c7f6e70 -size 25643 +oid sha256:18ee45f13ebcc1f288aed2d9b1ac2365e514a427debc5b50ac0afb746724075d +size 25639 diff --git a/screenshots/de/features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Day_0_de.png b/screenshots/de/features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Day_0_de.png index 9c833955ca..1922ceeca8 100644 --- a/screenshots/de/features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7cf5c1518dbaf6952348815535c23e5291684857fab10314de23330d13c8c781 +oid sha256:9ffcca12721a8b7e9f4e3b012ab4cde97217d844734be03ae694198fe0e9dd28 size 25661 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineViewMessageShield_Day_0_de.png b/screenshots/de/features.messages.impl.timeline_TimelineViewMessageShield_Day_0_de.png index 83172bfcb7..49110f2c7e 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineViewMessageShield_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineViewMessageShield_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dbd684f8e72f659abedd2a4a6fee985814145089ed4e167cac950119ff1d2aab -size 38009 +oid sha256:bc45b7e1eeb26f0fc4db224924933b51a721fa14e99cbf5b36fd717f6e6dffc8 +size 38008 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_0_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_0_de.png index d1e96eb107..f5251ff844 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_0_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d098e36c94526f6b7d211cd7f7d094912f1148b4ff2f40e82c0d48b0633ac70 -size 51750 +oid sha256:76f20ec1110d42dfe8990342dc8f7b4486f58d91e112af568ee97739638345f8 +size 51772 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_11_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_11_de.png index 649ee1b9a3..d7498d62da 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_11_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab3d9d6fb1f4a97372ab83bb075c0c139a8beab3b41974f4e0435dc620b12ab5 -size 88411 +oid sha256:d40d94c839c967c829af11ce71af08d82726a2d0b3bb60fabbe164d626cae8e7 +size 88428 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_12_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_12_de.png index 154e729716..6c2b7233ba 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_12_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_12_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9e9be49fb8ecbab397aa9ca1d608d576d2549924bc5be05c2a47d3a76c2e0ba5 -size 53186 +oid sha256:6af3614a4b2a16e11763bcd683a1fcb2417926fc73761103faa105862ee07666 +size 53226 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_13_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_13_de.png index f8ebb81541..38697a9ba0 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_13_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_13_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce663c192fdc65b91f61194904da3553eb605630c6f7bf47f9853d3945584a65 -size 65076 +oid sha256:127086e85b6c01eca7289b9b466f0bd733f12d188412536e23a25dc172227c3f +size 65100 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_14_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_14_de.png index 61c758aaa3..820d4c7800 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_14_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_14_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:14facefce7d332e13fbc67109b3b4fff22881bb64c4a419094f5fd2371b2d9fb -size 49691 +oid sha256:d24935315c3d1c61ef4489699425195ec5cb9962e1dbb76244313ed2d307a081 +size 49729 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_15_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_15_de.png index 03d2f3f77e..e9be31d888 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_15_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_15_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a004d13252462f52a0f324cca838dc48d6d773e61c235fde33ac68ff5bac9e52 -size 73672 +oid sha256:995518b5ec90271f4eff1be312ed58401b7ee570d67870bff37c38b84ce1f07a +size 73751 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_16_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_16_de.png index 9f08b2943c..b9b9f2d0c2 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_16_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_16_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b705e80b55329df2d6786c8c619100880ba7de6a36602911857acf55fc2691b -size 59323 +oid sha256:e0bf2572edc0a6fd4c9e6e3ea17dfb05ef0caa5b798fb45bc364f55f699de914 +size 59357 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_17_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_17_de.png index a8017a2f55..02fe0473ed 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_17_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_17_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1548fa74ef4a3cd52599a467fe91d69e921559b24536ec4e258579ab853cb83d -size 65828 +oid sha256:46e493339ea7dd187dd122f5f3bb892f209f79fde19c3a50baa71e1e0113c0ce +size 65873 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_1_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_1_de.png index 04239eb227..94d117cfee 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_1_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21c6b7761c4453d3499d9841df292ba7e6f0703f41677351fe47bf9f3f038c33 -size 73626 +oid sha256:62d955f772483d8ede6d517a764b593baec2caa2ba09795a135b56d785119e9c +size 73629 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_4_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_4_de.png index 4511b92aa6..9e403ffde3 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_4_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:203290c665873113dd97089cc1b4a2eb169fe5f57ec55f968ff5f8f90b10e091 -size 72026 +oid sha256:0e628befc54944ae264dd102247b85d3c1d71748800ecd30ad5a3bde41aa7d5d +size 72027 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_6_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_6_de.png index ba4030eb50..67b63337a8 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_6_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a977db6aa54185e160ad87850205d317191d51aa8dc24839dda3b4f96a2f4c96 -size 75119 +oid sha256:6cc04814e771799188598bd9511f22bceb13bf5bd8484c4378137390bb2eed58 +size 75123 diff --git a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_8_de.png b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_8_de.png index 2139198bc9..65f68e3f9a 100644 --- a/screenshots/de/features.messages.impl.timeline_TimelineView_Day_8_de.png +++ b/screenshots/de/features.messages.impl.timeline_TimelineView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5cff768089c036e015d4e162f8e7a55b026a0b26e84d08fed85cb336adb8bd9f -size 55396 +oid sha256:49a4829e1f8768ccf77c60ccd76853eb6bc24a545728fb80c75ed9fd900fd19b +size 55427 diff --git a/screenshots/de/features.messages.impl.topbars_MessagesViewTopBar_Day_0_de.png b/screenshots/de/features.messages.impl.topbars_MessagesViewTopBar_Day_0_de.png index d6e752b972..89b6968710 100644 --- a/screenshots/de/features.messages.impl.topbars_MessagesViewTopBar_Day_0_de.png +++ b/screenshots/de/features.messages.impl.topbars_MessagesViewTopBar_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8348ac5ab0a32d80f7566bd734ace5ed549d55e1d003571e0e7fe3a68986f7dd -size 42668 +oid sha256:9eee9b32dc1ebb91657140f277f4ef9f41167383b28d12a3b0d8137d8637fcf7 +size 41896 diff --git a/screenshots/de/features.messages.impl.topbars_ThreadTopBar_Day_0_de.png b/screenshots/de/features.messages.impl.topbars_ThreadTopBar_Day_0_de.png index 77c579fc16..a35ff56f17 100644 --- a/screenshots/de/features.messages.impl.topbars_ThreadTopBar_Day_0_de.png +++ b/screenshots/de/features.messages.impl.topbars_ThreadTopBar_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f05ab027f2840e839337a59656cbf3b0f526e45ef8abcfb4677585a559e14fd7 -size 33920 +oid sha256:908b3602724e506773cf9b187755ed45a665646d02cff6af5b29a3b218427aa8 +size 33292 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_0_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_0_de.png index 86bb6e2223..f7f3218bea 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_0_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8448b68bda8acd2582a076872af17625276a7d76fd81ae0a55886621f4251a6 -size 58941 +oid sha256:641522311897076eab5d1cf178e979624d86247883d6f5d929b097a5c377f2f5 +size 58956 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_10_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_10_de.png index 92fb37b785..b4fd6a6ecb 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_10_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5969d4a287e7a6f589639569da53d97c8adf7c190d05776f5342084d4344c886 -size 53839 +oid sha256:15f0e4899422bbc75362f91f67b1f25b61f7c1ba5885b9a186d62cf0fa1962d7 +size 68472 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_11_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_11_de.png new file mode 100644 index 0000000000..5782aa30b6 --- /dev/null +++ b/screenshots/de/features.messages.impl_MessagesView_Day_11_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:696d10fb1ac5464bd89726955a1ae65fc70b1fd5dd03c74be808ef24abeb3ca1 +size 69375 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_12_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_12_de.png new file mode 100644 index 0000000000..b4fd6a6ecb --- /dev/null +++ b/screenshots/de/features.messages.impl_MessagesView_Day_12_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15f0e4899422bbc75362f91f67b1f25b61f7c1ba5885b9a186d62cf0fa1962d7 +size 68472 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_1_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_1_de.png index ad4ccaf62a..a6c79b0a59 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_1_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af28ff85e055081974c119833e5ee32d2ebc1728ff687b341cb686af3aa83d6b -size 58000 +oid sha256:48199bc8c630783e0c5cf6d4c9aa26a0955b90f052f745225ae2a11e53b72a6b +size 41824 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_2_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_2_de.png index 1641b39414..a5d62de535 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_2_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5605bbc41c6270eb6cd44c3bf993a6b5e9907b5dccb9bbd3834e4d7eded97198 -size 41782 +oid sha256:311e90c57d42ddd23c9ba4ba225b12e40a1dac3406d66b21c154052262df2439 +size 61292 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_3_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_3_de.png index 2282c34d85..cc94485260 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_3_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73173fa138e715f2cb0f9f7950208b6d3ab5d1a56f054d514ad24ddf4780a77c -size 61245 +oid sha256:1fbad6102fbb30e45eda90fd5eb193df15bbadaaaff1a0caaad0d8b0fd4ee09c +size 57424 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_4_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_4_de.png index af8d121795..944617d227 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_4_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3395537215d458bc647028d7b338e91fdf94cb242033afe063e165d9459c05af -size 57444 +oid sha256:13a9d2ae79c61fc650e089f41ce4397f5845a33ae30beec04439169d144704e5 +size 55332 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_5_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_5_de.png index 7c1807d837..0a8e3d020c 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_5_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9803a08e9c42e821941c85f596e909755723680f75534104f297a6f2bcb37a7d -size 55309 +oid sha256:99cc036c763c411b1946cb4aa9d7b0cd954145bc80cb14707fa4c6a0365df56f +size 61802 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_6_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_6_de.png index 9a48de10cb..05e168f79c 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_6_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9ef54103492179e8003b304ec1554873ac031d18b1bb00aeb65767b403b58915 -size 61819 +oid sha256:630a7b5f681c081c52eb17edf8dc0d90cc1e81c0ddf1557592d720025b943e4f +size 49239 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_7_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_7_de.png index 93a2e19fca..f39a8180b8 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_7_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ceff1004db2bbe6cbdf2c6d62e4521b89a9c63823b6cd1c709037e3bae306302 -size 49568 +oid sha256:69b3bae72db55066c0c3b4ae6eb7f75971a9c6a132adc2c2deecd42da3238efe +size 62321 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_8_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_8_de.png index d0b40bab37..7a8454000c 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_8_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:12e6c4fa5f0fffd03a9ef247a41d64e3ac579b6a5b803fcf0fb89fcc71bee9c7 -size 62309 +oid sha256:586f28a39e9f30c5df5e0ea989366911a31fab6d2a3b3d055a830ace203b229e +size 66261 diff --git a/screenshots/de/features.messages.impl_MessagesView_Day_9_de.png b/screenshots/de/features.messages.impl_MessagesView_Day_9_de.png index ea6e3ef99b..0a4d0ee277 100644 --- a/screenshots/de/features.messages.impl_MessagesView_Day_9_de.png +++ b/screenshots/de/features.messages.impl_MessagesView_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2439aecde9e9ad584cc7e06c6483d66af120d5abbb4915381be6b091f51168ca -size 66206 +oid sha256:33b2ef146d0425a07ad8600732abb79815ea72df3421c31f08cd281e788fbffe +size 53880 diff --git a/screenshots/de/features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Day_0_de.png b/screenshots/de/features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Day_0_de.png index 503edd0f00..b33fa90850 100644 --- a/screenshots/de/features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Day_0_de.png +++ b/screenshots/de/features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e4579f387dd478cd9f87c889e4594bd39ecee596a31c1a8059df711302932028 -size 22414 +oid sha256:9e6b6f0a7252526861cfa8726bf2edf5a537276e014a4849bc672e77f888925b +size 22424 diff --git a/screenshots/de/features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Day_0_de.png b/screenshots/de/features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Day_0_de.png index d43cbef044..1bcf4af9e1 100644 --- a/screenshots/de/features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Day_0_de.png +++ b/screenshots/de/features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6796633c10fd0c8e78ca0467533ff80adbfc55188583a7d48631eff075a2be40 -size 22166 +oid sha256:c208d059e2c143cb842a115d9b85fe2c9d98fa1b8add67811b190bf11762af83 +size 22161 diff --git a/screenshots/de/features.poll.api.pollcontent_PollAnswerViewEndedSelected_Day_0_de.png b/screenshots/de/features.poll.api.pollcontent_PollAnswerViewEndedSelected_Day_0_de.png index 109bbb1c2d..b7dc9cffda 100644 --- a/screenshots/de/features.poll.api.pollcontent_PollAnswerViewEndedSelected_Day_0_de.png +++ b/screenshots/de/features.poll.api.pollcontent_PollAnswerViewEndedSelected_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ea0b250b3ca6e36366eb59af5fe7dcaedcaef3a9135c0946a6fe93519101576 -size 22239 +oid sha256:4ac811497cc4e60d5f32a26edc0e9c320649163b412cbb7160c93c5a64659d8d +size 22240 diff --git a/screenshots/de/features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Day_0_de.png b/screenshots/de/features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Day_0_de.png index 1507ee7439..325ae125ba 100644 --- a/screenshots/de/features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Day_0_de.png +++ b/screenshots/de/features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:745abd2a67bb00bdf70082c4a8d42375e5d9b53b86558c187269e9bbd07aa237 -size 22294 +oid sha256:1edd7684b4441dc428e42116a24f24cd524365009485db94d4baed3a62a58c92 +size 22290 diff --git a/screenshots/de/features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Day_0_de.png b/screenshots/de/features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Day_0_de.png index c5861c340d..8fd59fd41c 100644 --- a/screenshots/de/features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Day_0_de.png +++ b/screenshots/de/features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c38febc28cbb78ff8091c53c1b6ac5612c33292b01f6915b5cca6fb211ea0971 -size 22159 +oid sha256:26e2cecea8b7f730cc46ae2a80c6bd71f6caab54c4e37161864a12fa67211f54 +size 22169 diff --git a/screenshots/de/features.poll.api.pollcontent_PollContentViewCreatorEditable_Day_0_de.png b/screenshots/de/features.poll.api.pollcontent_PollContentViewCreatorEditable_Day_0_de.png index 12abdc7b74..20b98600f7 100644 --- a/screenshots/de/features.poll.api.pollcontent_PollContentViewCreatorEditable_Day_0_de.png +++ b/screenshots/de/features.poll.api.pollcontent_PollContentViewCreatorEditable_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc51ea75419925320bdf8a5bf02b7d20df56731bc50872fae3d1e69511ee4227 -size 52931 +oid sha256:7b652f6d097b68e7fdf440b2bf200ddbd6d7eeeab0db34f4640c575ebaefe321 +size 52907 diff --git a/screenshots/de/features.poll.api.pollcontent_PollContentViewCreatorEnded_Day_0_de.png b/screenshots/de/features.poll.api.pollcontent_PollContentViewCreatorEnded_Day_0_de.png index edaaa84f5d..39be6df81c 100644 --- a/screenshots/de/features.poll.api.pollcontent_PollContentViewCreatorEnded_Day_0_de.png +++ b/screenshots/de/features.poll.api.pollcontent_PollContentViewCreatorEnded_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c052f644f55628cd3e0f3b485df7da367741ce978a16867df60f50640ed95ac9 -size 49177 +oid sha256:fc6f30976bc29cb3bf164aed6d440c2e2605197a3de090c2cf94c6f1c1b89526 +size 49158 diff --git a/screenshots/de/features.poll.api.pollcontent_PollContentViewCreator_Day_0_de.png b/screenshots/de/features.poll.api.pollcontent_PollContentViewCreator_Day_0_de.png index 7b454e20eb..f0c19d5a63 100644 --- a/screenshots/de/features.poll.api.pollcontent_PollContentViewCreator_Day_0_de.png +++ b/screenshots/de/features.poll.api.pollcontent_PollContentViewCreator_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9de2142007e9b29f4418b2cd2e109ad696d397553dbe8ee720ab9e32fee21f05 -size 52612 +oid sha256:37ec62ff5de2fa0a8199c45eaee20339a203db7f767a587635e3a01dfbd2e007 +size 52601 diff --git a/screenshots/de/features.poll.api.pollcontent_PollContentViewDisclosed_Day_0_de.png b/screenshots/de/features.poll.api.pollcontent_PollContentViewDisclosed_Day_0_de.png index 7e3c3f8843..92397afa8b 100644 --- a/screenshots/de/features.poll.api.pollcontent_PollContentViewDisclosed_Day_0_de.png +++ b/screenshots/de/features.poll.api.pollcontent_PollContentViewDisclosed_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6f822ac721fa7c0e129c787852e5d085713d4fdbc93d8475b48c50436fe58410 -size 48798 +oid sha256:f637dcf16dac07842061a0c3705527ddf481dddae403e76fa75094d690edbeae +size 48784 diff --git a/screenshots/de/features.poll.api.pollcontent_PollContentViewEnded_Day_0_de.png b/screenshots/de/features.poll.api.pollcontent_PollContentViewEnded_Day_0_de.png index edaaa84f5d..39be6df81c 100644 --- a/screenshots/de/features.poll.api.pollcontent_PollContentViewEnded_Day_0_de.png +++ b/screenshots/de/features.poll.api.pollcontent_PollContentViewEnded_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c052f644f55628cd3e0f3b485df7da367741ce978a16867df60f50640ed95ac9 -size 49177 +oid sha256:fc6f30976bc29cb3bf164aed6d440c2e2605197a3de090c2cf94c6f1c1b89526 +size 49158 diff --git a/screenshots/de/features.poll.api.pollcontent_PollContentViewUndisclosed_Day_0_de.png b/screenshots/de/features.poll.api.pollcontent_PollContentViewUndisclosed_Day_0_de.png index 4aebb5687f..3f52e4ed1a 100644 --- a/screenshots/de/features.poll.api.pollcontent_PollContentViewUndisclosed_Day_0_de.png +++ b/screenshots/de/features.poll.api.pollcontent_PollContentViewUndisclosed_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64f208428eef599454b3a95986a921958020a9802957229c3ba06ae74ed541e7 -size 46575 +oid sha256:d169a48d14b04cf2023d75b5ead3fdd34c6a535ea4413e7d4214884a420b94b0 +size 46569 diff --git a/screenshots/de/features.poll.impl.create_CreatePollView_Day_0_de.png b/screenshots/de/features.poll.impl.create_CreatePollView_Day_0_de.png index 2258235f56..9334461e5f 100644 --- a/screenshots/de/features.poll.impl.create_CreatePollView_Day_0_de.png +++ b/screenshots/de/features.poll.impl.create_CreatePollView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c0c95659736547acb752eab919dc78c1a8f29ff048e85118dc52c1ad6eb5ea60 -size 39531 +oid sha256:87dd25a450d2cad91ae7c091d0494ad715fcf875c3e8dc1e20ab905cc5d6b5f6 +size 39535 diff --git a/screenshots/de/features.poll.impl.create_CreatePollView_Day_1_de.png b/screenshots/de/features.poll.impl.create_CreatePollView_Day_1_de.png index c826880b23..e60f3a8ada 100644 --- a/screenshots/de/features.poll.impl.create_CreatePollView_Day_1_de.png +++ b/screenshots/de/features.poll.impl.create_CreatePollView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b47e907d51ff73ad68129233cea46102360a3f4ec246a7961583ac0be58d481b -size 42357 +oid sha256:d582bb6555ee254cc885a2054c870daa5f665a7ecef85db6cfa58ecc2103d8bb +size 42352 diff --git a/screenshots/de/features.poll.impl.create_CreatePollView_Day_2_de.png b/screenshots/de/features.poll.impl.create_CreatePollView_Day_2_de.png index 2500a47e29..41623d158d 100644 --- a/screenshots/de/features.poll.impl.create_CreatePollView_Day_2_de.png +++ b/screenshots/de/features.poll.impl.create_CreatePollView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f7893ea8ec10b5a7fd2d335f498a637602e0a0e30fbf5a9368a5cad150bbede -size 42554 +oid sha256:537fb0eec587e1f9e7b5f4095e9aa09c687f47e2b45025efa7dbc8cb4e030fd3 +size 42366 diff --git a/screenshots/de/features.poll.impl.create_CreatePollView_Day_3_de.png b/screenshots/de/features.poll.impl.create_CreatePollView_Day_3_de.png index 9c94ee1ef1..022add83af 100644 --- a/screenshots/de/features.poll.impl.create_CreatePollView_Day_3_de.png +++ b/screenshots/de/features.poll.impl.create_CreatePollView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4a6fb11ea03e64872c258bd25f304bb955c3ff779e190cc7495de2e8d0cdf71 -size 50031 +oid sha256:af28f956876e0e02929e76dc11e36eb6d92db76aa57b255016e2916b38a3c7d7 +size 50039 diff --git a/screenshots/de/features.poll.impl.create_CreatePollView_Day_4_de.png b/screenshots/de/features.poll.impl.create_CreatePollView_Day_4_de.png index 7a0b301a20..219147d7e4 100644 --- a/screenshots/de/features.poll.impl.create_CreatePollView_Day_4_de.png +++ b/screenshots/de/features.poll.impl.create_CreatePollView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d5d9dd5feb7bda399c539ac68d9f57d4c4ac4a070245b121e1cda186c2774d41 -size 27942 +oid sha256:d15e1e2edc67cbcd5f52bb6e3f253519bc9829c8f00bacabc31f35ef24b0aa26 +size 27925 diff --git a/screenshots/de/features.poll.impl.create_CreatePollView_Day_5_de.png b/screenshots/de/features.poll.impl.create_CreatePollView_Day_5_de.png index e7ad0cf1f1..afc6e2ecdf 100644 --- a/screenshots/de/features.poll.impl.create_CreatePollView_Day_5_de.png +++ b/screenshots/de/features.poll.impl.create_CreatePollView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:65f83f62ae167d7794c5d049b4459d4d4ef2572185adbdd1ff929f9561cf1a0f -size 118571 +oid sha256:0c609c8a701f5b8141b3c536a43b86c856f7692ade2033e04d82c1fdece2618f +size 118602 diff --git a/screenshots/de/features.poll.impl.create_CreatePollView_Day_6_de.png b/screenshots/de/features.poll.impl.create_CreatePollView_Day_6_de.png index ba48f2d1dd..de4c4abf0a 100644 --- a/screenshots/de/features.poll.impl.create_CreatePollView_Day_6_de.png +++ b/screenshots/de/features.poll.impl.create_CreatePollView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee2e8c1cf237f2c4b0ae6ccd9dcef280c0d4c83b788e140b38a47ac099e69336 -size 43253 +oid sha256:4c1d36cf8727b12859d381c204992cbec4474c361c5fbbb5add0a1e58e570869 +size 43255 diff --git a/screenshots/de/features.poll.impl.create_CreatePollView_Day_7_de.png b/screenshots/de/features.poll.impl.create_CreatePollView_Day_7_de.png index 48641b50c0..72bc914c60 100644 --- a/screenshots/de/features.poll.impl.create_CreatePollView_Day_7_de.png +++ b/screenshots/de/features.poll.impl.create_CreatePollView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1fcf6e7dc35e868539efe503ed937d978ed75c5f3f46e4f19ab52dc3bec71303 -size 40813 +oid sha256:bd9b328260734764e74a551326cd19a4661ac9c68b869a0057c70bdb726957b4 +size 40801 diff --git a/screenshots/de/features.poll.impl.history_PollHistoryView_Day_0_de.png b/screenshots/de/features.poll.impl.history_PollHistoryView_Day_0_de.png index b0887fba96..984b53aac9 100644 --- a/screenshots/de/features.poll.impl.history_PollHistoryView_Day_0_de.png +++ b/screenshots/de/features.poll.impl.history_PollHistoryView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:07769008e1f31eba70a3800f040d43066f14a4e004afcff7093304a24929504d -size 60145 +oid sha256:4d9309c049d6a06e4785cba0f715b3942bd5911caa5de8c6a8f7ed612f669df1 +size 60112 diff --git a/screenshots/de/features.poll.impl.history_PollHistoryView_Day_1_de.png b/screenshots/de/features.poll.impl.history_PollHistoryView_Day_1_de.png index 18922dc7ee..8cbf87260f 100644 --- a/screenshots/de/features.poll.impl.history_PollHistoryView_Day_1_de.png +++ b/screenshots/de/features.poll.impl.history_PollHistoryView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e7dafa33354b3b8eb6cc287ef56d4cd5374d3f1060f0c95c32a0ba4c23c91b1 -size 64360 +oid sha256:1850dcd657c25a5af59df9dd4dd6ac69c2d572e3484447eca240c8c3c5486300 +size 64329 diff --git a/screenshots/de/features.poll.impl.history_PollHistoryView_Day_2_de.png b/screenshots/de/features.poll.impl.history_PollHistoryView_Day_2_de.png index 6ac61e0431..f832c07c37 100644 --- a/screenshots/de/features.poll.impl.history_PollHistoryView_Day_2_de.png +++ b/screenshots/de/features.poll.impl.history_PollHistoryView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8f085fd176b3136ed1f75c29e03ee7ccffa0f1969b19f1951edf60da425e56c -size 17233 +oid sha256:dbc35b4f12d99a4abca5ad4a67bdb5b88861c5bc3eb77bafe482d5f00ba2a18c +size 17255 diff --git a/screenshots/de/features.poll.impl.history_PollHistoryView_Day_3_de.png b/screenshots/de/features.poll.impl.history_PollHistoryView_Day_3_de.png index fe440a1f2c..1d9f7dedf8 100644 --- a/screenshots/de/features.poll.impl.history_PollHistoryView_Day_3_de.png +++ b/screenshots/de/features.poll.impl.history_PollHistoryView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2bf68384e86515099a8d4b1430a6f03ff54829317bdd9ac949c77270e57d953a -size 17451 +oid sha256:9d427e2b574d12eb8c14419e1f19d549c0f16b05ef3d66718e3e5622a52c9e32 +size 17472 diff --git a/screenshots/de/features.poll.impl.history_PollHistoryView_Day_4_de.png b/screenshots/de/features.poll.impl.history_PollHistoryView_Day_4_de.png index dbe6078da1..4c7e7d0c5e 100644 --- a/screenshots/de/features.poll.impl.history_PollHistoryView_Day_4_de.png +++ b/screenshots/de/features.poll.impl.history_PollHistoryView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5b13f60e9b9363d183f92d278a39ce49e59d31248de9e027fab6dd3287ce982 -size 21162 +oid sha256:68e27b3b8b5fa00102a16d83ca0b76b8a7de9024dc3fbf077e80b61940e7e482 +size 21184 diff --git a/screenshots/de/features.preferences.impl.about_AboutView_Day_0_de.png b/screenshots/de/features.preferences.impl.about_AboutView_Day_0_de.png index 57b718d6e0..bd46afe3c6 100644 --- a/screenshots/de/features.preferences.impl.about_AboutView_Day_0_de.png +++ b/screenshots/de/features.preferences.impl.about_AboutView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:225e200388d0797309c165c7b92d2a6e63fb45314a9d98948d25076fbbb258ad -size 19145 +oid sha256:b8537888f3449918d5a0b3c88c97e124ce00a9e6594d5e3cf52a99a948c71f11 +size 19170 diff --git a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_0_de.png b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_0_de.png index 18075624e5..d5247fa692 100644 --- a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_0_de.png +++ b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:640fc6cb730521e831bc408c13fddcb16d7fc5767be0599bd14703e135cba673 -size 51711 +oid sha256:872a354ccbec2df1bc8cf8d5587b03a4e3a36020f6252f8fccc32bc4d2988bdd +size 51814 diff --git a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_1_de.png b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_1_de.png index 7e8d30e121..29fc4e4e08 100644 --- a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_1_de.png +++ b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e1147eb6941bac62236bf6b10cdb8f08f6338a89567916517112aa4b0f48939 -size 51577 +oid sha256:abb3d30cd0a680a1383920e5918e6c70c616c41a30eeafe9ebb227943cf86c25 +size 51678 diff --git a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_2_de.png b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_2_de.png index 029582be59..b16886817a 100644 --- a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_2_de.png +++ b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1d7d650a209b796acfa2dd8f5582de6792609136d9048a7ce6e87ec69a37e199 -size 51553 +oid sha256:43d6a942764ffb1769f7ff6069e4e4b47826fb4ad22aa411abe44c5acb8d00be +size 51653 diff --git a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_3_de.png b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_3_de.png index 0ead90491a..a7f4a2828e 100644 --- a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_3_de.png +++ b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:57e30dfd18e347b047328c73d011b70f65285fda4185a9ba8b1f4f0b5f33b81c -size 51582 +oid sha256:87d053a521e81a094ce2cbd363405694826c1548dba5c8bab5d71491353f87b8 +size 51683 diff --git a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_4_de.png b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_4_de.png index b1503ea24b..a21b372629 100644 --- a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_4_de.png +++ b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:37f4cec6c62f23337bf8071bcd784a090cc3da0ae6d965bc303b489d5c3c857f -size 51430 +oid sha256:20ecc3871970dfbb8cc5221ce99ffa3f35670960050c7f2b4f5b4849f6f0ffb7 +size 51533 diff --git a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_5_de.png b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_5_de.png index bbeb292cf2..0480cc6ac0 100644 --- a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_5_de.png +++ b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9210fdc033816afc90cb87ed3805f7ef87f255b83059a330816efab67b421d20 -size 51705 +oid sha256:b91020943837a618e6928d517b58f2e10625074305712ece87e2a4652ef600b6 +size 51807 diff --git a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_6_de.png b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_6_de.png index bffa4ec55c..5b7e996f7c 100644 --- a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_6_de.png +++ b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:994c0459cf57b9cd03f666389c38e745e1885909d89ec26484b1482d465bfe00 -size 51387 +oid sha256:fafdd20518032eac934ddf2b41f549be080c9aa7d1ee48da70363ed52b3c3791 +size 51492 diff --git a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_7_de.png b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_7_de.png index 5fdde723c2..3a4c6eacf1 100644 --- a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_7_de.png +++ b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba0513caef897e5f759f6b57d52204adaa4754741ba8677c6201cb45290cf258 -size 50932 +oid sha256:21bf02f826bd4ce099e674a6930136df14fa2d833beb75cd93023f7f75ea335a +size 51034 diff --git a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_8_de.png b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_8_de.png index 6cfb8fa904..52b1cbde47 100644 --- a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_8_de.png +++ b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewDark_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa4223f158398f7e8318d2dccde9ad5f19c6ddbfbecd56195cb33ca31b611a65 -size 58877 +oid sha256:963908f17066a341b6f2169b1d2c986d0320821c0f26b2e45c3c96150a7aa59a +size 58977 diff --git a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_0_de.png b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_0_de.png index 7c7382892c..081f5c2873 100644 --- a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_0_de.png +++ b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9aae1dc71cadb6e66f6e8e0442ba2930d763db9dc0b37ebc08bc1bb55ccc2769 -size 53769 +oid sha256:4ec9e68496f046e9fe19345ac4d4db4b41aabd3a2ec55393eebbd9323146e852 +size 53852 diff --git a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_1_de.png b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_1_de.png index 76b609e927..3ec9c9f529 100644 --- a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_1_de.png +++ b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d80855517f8b67231fe586bdf3b5f093fafd31473e1df26133691ceaccc7cc88 -size 53647 +oid sha256:4e44c054035d55d81eb2452ae5217891e8801a413b6220afebc87ecc94a426c9 +size 53731 diff --git a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_2_de.png b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_2_de.png index 1bd59b2eb5..e40d353565 100644 --- a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_2_de.png +++ b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:37cee079ad400733cd5e79bf7cc2ca8e57bd3c943718e8a0141f40c05876d81f -size 53651 +oid sha256:bf7795d8760b0ef10aaf69bafa3ff37ea0c47dc9f50ec6ccef8fd23674295509 +size 53737 diff --git a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_3_de.png b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_3_de.png index 96d13a3559..e93fc44323 100644 --- a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_3_de.png +++ b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:93cbe7c58a35811ea6ede3b159eb423b450ffa0394ac7d55c334c3222a65c077 -size 53648 +oid sha256:1a26bb5d30d8957934bdd80ab6074770c4dee54cc239c26a815085f6114ac7fc +size 53730 diff --git a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_4_de.png b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_4_de.png index 0893c5aeab..702360cfb6 100644 --- a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_4_de.png +++ b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9c34f1a555c5c4eec4aa836cdc2e51ae3e6af073b596b6759040e329a8d6a65b -size 53551 +oid sha256:75bfc93470f5a29e5a0dd2143eeac5f84addd8a489c8dd971ff01bb09661f01f +size 53610 diff --git a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_5_de.png b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_5_de.png index caca68cc7e..9ea55ff156 100644 --- a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_5_de.png +++ b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61e20fdeccf23e529c4c88d008a259bb287bdc9d45d3fc79b14ec43e9f33b96d -size 53791 +oid sha256:bc7f868e0f2284456c8a8d62ec2666ea53873253d335eb7bb5ab51ec79925b71 +size 53872 diff --git a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_6_de.png b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_6_de.png index bc9745c65c..e6bca0c665 100644 --- a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_6_de.png +++ b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9cbb9fb883cc6763109ab6b8fa8a47f3d476301a98ae4d323124486612fdc50d -size 53481 +oid sha256:2570f2ff5c676c75cd135e00508ee2a47cca82f163a3de6500ba82fe9e839fd0 +size 53560 diff --git a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_7_de.png b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_7_de.png index b8f8f80b22..2cd0f19ecc 100644 --- a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_7_de.png +++ b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8caeef8ba4c9429da99219cd4384e2f73e4616599c7347de7f5d7a4988e9df6f -size 52959 +oid sha256:b992e3aa729a10c5ff69441f79178ddd6c150fc83050fbeed4ec0e2340f47de2 +size 53043 diff --git a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_8_de.png b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_8_de.png index faddbcf144..6530d2640f 100644 --- a/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_8_de.png +++ b/screenshots/de/features.preferences.impl.advanced_AdvancedSettingsViewLight_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b33cff6bed2dd649aea96274dd8f837488d966ff5e06ad5484521a096a269bac -size 61633 +oid sha256:38cb8627784bf96336897f31f94aa1f80fb5a6d39ff05390ceb8940988a6964a +size 61704 diff --git a/screenshots/de/features.preferences.impl.analytics_AnalyticsSettingsView_Day_0_de.png b/screenshots/de/features.preferences.impl.analytics_AnalyticsSettingsView_Day_0_de.png index 0190896940..e5d4e0dd53 100644 --- a/screenshots/de/features.preferences.impl.analytics_AnalyticsSettingsView_Day_0_de.png +++ b/screenshots/de/features.preferences.impl.analytics_AnalyticsSettingsView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15a7e16b4731665934b7b7243fe08b226a6a8b1ab9210f6376f324c961cacca6 -size 28481 +oid sha256:cc005defab7bd844371e48c1511fd209ca8a3e8902e8e234dbcb1f409eb2deac +size 28492 diff --git a/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_0_de.png b/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_0_de.png index 13d36736ba..871afe71ef 100644 --- a/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_0_de.png +++ b/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc34cfac791c18b8bd35d080e3b79a6e172362b79f9ef416ab62bc00a95594dc -size 59214 +oid sha256:e5a733462618eb242f77a46c18e66a3d98d7de1ff300441350a56bf59edcb63b +size 53727 diff --git a/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_1_de.png b/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_1_de.png index d295317c9c..5fe49e4950 100644 --- a/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_1_de.png +++ b/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5080309ae298b821d0460d64d93bb5aa924f474d3acb6d890d632f0ff91261bd -size 58917 +oid sha256:4e1bb7419a82d26252fea05af5206635e64a1b64d4063ac51e5ff5c51fd3d655 +size 58261 diff --git a/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_2_de.png b/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_2_de.png index 63d0550a0f..26b99d52d0 100644 --- a/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_2_de.png +++ b/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d88f009ac745a10ac86b4c0cbf4bfe319709e4c6af9cdfcb74bab3c2a7820c6 -size 7996 +oid sha256:1d15f230f5c476b0929da8f6a96e3c6be2a3c2697f4d2c7dc6a6252ba644be45 +size 8014 diff --git a/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_3_de.png b/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_3_de.png index 4c195668f4..e838aea107 100644 --- a/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_3_de.png +++ b/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:57af5d4ae6c2c278808a14a9d47f11d8e393d6c378ccfc37ee5dbccb29335668 -size 58752 +oid sha256:fb7f869a12d97b8890e582f378f6d6811d1cff943121d3513e964b89bd29bf9c +size 58676 diff --git a/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_de.png b/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_de.png index 51f3c6ac32..3ca273fede 100644 --- a/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_de.png +++ b/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac0473003635a3020b0834b6464f61ad1546ca8e4416415d3cd228bdfa8fdae2 -size 62788 +oid sha256:555e0b1dc3ad335956a9c9595ab074a8678b81a593d8d5789b64369be625b40e +size 57370 diff --git a/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_5_de.png b/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_5_de.png index bd5eba6e6b..f3a824bcf6 100644 --- a/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_5_de.png +++ b/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e83d8f329fc02c4e1d82dcf37e3ddaafccd61449ebb3a70fe54a54a07760645b -size 61926 +oid sha256:a0d7f67c26cb32ed1d64ddf4abb629e9b72a6a38d74079c549a27b8c1ee9fca2 +size 56497 diff --git a/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_6_de.png b/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_6_de.png index 13d36736ba..871afe71ef 100644 --- a/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_6_de.png +++ b/screenshots/de/features.preferences.impl.blockedusers_BlockedUsersView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc34cfac791c18b8bd35d080e3b79a6e172362b79f9ef416ab62bc00a95594dc -size 59214 +oid sha256:e5a733462618eb242f77a46c18e66a3d98d7de1ff300441350a56bf59edcb63b +size 53727 diff --git a/screenshots/de/features.preferences.impl.developer_DeveloperSettingsView_Day_0_de.png b/screenshots/de/features.preferences.impl.developer_DeveloperSettingsView_Day_0_de.png index 91da509ee5..ab9bd3641a 100644 --- a/screenshots/de/features.preferences.impl.developer_DeveloperSettingsView_Day_0_de.png +++ b/screenshots/de/features.preferences.impl.developer_DeveloperSettingsView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bea9c1167e5a43a86c8183d97ff97b8f99aa063799b1b127aa24f44e375eb31f -size 50157 +oid sha256:0f2e04d19b48bc1926a59e583e1332057b7a5f49696c764134e3fa448d04747a +size 50172 diff --git a/screenshots/de/features.preferences.impl.developer_DeveloperSettingsView_Day_1_de.png b/screenshots/de/features.preferences.impl.developer_DeveloperSettingsView_Day_1_de.png index 91da509ee5..c9e3d940f2 100644 --- a/screenshots/de/features.preferences.impl.developer_DeveloperSettingsView_Day_1_de.png +++ b/screenshots/de/features.preferences.impl.developer_DeveloperSettingsView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bea9c1167e5a43a86c8183d97ff97b8f99aa063799b1b127aa24f44e375eb31f -size 50157 +oid sha256:7becc279d37731e7f48a98dc5b2bfcc46d5ff49c6defd2cfa4eb3700e2680817 +size 44887 diff --git a/screenshots/de/features.preferences.impl.developer_DeveloperSettingsView_Day_2_de.png b/screenshots/de/features.preferences.impl.developer_DeveloperSettingsView_Day_2_de.png index 27aeb2fde1..2bf5079ba3 100644 --- a/screenshots/de/features.preferences.impl.developer_DeveloperSettingsView_Day_2_de.png +++ b/screenshots/de/features.preferences.impl.developer_DeveloperSettingsView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:daac79a6d57fdb849ae8ec8cf8dd404b13d0eb954ec2a5507423305da301c27c -size 47867 +oid sha256:9723ad14c560fd385ce307f08c6ee8dac98b128111164ab0581941d774f89834 +size 47884 diff --git a/screenshots/de/features.preferences.impl.developer_DeveloperSettingsView_Day_3_de.png b/screenshots/de/features.preferences.impl.developer_DeveloperSettingsView_Day_3_de.png new file mode 100644 index 0000000000..5f2ffeb5a7 --- /dev/null +++ b/screenshots/de/features.preferences.impl.developer_DeveloperSettingsView_Day_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f82358720031b51116b369477e74fe01cdb2ef67752aa6c63a6448b3124af79a +size 40047 diff --git a/screenshots/de/features.preferences.impl.labs_LabsView_Day_0_de.png b/screenshots/de/features.preferences.impl.labs_LabsView_Day_0_de.png index 972332f722..283d6a560f 100644 --- a/screenshots/de/features.preferences.impl.labs_LabsView_Day_0_de.png +++ b/screenshots/de/features.preferences.impl.labs_LabsView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2170c55c31cc2065787a634fde443fea1f4f5acd1d7a7cce14f8255447b2e8bb -size 48317 +oid sha256:36d6e179c0fdecb3b4ebc1233fd3543c19c2615adeba0db15917e5540111d1f5 +size 48335 diff --git a/screenshots/de/features.preferences.impl.labs_LabsView_Day_1_de.png b/screenshots/de/features.preferences.impl.labs_LabsView_Day_1_de.png index b21729fa34..3edea046cf 100644 --- a/screenshots/de/features.preferences.impl.labs_LabsView_Day_1_de.png +++ b/screenshots/de/features.preferences.impl.labs_LabsView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2a065f3e8983d7fd2cf363984ca07f69bd2e0841d5302e7b2ac2c60a259332e2 -size 39484 +oid sha256:eb2bf0d0f83c2125a34393713a9f385734ef16e02a14e93ea152125b59b0c66e +size 39483 diff --git a/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_0_de.png b/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_0_de.png index c2e54513f4..fcca3bd638 100644 --- a/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_0_de.png +++ b/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:50738f74443668d50539e34bfd9581330b393ee1855462b392ae741f7dae9dd1 -size 50563 +oid sha256:1a9be1e5078fd9270518e95405d151a98a90f670d23e4bb01145b21b04a6b905 +size 50587 diff --git a/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_1_de.png b/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_1_de.png index 881b77d38a..d844d6c295 100644 --- a/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_1_de.png +++ b/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5da51224d52c42aff7bea1f0573194170933da0141021ca506c338ad6f68ddee -size 51277 +oid sha256:492ef4165ef99a2410ad1925ef1990a923af733af09c5c1b8d94ccd3d31d3e20 +size 51286 diff --git a/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_2_de.png b/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_2_de.png index c7b4a62bae..0b19442a96 100644 --- a/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_2_de.png +++ b/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:229aaf6b2a92bcd43c97cfa9e77c4c75b294a587306cea1d92c5b65208b5a97b -size 42261 +oid sha256:d59ce26ab11d829565dd9d0ca6c9bfd20296ceb5161e1adfb6064d666b77363f +size 42244 diff --git a/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_3_de.png b/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_3_de.png index b447e6eb76..f3b0172688 100644 --- a/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_3_de.png +++ b/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d5130de6e57ac0b4ada7b7a7de549cb08c1a1b48ac64cc05ab39f8d2efb41680 -size 41413 +oid sha256:84868285dd59f25fb9ba202f25a7ea84142f272584e50a740d50c1b1740eb9ea +size 41392 diff --git a/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_4_de.png b/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_4_de.png index 3a9acf603a..150cf37c78 100644 --- a/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_4_de.png +++ b/screenshots/de/features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17d720145734aed7fea487a031712556382617ef60f97405569979f153e39af1 -size 69922 +oid sha256:0ee9a4755fced9f049b1e37e8f7600420fb07bc8bea623afb522da2bc9b13dae +size 69944 diff --git a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_0_de.png b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_0_de.png index 1a072ef681..a7339b685a 100644 --- a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_0_de.png +++ b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8cbb924d55793c11e6d67fe80f447f354ec216a9b5684504342c51f65c5daa55 -size 68022 +oid sha256:d08d8768775583b81147a2ea91e978e0c1e0246d13b111bf535912d6ef8d24e9 +size 68034 diff --git a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_10_de.png b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_10_de.png index f38589684b..df7a47ab30 100644 --- a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_10_de.png +++ b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f717fc891e9c7f5c5ec7a1ae5025242e4fca4c697c1e1d7a092da2c30b1180dc -size 48666 +oid sha256:c753ca39238bd8e151d828497df7cffbc08df420adfcd2031c6ce06935cc2926 +size 48679 diff --git a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_11_de.png b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_11_de.png index 5837583cf1..7e0c79e38f 100644 --- a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_11_de.png +++ b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc683b3b8b0b3ffa9da10e505559f98b2faef0dd85523d2bf21faa32956d9483 -size 42832 +oid sha256:17629241fc84abf7b59e66e87ca77b0c35cea81f6448b705ca691689674f6b57 +size 42812 diff --git a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_12_de.png b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_12_de.png index 217e001190..89fabeac02 100644 --- a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_12_de.png +++ b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_12_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:34fd1153d2a1113ab211cdaaa4ab7a40a7683d2dc07cc63a5ee833c80e9fed7f -size 72473 +oid sha256:9fc32c25f6fa620ea6386f9724207a1207ed834586caa1faf30d6c9858e95d5c +size 72494 diff --git a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_13_de.png b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_13_de.png index 9c871496e8..3d6bfa81f6 100644 --- a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_13_de.png +++ b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_13_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f5a50190d6b249f9eb0d8346f69a6492ae10d56fc24bfd5ae78458a753b65a5 -size 19617 +oid sha256:6f674d824e0f3370bdf12a05dfe0b75e74f30412c342e6effe04a2e96ddc45ee +size 19623 diff --git a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_1_de.png b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_1_de.png index 606644fbd2..d0ee47d6a7 100644 --- a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_1_de.png +++ b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40b1622c11a7de63f2585946e6b066ff0dc482d69de3692a257c31374999090d -size 54576 +oid sha256:eb5f2b7feb440a5eab44105522a9524b319ad56cffc40fd42f3e8ac5cf83ba9f +size 54585 diff --git a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_2_de.png b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_2_de.png index 097a820851..a3cbf3b114 100644 --- a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_2_de.png +++ b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ddad47b9086ce793d7085eda8aa7847b120e7a8fcd58c45f803ea0549f202790 -size 48853 +oid sha256:b92ef498563e8e66ae16ba2df6a3ba6809e1995cdc019dba4b1392a5ecb32356 +size 48835 diff --git a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_3_de.png b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_3_de.png index 589bdcabc5..9540ddcad7 100644 --- a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_3_de.png +++ b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3b8df24928cda90d3e29186034cf088ebbe37a75b99e7ac8132f515e339a6bc2 -size 50019 +oid sha256:81869966ef4cf966a0890c5f54f56208803d9b925a817c7ac1300fd26bde4cd8 +size 50000 diff --git a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_4_de.png b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_4_de.png index 589bdcabc5..9540ddcad7 100644 --- a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_4_de.png +++ b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3b8df24928cda90d3e29186034cf088ebbe37a75b99e7ac8132f515e339a6bc2 -size 50019 +oid sha256:81869966ef4cf966a0890c5f54f56208803d9b925a817c7ac1300fd26bde4cd8 +size 50000 diff --git a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_5_de.png b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_5_de.png index 606644fbd2..d0ee47d6a7 100644 --- a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_5_de.png +++ b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40b1622c11a7de63f2585946e6b066ff0dc482d69de3692a257c31374999090d -size 54576 +oid sha256:eb5f2b7feb440a5eab44105522a9524b319ad56cffc40fd42f3e8ac5cf83ba9f +size 54585 diff --git a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_6_de.png b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_6_de.png index 0fc4259316..e34753f2b4 100644 --- a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_6_de.png +++ b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b83ce1b120f5eca77007f7f7d957bdd3c45135f5f3e454ec68c50564924373f3 -size 47163 +oid sha256:674a69f954886ca4623116a44263cde9a2b43de023b0e82bcd0935a8a7e20e5f +size 47140 diff --git a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_7_de.png b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_7_de.png index 663000bae6..e4e29291f4 100644 --- a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_7_de.png +++ b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:633b2df37189860357e7a98285f57c098e580d61cb82fdf274055e7559865a9b -size 52264 +oid sha256:6e72182a76fd2e4568eb34dfa9bff5896952ff91779c61dd99e0cd408060e36f +size 52238 diff --git a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_8_de.png b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_8_de.png index 606644fbd2..d0ee47d6a7 100644 --- a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_8_de.png +++ b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40b1622c11a7de63f2585946e6b066ff0dc482d69de3692a257c31374999090d -size 54576 +oid sha256:eb5f2b7feb440a5eab44105522a9524b319ad56cffc40fd42f3e8ac5cf83ba9f +size 54585 diff --git a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_9_de.png b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_9_de.png index 606644fbd2..d0ee47d6a7 100644 --- a/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_9_de.png +++ b/screenshots/de/features.preferences.impl.notifications_NotificationSettingsView_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40b1622c11a7de63f2585946e6b066ff0dc482d69de3692a257c31374999090d -size 54576 +oid sha256:eb5f2b7feb440a5eab44105522a9524b319ad56cffc40fd42f3e8ac5cf83ba9f +size 54585 diff --git a/screenshots/de/features.preferences.impl.root_MultiAccountSection_Day_0_de.png b/screenshots/de/features.preferences.impl.root_MultiAccountSection_Day_0_de.png deleted file mode 100644 index 2ab16568a9..0000000000 --- a/screenshots/de/features.preferences.impl.root_MultiAccountSection_Day_0_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2505c99e7ce75631696f09774c312363d414856bd2cdeece40a5bbfb04f38184 -size 59248 diff --git a/screenshots/de/features.preferences.impl.root_PreferencesRootViewDark_0_de.png b/screenshots/de/features.preferences.impl.root_PreferencesRootViewDark_0_de.png index 8b4e8902b2..9c1a6d041e 100644 --- a/screenshots/de/features.preferences.impl.root_PreferencesRootViewDark_0_de.png +++ b/screenshots/de/features.preferences.impl.root_PreferencesRootViewDark_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b63b8a1a2e1f6f3eb66d567103184177bd5fed9dafceb1e9d0900465a648d009 -size 42080 +oid sha256:bd194f574a10021900922b975c3168b16f52e6710e1630e53784f25466ad0be9 +size 41559 diff --git a/screenshots/de/features.preferences.impl.root_PreferencesRootViewDark_1_de.png b/screenshots/de/features.preferences.impl.root_PreferencesRootViewDark_1_de.png index 8bd32ee5ad..99ddb60522 100644 --- a/screenshots/de/features.preferences.impl.root_PreferencesRootViewDark_1_de.png +++ b/screenshots/de/features.preferences.impl.root_PreferencesRootViewDark_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f62deb4536b2b626505244590ce5d6531931f1cfbb87d5d97c2faa0db79d691f -size 41904 +oid sha256:166e8fb118c62ddef9f92be1ae2ff427d60d8a3bc9a348b047c6d3a165afd730 +size 41377 diff --git a/screenshots/de/features.preferences.impl.root_PreferencesRootViewLight_0_de.png b/screenshots/de/features.preferences.impl.root_PreferencesRootViewLight_0_de.png index e9d0333419..54daf8b831 100644 --- a/screenshots/de/features.preferences.impl.root_PreferencesRootViewLight_0_de.png +++ b/screenshots/de/features.preferences.impl.root_PreferencesRootViewLight_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d97d1234293a227f015f4eccc4adbe95fd97f2b28b9a0205ec558bd6ae287296 -size 43272 +oid sha256:7484ed26275f9b1198ff8ae6b9e1ccf149be88d43cce04bf4be7443abfac1f52 +size 42781 diff --git a/screenshots/de/features.preferences.impl.root_PreferencesRootViewLight_1_de.png b/screenshots/de/features.preferences.impl.root_PreferencesRootViewLight_1_de.png index 723e56cafb..546348fc80 100644 --- a/screenshots/de/features.preferences.impl.root_PreferencesRootViewLight_1_de.png +++ b/screenshots/de/features.preferences.impl.root_PreferencesRootViewLight_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:95662f13be6b716b5d5c5682f4a3cc5ad822a8899c677fdafd818c24b0e0d7d8 -size 43313 +oid sha256:f3ca986d2446bb2920809b20c0be5ccb0adf21383782613720d44784c3c5bab5 +size 42822 diff --git a/screenshots/de/features.preferences.impl.user.editprofile_EditUserProfileView_Day_0_de.png b/screenshots/de/features.preferences.impl.user.editprofile_EditUserProfileView_Day_0_de.png index 8563770fd5..6d1d7aba15 100644 --- a/screenshots/de/features.preferences.impl.user.editprofile_EditUserProfileView_Day_0_de.png +++ b/screenshots/de/features.preferences.impl.user.editprofile_EditUserProfileView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1d0db62c839925a48b4487b2b7fa7b597dc8d57aedd81d2f5ea64593f8b66532 -size 22153 +oid sha256:98a2048354e01703cc5a3b63a273682176b45f39dc79432e75c5f2961e71e941 +size 22986 diff --git a/screenshots/de/features.preferences.impl.user.editprofile_EditUserProfileView_Day_1_de.png b/screenshots/de/features.preferences.impl.user.editprofile_EditUserProfileView_Day_1_de.png index 6f0ea07dd6..e11e0938c6 100644 --- a/screenshots/de/features.preferences.impl.user.editprofile_EditUserProfileView_Day_1_de.png +++ b/screenshots/de/features.preferences.impl.user.editprofile_EditUserProfileView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f6707f3df5000f8ee5150b1c6655a4c6ba36746547306435dffbe0e786ec18e1 -size 70520 +oid sha256:fbe3d66e2749b8af8e922fb75a723b5885d3909e7fdf94f913d9a73189f6cefa +size 69959 diff --git a/screenshots/de/features.preferences.impl.user.editprofile_EditUserProfileView_Day_2_de.png b/screenshots/de/features.preferences.impl.user.editprofile_EditUserProfileView_Day_2_de.png new file mode 100644 index 0000000000..8c3409315d --- /dev/null +++ b/screenshots/de/features.preferences.impl.user.editprofile_EditUserProfileView_Day_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb4abd8d65e77aecba3d8fa4a4f5e0417b11b8b1b8e438da0b24e14fa5472bdf +size 34714 diff --git a/screenshots/de/features.rageshake.impl.bugreport_BugReportViewDay_0_de.png b/screenshots/de/features.rageshake.impl.bugreport_BugReportViewDay_0_de.png new file mode 100644 index 0000000000..069365f957 --- /dev/null +++ b/screenshots/de/features.rageshake.impl.bugreport_BugReportViewDay_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ff1d538bebf4958452baf69c83fe1ee6227a71ba92cf1d6c78dc4f83f813d69 +size 50870 diff --git a/screenshots/de/features.rageshake.impl.bugreport_BugReportViewDay_1_de.png b/screenshots/de/features.rageshake.impl.bugreport_BugReportViewDay_1_de.png new file mode 100644 index 0000000000..426b7883ec --- /dev/null +++ b/screenshots/de/features.rageshake.impl.bugreport_BugReportViewDay_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:baf5f387a778d4766d20c21f50717c97414af520bd430cace9a017df3fe53f2f +size 116512 diff --git a/screenshots/de/features.rageshake.impl.bugreport_BugReportViewDay_2_de.png b/screenshots/de/features.rageshake.impl.bugreport_BugReportViewDay_2_de.png new file mode 100644 index 0000000000..580cb1002f --- /dev/null +++ b/screenshots/de/features.rageshake.impl.bugreport_BugReportViewDay_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25f347841164816d5dad8b20747b64dfeab8668434bfc8b8620d2a5253cb79f9 +size 47761 diff --git a/screenshots/de/features.rageshake.impl.bugreport_BugReportViewDay_3_de.png b/screenshots/de/features.rageshake.impl.bugreport_BugReportViewDay_3_de.png new file mode 100644 index 0000000000..069365f957 --- /dev/null +++ b/screenshots/de/features.rageshake.impl.bugreport_BugReportViewDay_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ff1d538bebf4958452baf69c83fe1ee6227a71ba92cf1d6c78dc4f83f813d69 +size 50870 diff --git a/screenshots/de/features.rageshake.impl.bugreport_BugReportViewDay_4_de.png b/screenshots/de/features.rageshake.impl.bugreport_BugReportViewDay_4_de.png new file mode 100644 index 0000000000..7cb5b53595 --- /dev/null +++ b/screenshots/de/features.rageshake.impl.bugreport_BugReportViewDay_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86b159da39ab8f0b1a648d7ada22bb8e30b86a3cff21dc01148e0527e8f7abe8 +size 40307 diff --git a/screenshots/de/features.rageshake.impl.bugreport_BugReportView_Day_0_de.png b/screenshots/de/features.rageshake.impl.bugreport_BugReportView_Day_0_de.png deleted file mode 100644 index 2820191c87..0000000000 --- a/screenshots/de/features.rageshake.impl.bugreport_BugReportView_Day_0_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:818aa8617bd3866d4643ded290119eb1324cfe011c5bea327828b02f09756d94 -size 79283 diff --git a/screenshots/de/features.rageshake.impl.bugreport_BugReportView_Day_1_de.png b/screenshots/de/features.rageshake.impl.bugreport_BugReportView_Day_1_de.png deleted file mode 100644 index e300e1fe4d..0000000000 --- a/screenshots/de/features.rageshake.impl.bugreport_BugReportView_Day_1_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8a8fc1f3081a7565abf7bb9c8d0d012ecc5981b86f3d60e77febeadb5dd2c46b -size 98560 diff --git a/screenshots/de/features.rageshake.impl.bugreport_BugReportView_Day_2_de.png b/screenshots/de/features.rageshake.impl.bugreport_BugReportView_Day_2_de.png deleted file mode 100644 index 11b865af46..0000000000 --- a/screenshots/de/features.rageshake.impl.bugreport_BugReportView_Day_2_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1939faffe9d5e8591462d4c5302c26d969be2560ec4e954913a59d9ec1f9601c -size 74937 diff --git a/screenshots/de/features.rageshake.impl.bugreport_BugReportView_Day_3_de.png b/screenshots/de/features.rageshake.impl.bugreport_BugReportView_Day_3_de.png deleted file mode 100644 index 2820191c87..0000000000 --- a/screenshots/de/features.rageshake.impl.bugreport_BugReportView_Day_3_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:818aa8617bd3866d4643ded290119eb1324cfe011c5bea327828b02f09756d94 -size 79283 diff --git a/screenshots/de/features.rageshake.impl.bugreport_BugReportView_Day_4_de.png b/screenshots/de/features.rageshake.impl.bugreport_BugReportView_Day_4_de.png deleted file mode 100644 index 66088d0cd9..0000000000 --- a/screenshots/de/features.rageshake.impl.bugreport_BugReportView_Day_4_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bce0a946397aa0223be195fc0090b6bf009ed02a914879840bc445b271a37a32 -size 57646 diff --git a/screenshots/de/features.reportroom.impl_ReportRoomView_Day_0_de.png b/screenshots/de/features.reportroom.impl_ReportRoomView_Day_0_de.png index 2b9365224e..539faa5f0d 100644 --- a/screenshots/de/features.reportroom.impl_ReportRoomView_Day_0_de.png +++ b/screenshots/de/features.reportroom.impl_ReportRoomView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17152083f68f5db4b3badd5bc599456ff0f56e2e85baa3b26124b302ce4b52ce -size 33635 +oid sha256:f7794082fc76e428fd7ee608440b491f405e1bb911fbe29bf8ed795e45defdcf +size 33655 diff --git a/screenshots/de/features.reportroom.impl_ReportRoomView_Day_1_de.png b/screenshots/de/features.reportroom.impl_ReportRoomView_Day_1_de.png index 822822882f..7cdb9874e1 100644 --- a/screenshots/de/features.reportroom.impl_ReportRoomView_Day_1_de.png +++ b/screenshots/de/features.reportroom.impl_ReportRoomView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa4ea00e96212dae3bd8e87e96c0267624a3c63574dd6c279118916a38391860 -size 32194 +oid sha256:823991757597542fdacdc4d20b098025c2251a26de52dc605847eda4c7583061 +size 32213 diff --git a/screenshots/de/features.reportroom.impl_ReportRoomView_Day_2_de.png b/screenshots/de/features.reportroom.impl_ReportRoomView_Day_2_de.png index 3ceca63e8d..2b1d55caa5 100644 --- a/screenshots/de/features.reportroom.impl_ReportRoomView_Day_2_de.png +++ b/screenshots/de/features.reportroom.impl_ReportRoomView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:290d26a59077e059d04914ef3dfc8d5b19685c5fa447b54d2066fd8a901f5853 -size 33255 +oid sha256:9cb345cbc2f888396253602031a0bd38436a61f0d3f6dcf3345a1438a1b8e58a +size 33274 diff --git a/screenshots/de/features.reportroom.impl_ReportRoomView_Day_3_de.png b/screenshots/de/features.reportroom.impl_ReportRoomView_Day_3_de.png index b6378c2b9c..24c9aa5a40 100644 --- a/screenshots/de/features.reportroom.impl_ReportRoomView_Day_3_de.png +++ b/screenshots/de/features.reportroom.impl_ReportRoomView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d7341fbf53fbf1066bcf75691652782cb549b9ffb715c0b80d8c5bf7dedf33d1 -size 29213 +oid sha256:c563f1bb01d08dab1cb84e11357a05b6a1ef107ae4df426e3a70c94dbbcd77a4 +size 29190 diff --git a/screenshots/de/features.reportroom.impl_ReportRoomView_Day_4_de.png b/screenshots/de/features.reportroom.impl_ReportRoomView_Day_4_de.png index dc2fd86233..b695a15cc2 100644 --- a/screenshots/de/features.reportroom.impl_ReportRoomView_Day_4_de.png +++ b/screenshots/de/features.reportroom.impl_ReportRoomView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:43c3083501e9fa128a2eab0896829f1a37ac673111d2ac7cbc3886ee1a02f884 -size 32001 +oid sha256:68d9fc89734c94e8aac18e547fc0c335522b800217fb7ff143732679b7eac8d7 +size 31975 diff --git a/screenshots/de/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_0_de.png b/screenshots/de/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_0_de.png new file mode 100644 index 0000000000..a4bc8d4e44 --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39a79b789373312096c863a37afc548827a4d662571aa6c8efd62ff9c60b2a00 +size 65296 diff --git a/screenshots/de/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_1_de.png b/screenshots/de/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_1_de.png new file mode 100644 index 0000000000..875ef8326c --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d95bf342ddba5663a36a1f435351e7a3017cad73f7cd58504913681feced1ce2 +size 65157 diff --git a/screenshots/de/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_2_de.png b/screenshots/de/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_2_de.png new file mode 100644 index 0000000000..a04a0cd23a --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5ab43cd09460063624d2e583b71af3bb3a286890cbf07744d101042aa7c45f5 +size 56783 diff --git a/screenshots/de/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_3_de.png b/screenshots/de/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_3_de.png new file mode 100644 index 0000000000..7da0708b92 --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:41d2484c7ddee013f9cf001dc2eb0ff8a99ac22b7e70816752c97893d1db4c95 +size 54265 diff --git a/screenshots/de/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_4_de.png b/screenshots/de/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_4_de.png new file mode 100644 index 0000000000..6d24520c56 --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3afe4b55dd9e11397b9f4b5a99cfc6f5a1a4cebcbdc8cdfb0f81f43eb6912f15 +size 63039 diff --git a/screenshots/de/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_5_de.png b/screenshots/de/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_5_de.png new file mode 100644 index 0000000000..c0656d9b7e --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_5_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ad55fc63c50a403a3218e9168df15c92f91430e60bd4adde45247080cf50543 +size 62971 diff --git a/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_0_de.png b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_0_de.png new file mode 100644 index 0000000000..ce06429382 --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70b9a96c4dff57091a091d74896aa85ae1d5ed3a45e58dd46e0e4e5cc758150f +size 16149 diff --git a/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_10_de.png b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_10_de.png new file mode 100644 index 0000000000..0446bc13ad --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_10_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5bf22144267e8ebed2c074cfe017aebcd5c59f5ad55cf81775802654aa0f5450 +size 52254 diff --git a/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_11_de.png b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_11_de.png new file mode 100644 index 0000000000..3fc5b0b10c --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_11_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea2b8ef893a86cff4960d16540a417d9c03b0c7c14952c0f798d95dabb2bcbd4 +size 55482 diff --git a/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_12_de.png b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_12_de.png new file mode 100644 index 0000000000..9d47bfbbfb --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_12_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5061a9d86c7b222f7be0d61d2ff4ca4da7504f50d172189c9c9a8a70baa1a126 +size 55517 diff --git a/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_13_de.png b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_13_de.png new file mode 100644 index 0000000000..09a60754b4 --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_13_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a6779a089b642b924e927cdf0813dd25707dbaeaaab6bc0d4c255df3bc6f7c2 +size 66133 diff --git a/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_1_de.png b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_1_de.png new file mode 100644 index 0000000000..6be2aa5649 --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d768711fbfe3c3086657733175ad23601160ba9485365d1ed24b56e81c54061f +size 70451 diff --git a/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_2_de.png b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_2_de.png new file mode 100644 index 0000000000..e4b304c7a2 --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ac6592c003e271d3578152b1c579b457fd64673789173ec37d1a73cd55422cb +size 64133 diff --git a/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_3_de.png b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_3_de.png new file mode 100644 index 0000000000..e5930b5c27 --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf48c5af53b599814c1a43db660283f091e4a1948839e211eb117b931df79a77 +size 63967 diff --git a/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_4_de.png b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_4_de.png new file mode 100644 index 0000000000..094c990d90 --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3664e7b03b65c06d0e79fa2b6d690cb1e1bcb33df279ce2e01b15b447f1a41d +size 57833 diff --git a/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_6_de.png b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_6_de.png new file mode 100644 index 0000000000..1c27d4d4f9 --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_6_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:06b099b4780f03abf3a9e3a6417c7981a83276bc5c689f9508db3c8e54a4631b +size 61079 diff --git a/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_7_de.png b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_7_de.png new file mode 100644 index 0000000000..bc7cefa438 --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_7_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fdeb03d0a6a5edac44e9c9f6d27843ac86194d5791188c23d874ecbd5343f782 +size 65469 diff --git a/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_8_de.png b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_8_de.png new file mode 100644 index 0000000000..fa0f3ecc71 --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_8_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63856a50b0fa099695709dc720e600ae9b36b62cb3bfac28a940607c72efd5c5 +size 54966 diff --git a/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_9_de.png b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_9_de.png new file mode 100644 index 0000000000..e5930b5c27 --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_9_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf48c5af53b599814c1a43db660283f091e4a1948839e211eb117b931df79a77 +size 63967 diff --git a/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_0_de.png b/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_0_de.png new file mode 100644 index 0000000000..cd690a7726 --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:673a833d5c5e9026b680a73017ca6e6dd9f3e33c0b13a0de4967133b85a13bfa +size 33146 diff --git a/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_1_de.png b/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_1_de.png new file mode 100644 index 0000000000..bd1a2e7ac3 --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc67b423cec2885955d8b9df9df2a0fae2767c9ab96be944824efcb07dc70b25 +size 35233 diff --git a/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_2_de.png b/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_2_de.png new file mode 100644 index 0000000000..e8fc49b273 --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:943737f471cd135ed4e584bbf28159701453a5a85ac6fa2a23fae21f71192869 +size 63918 diff --git a/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_3_de.png b/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_3_de.png new file mode 100644 index 0000000000..98bfbf4ddf --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:24961d4b8ba165e40edc46675151005c34ec7b6de8cb3755aaab3481e87ca9ac +size 30661 diff --git a/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_4_de.png b/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_4_de.png new file mode 100644 index 0000000000..4982d7286a --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03f5a3ded714646cd1e316d8cf6bb9517931ce2d1407adcf55929a3a598f5a64 +size 28635 diff --git a/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_5_de.png b/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_5_de.png new file mode 100644 index 0000000000..c55a884523 --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_5_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3004eae40ff8395952efbca378f2cd53afefb36156e8d8f77ee4b09ea0509914 +size 43327 diff --git a/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_6_de.png b/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_6_de.png new file mode 100644 index 0000000000..98bfbf4ddf --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_6_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:24961d4b8ba165e40edc46675151005c34ec7b6de8cb3755aaab3481e87ca9ac +size 30661 diff --git a/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_7_de.png b/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_7_de.png new file mode 100644 index 0000000000..198b0c2a5a --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_7_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e514066d66b1e868c7229302478e48fa5e9a72fdccbe54d9e179889544f73ea5 +size 28037 diff --git a/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_8_de.png b/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_8_de.png new file mode 100644 index 0000000000..8a01bb44d8 --- /dev/null +++ b/screenshots/de/features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_8_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66197931c626c563b2c6fcbf1fc8f57366bbc1b9a22c3d4dc18d64457e5a8d24 +size 31131 diff --git a/screenshots/de/features.roomaliasresolver.impl_RoomAliasResolverView_Day_1_de.png b/screenshots/de/features.roomaliasresolver.impl_RoomAliasResolverView_Day_1_de.png index 16e8224c33..31c009ac75 100644 --- a/screenshots/de/features.roomaliasresolver.impl_RoomAliasResolverView_Day_1_de.png +++ b/screenshots/de/features.roomaliasresolver.impl_RoomAliasResolverView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:544cbdde4d635c702e64c4279923527a10a5fc5bb199cc074e44d36208dcf1fb -size 33946 +oid sha256:c78aa1de9893644d7b9baca2ce49773db19f94add4ccb9ef372a99e9a9e1bbc5 +size 33927 diff --git a/screenshots/de/features.roomaliasresolver.impl_RoomAliasResolverView_Day_2_de.png b/screenshots/de/features.roomaliasresolver.impl_RoomAliasResolverView_Day_2_de.png index 05d32f6e21..b18d7c5563 100644 --- a/screenshots/de/features.roomaliasresolver.impl_RoomAliasResolverView_Day_2_de.png +++ b/screenshots/de/features.roomaliasresolver.impl_RoomAliasResolverView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:46650e2769751b04dae2cc0edf05d628678dbfa73439d207f3cca75379dab8bd -size 27744 +oid sha256:4a21f678e1eb53b46d47bd507c7093c5f6a72f56b4024d4427c27ca0ba2f80ad +size 27725 diff --git a/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_0_de.png b/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_0_de.png deleted file mode 100644 index 6bf7743e5b..0000000000 --- a/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_0_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:db034b5bc00f6e71c37ed2f180d3030e7b0f98f5247eb3f538566b82a43c8687 -size 30026 diff --git a/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_1_de.png b/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_1_de.png deleted file mode 100644 index df7331e342..0000000000 --- a/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_1_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:82bcb78fd3c9b508862c2494c94476e74ec666eb6ae1c096129e6375bbbbe65d -size 24597 diff --git a/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_2_de.png b/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_2_de.png deleted file mode 100644 index 65ab5399fc..0000000000 --- a/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_2_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:218769a104db9edf946f52ab66860e03fb032f5b3dddc1c95d1c0974466d64fa -size 31158 diff --git a/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_3_de.png b/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_3_de.png deleted file mode 100644 index 3d7f71518e..0000000000 --- a/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_3_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e2f19bcc39caeaa1f88862149f53b375d78c7e56e1ce908a4d2188a55fed76a4 -size 55617 diff --git a/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_4_de.png b/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_4_de.png deleted file mode 100644 index 495cab10cf..0000000000 --- a/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_4_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:062103c4a5ad33a859854da6c5634312abed1a9dc0b756cb23653c47606ba8e8 -size 30058 diff --git a/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_5_de.png b/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_5_de.png deleted file mode 100644 index 8fb76b2c20..0000000000 --- a/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_5_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:192516d8448d0a5b65c8208a0eefcaae922913fe8d2e859cb7e0dbababe4079e -size 30144 diff --git a/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_6_de.png b/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_6_de.png deleted file mode 100644 index 75be860ea6..0000000000 --- a/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_6_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:52effd0ee06fd2a7764974dada7fbbc93453eab2e17f58af4b004d7182598514 -size 27638 diff --git a/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_7_de.png b/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_7_de.png deleted file mode 100644 index 87677b8e2d..0000000000 --- a/screenshots/de/features.roomdetails.impl.edit_RoomDetailsEditView_Day_7_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:445db5ce8ac1d90129d2e53be3074f0c8d4518374355833c0ce3579fa9ff3394 -size 29761 diff --git a/screenshots/de/features.roomdetails.impl.invite_RoomInviteMembersView_Day_0_de.png b/screenshots/de/features.roomdetails.impl.invite_RoomInviteMembersView_Day_0_de.png index d47cbc1543..bee5244791 100644 --- a/screenshots/de/features.roomdetails.impl.invite_RoomInviteMembersView_Day_0_de.png +++ b/screenshots/de/features.roomdetails.impl.invite_RoomInviteMembersView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:93b580abe330752636e76451fa670ac637b6d365537a478c9ef9c8994d81550b -size 8886 +oid sha256:f47cfee459a5724d907f03c295907bce04d9fb23e1f1472d87c9e2ac6ecc9368 +size 8897 diff --git a/screenshots/de/features.roomdetails.impl.invite_RoomInviteMembersView_Day_1_de.png b/screenshots/de/features.roomdetails.impl.invite_RoomInviteMembersView_Day_1_de.png index e202093b7b..50a1c51bfc 100644 --- a/screenshots/de/features.roomdetails.impl.invite_RoomInviteMembersView_Day_1_de.png +++ b/screenshots/de/features.roomdetails.impl.invite_RoomInviteMembersView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:52aaf6c726ae33832da29cac4f301a682f1716fd0c4c5959bcf738eb4f6c5c3f -size 8747 +oid sha256:76c81b27bab88665a6a109f3b876f6892f719e6417eeea16445c2b278d07d8dc +size 8750 diff --git a/screenshots/de/features.roomdetails.impl.invite_RoomInviteMembersView_Day_2_de.png b/screenshots/de/features.roomdetails.impl.invite_RoomInviteMembersView_Day_2_de.png index d47cbc1543..bee5244791 100644 --- a/screenshots/de/features.roomdetails.impl.invite_RoomInviteMembersView_Day_2_de.png +++ b/screenshots/de/features.roomdetails.impl.invite_RoomInviteMembersView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:93b580abe330752636e76451fa670ac637b6d365537a478c9ef9c8994d81550b -size 8886 +oid sha256:f47cfee459a5724d907f03c295907bce04d9fb23e1f1472d87c9e2ac6ecc9368 +size 8897 diff --git a/screenshots/de/features.roomdetails.impl.invite_RoomInviteMembersView_Day_3_de.png b/screenshots/de/features.roomdetails.impl.invite_RoomInviteMembersView_Day_3_de.png index 949ec34d43..a7da63a081 100644 --- a/screenshots/de/features.roomdetails.impl.invite_RoomInviteMembersView_Day_3_de.png +++ b/screenshots/de/features.roomdetails.impl.invite_RoomInviteMembersView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b580adefe5d563c71caaaf17639b84b7e6c8f5eda5c3d154cce9a5176ed7825e -size 24822 +oid sha256:ba11832ef57b2827b42ddd8266edd7e6b9219c7942fbb0090f07e2a7214687e4 +size 24803 diff --git a/screenshots/de/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_de.png b/screenshots/de/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_de.png deleted file mode 100644 index 1818e969cd..0000000000 --- a/screenshots/de/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3a1c48e7b690b44d5e2fea3a6a0538899269a663497fd4247196fda09582c426 -size 12289 diff --git a/screenshots/de/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_de.png b/screenshots/de/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_de.png deleted file mode 100644 index e288ea7c25..0000000000 --- a/screenshots/de/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:44edbf94083aff6c3e43d58315ed1909fa826f779194b00dcf6174de781e1343 -size 12528 diff --git a/screenshots/de/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_de.png b/screenshots/de/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_de.png deleted file mode 100644 index 1818e969cd..0000000000 --- a/screenshots/de/features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3a1c48e7b690b44d5e2fea3a6a0538899269a663497fd4247196fda09582c426 -size 12289 diff --git a/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_0_de.png b/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_0_de.png index 2922988155..17a0e0c2a1 100644 --- a/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_0_de.png +++ b/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:63a9eab94985fec27378400856858dd2a6899e831919d57f3b0804254e521056 -size 46007 +oid sha256:344c0d1c9ac8e0b15b396b9bae16136db85a1b81b0cb9f959e2f27a8d6e36c6f +size 13650 diff --git a/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_1_de.png b/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_1_de.png index d9707b6ac9..e6c3dab94a 100644 --- a/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_1_de.png +++ b/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ad3c2694bb93a5bcf3e1e2a48bb96e3cc4a60a41e1fcab0adebde63b9595006 -size 47203 +oid sha256:a762a5501000d41b1407045482440beb78742a9b671bdd2400d5c23dfb15586f +size 22798 diff --git a/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_2_de.png b/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_2_de.png index e288ea7c25..bbd4881433 100644 --- a/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_2_de.png +++ b/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44edbf94083aff6c3e43d58315ed1909fa826f779194b00dcf6174de781e1343 -size 12528 +oid sha256:fb9f15f43fe78b8cb9683297c3adec0b8354f3afe9b7deb523e0c94964c2ca7c +size 56601 diff --git a/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_3_de.png b/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_3_de.png index 918c13da80..3bfa26d702 100644 --- a/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_3_de.png +++ b/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3ac0f974d8d56b3ecc34ac6fbb0f033975c0ed513cb6e05f610d4a95ebccd8f -size 13860 +oid sha256:be327333c430fe7de69ee8540ee2f499e2e02575e95c05fc54b5264fa79b8771 +size 31384 diff --git a/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_4_de.png b/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_4_de.png index e288ea7c25..4bded04dba 100644 --- a/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_4_de.png +++ b/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44edbf94083aff6c3e43d58315ed1909fa826f779194b00dcf6174de781e1343 -size 12528 +oid sha256:9070e72ccf6ba4553cd093f686f55329221b88df7b188716f20b2029ad639517 +size 57820 diff --git a/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_5_de.png b/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_5_de.png index 6a4ec71b9a..0ef6abe726 100644 --- a/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_5_de.png +++ b/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2297fbeb63dbbf5281266f90015e047528e3287c923e2207af061458fd12897e -size 7929 +oid sha256:a9223b4e39ba96f63148d6b33ffb70cd3893926032bcf1b05e76193873f47716 +size 19654 diff --git a/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_6_de.png b/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_6_de.png new file mode 100644 index 0000000000..27e87d2a1f --- /dev/null +++ b/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_6_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5989462acb327dea61c68b082349b9bba499bf98968563f8c144a04169e205e6 +size 29897 diff --git a/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_7_de.png b/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_7_de.png deleted file mode 100644 index 1e4316618c..0000000000 --- a/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_7_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0a686e2024b1de0408f0d5920ac576b2822677dea98ab85cec11652edf3eb02b -size 24681 diff --git a/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_8_de.png b/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_8_de.png deleted file mode 100644 index 8397efd92e..0000000000 --- a/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_8_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:31de17fb1f44c06ffd9ce5d7e59c91b7da986720f90074802b1b566b55859186 -size 12303 diff --git a/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_9_de.png b/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_9_de.png deleted file mode 100644 index 425107966e..0000000000 --- a/screenshots/de/features.roomdetails.impl.members_RoomMemberListView_Day_9_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:736b973e4177726e15beee8377efc9e61db4e08198aef5d9c41873e653959f51 -size 21663 diff --git a/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_0_de.png b/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_0_de.png index 62440e7612..98c112d387 100644 --- a/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_0_de.png +++ b/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0ae03eeb528590d37def020f34e8ab138e16be7118c8742ffb5e5b0401ca91c -size 48185 +oid sha256:b5418e4c3ed3c02b1a78fc57e75b61182f6d230494aef7548f9d8305748b2bec +size 48196 diff --git a/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_1_de.png b/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_1_de.png index a0031de173..a80c3fba86 100644 --- a/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_1_de.png +++ b/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d6b27f6cf42ae88bb10a519096a3a6e469d6447a50996eeb61eb02b3bdbbfa26 -size 49895 +oid sha256:2c26e8b5415952aacd078ca4c02a851a65404ce87f1e0b756386d08dd111f527 +size 49908 diff --git a/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_2_de.png b/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_2_de.png index 874aa52144..5006c87051 100644 --- a/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_2_de.png +++ b/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f73fa94cd961c62d08d0abf046560c780fcadbf0ac412693e741fde8d093a3ee -size 40690 +oid sha256:ebda49047af7040febf3bb5cae22521d9043dadd9e74700010e8ab8a45252bd1 +size 40663 diff --git a/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_3_de.png b/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_3_de.png index 8a3190e8a6..03be47ca26 100644 --- a/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_3_de.png +++ b/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb7aae1df2a4cbfbda74c6c8812198140e54f12b7798af9a1a90500616b05595 -size 42653 +oid sha256:122f016a8ff5a1baf92bac5834189aca266ead157c4b5b4de8adf01994550f6d +size 42631 diff --git a/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_4_de.png b/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_4_de.png index 874aa52144..5006c87051 100644 --- a/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_4_de.png +++ b/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f73fa94cd961c62d08d0abf046560c780fcadbf0ac412693e741fde8d093a3ee -size 40690 +oid sha256:ebda49047af7040febf3bb5cae22521d9043dadd9e74700010e8ab8a45252bd1 +size 40663 diff --git a/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_5_de.png b/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_5_de.png index 8a3190e8a6..03be47ca26 100644 --- a/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_5_de.png +++ b/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb7aae1df2a4cbfbda74c6c8812198140e54f12b7798af9a1a90500616b05595 -size 42653 +oid sha256:122f016a8ff5a1baf92bac5834189aca266ead157c4b5b4de8adf01994550f6d +size 42631 diff --git a/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_6_de.png b/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_6_de.png index 62440e7612..98c112d387 100644 --- a/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_6_de.png +++ b/screenshots/de/features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0ae03eeb528590d37def020f34e8ab138e16be7118c8742ffb5e5b0401ca91c -size 48185 +oid sha256:b5418e4c3ed3c02b1a78fc57e75b61182f6d230494aef7548f9d8305748b2bec +size 48196 diff --git a/screenshots/de/features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Day_0_de.png b/screenshots/de/features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Day_0_de.png index b3fd6da7c2..0276033b12 100644 --- a/screenshots/de/features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Day_0_de.png +++ b/screenshots/de/features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:02ee83247e54bb39de7b3bc5e69e3bec21f4b45e374c7e5002a4612104e34025 -size 26304 +oid sha256:1ea526df6ed53d1e82dced90952e03e87b6ff4abe5e4f1bbc86c7b7d366d092c +size 26316 diff --git a/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_0_de.png b/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_0_de.png deleted file mode 100644 index 25b2d9076e..0000000000 --- a/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_0_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5df79822c29afc140d67075460ebb3ac32acbb12bd48b422cddf2b7f30f0d5a7 -size 38677 diff --git a/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_1_de.png b/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_1_de.png deleted file mode 100644 index a1f7290ef4..0000000000 --- a/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_1_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2d5ad508b5d73947fa07f9d5f56ec49949bd4efb60ff239df245814e26925a60 -size 35579 diff --git a/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_2_de.png b/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_2_de.png deleted file mode 100644 index 75478ce4ad..0000000000 --- a/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_2_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3d73f8dcebed2575d4de00ebf9b44e19e46fabed0fd9b252c5df6f5c4e82753d -size 47314 diff --git a/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_3_de.png b/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_3_de.png deleted file mode 100644 index d48bbf0afd..0000000000 --- a/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_3_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:02d9b9d5219a47dfd74a523f3e3b38e6273df6681686b086fef15187cde101d6 -size 38608 diff --git a/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_4_de.png b/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_4_de.png deleted file mode 100644 index 2f7500dd52..0000000000 --- a/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_4_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:be91a4e37639900c7d822b7922ebd3304e0cd6f33d69a8dc8797fbc898389f30 -size 36516 diff --git a/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_5_de.png b/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_5_de.png deleted file mode 100644 index 68d5d0381f..0000000000 --- a/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_5_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f6b9b30eecec15ac0adf1ba18dc578553770e228c944d928dc87bb308ad66a95 -size 38846 diff --git a/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_6_de.png b/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_6_de.png deleted file mode 100644 index e80ac211bd..0000000000 --- a/screenshots/de/features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_6_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:630a97f988f05420ac8348c9f5a446970ce1c823b7157772b6a1294410a0a555 -size 48100 diff --git a/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_0_de.png b/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_0_de.png deleted file mode 100644 index 391ef68dc9..0000000000 --- a/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_0_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:199d550dcc10ad32149be1e99aa8da10b0bf13f072e54a8e809e050f8cff2335 -size 48598 diff --git a/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_de.png b/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_de.png deleted file mode 100644 index 1a36865c33..0000000000 --- a/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d4973fd90ead80ac942ed9c7209ed309485d43b87cbd1ec89e4f0989f2a14e4c -size 50550 diff --git a/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_de.png b/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_de.png deleted file mode 100644 index c64083082c..0000000000 --- a/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2c95fe5ff025b2690fcf4466ece56f41f0758e84004a41d00fda090c5bec0811 -size 63879 diff --git a/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_de.png b/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_de.png deleted file mode 100644 index 113372b8b1..0000000000 --- a/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d52e4e8ebb9f92f23445be1374cb1c99a55ca4bc855566f7eca08e31ec419ee4 -size 45862 diff --git a/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_de.png b/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_de.png deleted file mode 100644 index 2540918855..0000000000 --- a/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:638d958fa225877f475feb357c0d839fef8a05b876238522c9750a8b90b23358 -size 43922 diff --git a/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_de.png b/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_de.png deleted file mode 100644 index a61d4381bc..0000000000 --- a/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:89d4726d9deb408e2a7fac1a71ad487b154faa9735131bd1fcbd64d0587746bc -size 56525 diff --git a/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_de.png b/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_de.png deleted file mode 100644 index 113372b8b1..0000000000 --- a/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d52e4e8ebb9f92f23445be1374cb1c99a55ca4bc855566f7eca08e31ec419ee4 -size 45862 diff --git a/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_de.png b/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_de.png deleted file mode 100644 index 23571dc46c..0000000000 --- a/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c60eaa6e529c230303960d0fa3ca03eec6c010bef06bb6a46db2d1241472a312 -size 43377 diff --git a/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_8_de.png b/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_8_de.png deleted file mode 100644 index 3ef980974f..0000000000 --- a/screenshots/de/features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_8_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:20c92b5c171e00018f7e5ae66e55706d2c264eae4d76db727241a5978d0b8d99 -size 46763 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_0_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_0_de.png deleted file mode 100644 index 295c6141f3..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_0_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b17bdc28601b61df525fd8375dd83d79ef8963dc03a436979fba88564fb12318 -size 26476 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_1_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_1_de.png deleted file mode 100644 index cf5a3e7dc4..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_1_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:41607b403d6a0cf790db112ef3908df6f02ce1a2013647f667af8a539221888d -size 31781 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_2_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_2_de.png deleted file mode 100644 index 7d68ce8154..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_2_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c0c1d9181d312dc01cceedd9fb446e7c4e704b20dad2eb00eb933dc0474167cd -size 32512 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_3_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_3_de.png deleted file mode 100644 index bffec9be0b..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_3_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c404ee0951b8f5102e6c4be33818eedde2b74c0dad4e9bcc3d1680a21ddba208 -size 26378 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_4_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_4_de.png deleted file mode 100644 index 855fe194c6..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_4_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0a840f0965ec9cf1a0bb10e0b6421682388ec9153489b1306ba21e34de67cab9 -size 28445 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_0_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_0_de.png deleted file mode 100644 index 4cfdd1a2df..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_0_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:10c9b3b2b60263b77aeac6a0a726200d82c89fb8ef3f9fc1c5d67eaf82748c0d -size 49097 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_1_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_1_de.png deleted file mode 100644 index 7ddf22af02..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_1_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e7af5e98b0b15551c1c91fff4e5333dfdda2b0edc2b2c94b69a30ead3957f332 -size 66780 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_2_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_2_de.png deleted file mode 100644 index f2a212c869..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_2_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e0e0bca9a56eae0d774652862621c1dc33feb1dfae88f633726c0c795a1d2427 -size 66905 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_3_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_3_de.png deleted file mode 100644 index 7de7d8ce2d..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_3_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:57cd6df19cc7b61a1c528e7ed107f8f6006d8a9cf1b82946fd45acb78746644e -size 67046 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_4_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_4_de.png deleted file mode 100644 index f36a6399ed..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_4_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:921f770410cbb2d168bf8b1ad7c3297208d7ca7d8d610ab30b4b65ffbdb9897d -size 66728 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_5_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_5_de.png deleted file mode 100644 index e3f4fe168a..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_5_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:19d8a60cf3c51f4ac26afd9dc8971d747c0ccf7ba49b3e8aedc3f06db54f6d2c -size 48860 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_6_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_6_de.png deleted file mode 100644 index e3f4fe168a..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_6_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:19d8a60cf3c51f4ac26afd9dc8971d747c0ccf7ba49b3e8aedc3f06db54f6d2c -size 48860 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_7_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_7_de.png deleted file mode 100644 index c8d111b76e..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_7_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d685380e4b4ec9634ceab25bb989dda305000bbc63d388d308584ce9be41e900 -size 50034 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_8_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_8_de.png deleted file mode 100644 index da38ec710f..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_8_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1711578b347285b594410d9c5bba9c5631fc930e45ccb78b25820b5f02a7a52f -size 42777 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_0_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_0_de.png deleted file mode 100644 index 21b2757a42..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_0_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6d11b17b52172a2d8b98acb81c0659dc97d09ee75ab232557a8622bbcbde22c0 -size 51007 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_1_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_1_de.png deleted file mode 100644 index 1bc76bf222..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_1_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ec5fc09759b68d94af3e9cb9b8a049b4b09f9c5d2088014021bf662f45a89d03 -size 69361 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_2_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_2_de.png deleted file mode 100644 index 9fcdbe91b4..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_2_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:016bfc7341d5a7811d02a3343b3d05caaf9c822b3a3d773385a7b4777644bf6f -size 69581 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_3_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_3_de.png deleted file mode 100644 index 403b3e6875..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_3_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e16d6e7dac990919c01f3a50dbad23de8122e9fce0f9ecb01cf392d2013dc037 -size 69597 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_4_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_4_de.png deleted file mode 100644 index 566b4a29c2..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_4_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:40e3e22e21b260c716f5d03a8630c9acfb8d84ee054fc996e952c71076682afb -size 69241 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_5_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_5_de.png deleted file mode 100644 index 4630cddbc0..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_5_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:38cc47045d968a748f6b7abd6b4415b1df9daeae23ae3ab00cf6c9850be4e025 -size 50973 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_6_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_6_de.png deleted file mode 100644 index 4630cddbc0..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_6_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:38cc47045d968a748f6b7abd6b4415b1df9daeae23ae3ab00cf6c9850be4e025 -size 50973 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_7_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_7_de.png deleted file mode 100644 index 63c9d2f2ec..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_7_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9322e1e84b86d8deeb0ff07ce296847c54d620901fa072fbe5321c1197fac16e -size 52370 diff --git a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_8_de.png b/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_8_de.png deleted file mode 100644 index afa28c596b..0000000000 --- a/screenshots/de/features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_8_de.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cc795edca25781362cab6a56ba73dd3ebde43544a82c94711b04add0fcde6760 -size 44368 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_0_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_0_de.png index aa301e7388..898a72ea3f 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_0_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:157e55e65bc769b489076156cd387185857480b1da354303fab02084b2475085 -size 46422 +oid sha256:b8697c4c08bce7b3e51730641c438a88a61a86d0f4a0eee4945d1a8645cd229c +size 46344 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_10_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_10_de.png index fccdf986ff..027896e5db 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_10_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:57c333bda4ed242cc088ed897b10af0e46ddcb02a15d2703b99b3ed4c8655f65 -size 45292 +oid sha256:b50eb0bb60ecd651d06348e2aec20b3dd44666cca9102b0d2403471905bf47a6 +size 45238 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_11_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_11_de.png index 31229a63c2..a24498e869 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_11_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e9e86d6b2c035e2051a53443e389fb6f64bdfb9a8735d503e7fc15dd50572802 -size 43704 +oid sha256:548e6a8fd8f8fdb037f206412b765d89524f40711d1aa1e25fec7693761b098c +size 43758 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_12_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_12_de.png index 91a19d8b15..189a57b5f7 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_12_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_12_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1e1b2535d2ba22a2fbdcfdfd2af2922fb823cf48e5543597ae495157d7c93de2 -size 47448 +oid sha256:5c4eec1f4dc41a34d4dcb6bb19f06e82df3af61d5502a1d69390c92a8dfe61ae +size 45547 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_13_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_13_de.png index b01e41e9bf..9e19611d2b 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_13_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_13_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c21465a9222bd9c1e8ef4cd87dfca89f58a680a1159a41de59c5f235dad600d0 -size 45520 +oid sha256:aa53be2c34487dbd6d256ac08bc0472e5a9041fcdfd4312b41b1587e1ade02c9 +size 45460 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_14_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_14_de.png index c8b623827d..d34c5f2d0e 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_14_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_14_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2de1957ccfe09b93100ed61addcb8274d6f20c87a28759d11ab02ba8a1638804 -size 46104 +oid sha256:1b0f4b25c48f814d0f65a9ebb90e624fd063e85c38638c34b5c2112c26d0a19e +size 46030 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_15_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_15_de.png index dd58719400..daea8bdf49 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_15_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_15_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7bbb4123dad65552f7a5ccd998b95c5adc2a31ac7102f3c6254f8eebb8d12db0 -size 46638 +oid sha256:694675fede76219bec32f4eb90778dd36a71c6949bbcb5b4e324ac95801dcbbe +size 46564 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_16_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_16_de.png index 666d8d0c47..8746083afc 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_16_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_16_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1124f60a8b3018c53e1f4a5ab822e49fb6224859901f49957a61462e00f62a36 -size 45864 +oid sha256:26f73d4bc06ff2bb9f0e322964c1131dde1f6c546c5ff9b7209a6dbcb5dc7273 +size 45810 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_17_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_17_de.png index 624318885c..af83349940 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_17_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_17_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df9db0d4e17dedc02ff65dfced875fd454e8648b44147a059ae77da7fbffac86 -size 45099 +oid sha256:7ed62b88643773270a7a045e9c80c29fd3f939c312ec4752cbbfac0a7d001f4f +size 45039 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_18_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_18_de.png index 2f6f00a2ab..0f1444ad40 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_18_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_18_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4aa3e3af5c19c99f5d4e602344631e3b146a06c76cef26e83e729cc2cc9cb310 -size 42265 +oid sha256:3c320a405aa9fdaaffa5ead648fa33d42f7c946c2a07fad2c803b084319f98bd +size 42275 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_19_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_19_de.png index 1f1fbb8c9c..9835d06f8b 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_19_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_19_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09f77e0c5a383e988fff92c1ef940a6f74d824af05ef6a5740430899887cdc04 -size 42200 +oid sha256:11a4d98f789ff1ad1e941a3155b4043e02b06858b319c03c6f1d2b419ed5edb3 +size 42231 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_1_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_1_de.png index 9e5cf2c548..58bf8b1e3a 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_1_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8cf15623f94fb77ece859777a5d9f874e9e799afe61a181ebf7c231e198d9d6c -size 41991 +oid sha256:432bd93e477130a98f2a4111de5051e0f19151594843365555d1428b03b991db +size 41923 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_2_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_2_de.png index a1fda78fee..0c18888edd 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_2_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7871c2de6d77425ba0ea7d773328a2a3272594b35a8602a01054fc2db1c1051 -size 39609 +oid sha256:8ff5afe13a98cec38fc19ff957fac1468ab440c8c2fca2e0d157e447a9739bc0 +size 39554 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_3_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_3_de.png index 5e8dc32fce..8c4387fd4a 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_3_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a867633de36bd2967ad0b2049cccfa3edef59f3eb732478be8bbdb03c6198a69 -size 45734 +oid sha256:a6e87fbf054dca331f6380f59fa40a1dbecc40c25bcb29ae09817360527a8f10 +size 45629 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_4_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_4_de.png index 64e13fd312..89d60e7c71 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_4_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01791e192c3cc7429544e4928e696a90b6a32a584dad5fedea8301f37b31ad79 -size 44840 +oid sha256:687e7b880a751047ec0cfffcb85b4c0cbfb04f52958cd731036f810b05d8784e +size 44805 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_5_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_5_de.png index a9e70a26b2..def514b66f 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_5_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:031a260a9859d77cf7a69fd2fe6c52f3bd694dc7c25c15fe2315e236ecc4eae5 -size 41891 +oid sha256:1343c24e5eea07d5f222d96fe7ee7dc6b5c42b3033c6b707758753d66489180c +size 41916 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_6_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_6_de.png index 77947d259d..42dadff2cd 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_6_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:03a08a3999039ff5ac3a696b26e778d3136b21b5f30b5dd0a49b077170d79869 -size 45361 +oid sha256:bfd031259f414e7bcda1934e152f47d062114b76b5e1b3b38329291f6086d999 +size 45315 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_7_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_7_de.png index 3c026d6c15..206579177d 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_7_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6967b9dbd4f79556952c527985e4cdab7c569253716a5d62eda24d46b33a703f -size 46516 +oid sha256:2862e36d811b09539ef1fa6b80690c0dca99a845a599682087ee4df223c76308 +size 46436 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_8_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_8_de.png index 72aadc6460..f6fe12c78a 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_8_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51ff86136abb6f6d76ce6f51978aebd4d710fe7f1b753a7782146e28b5656d7a -size 45573 +oid sha256:d78af3f2a5a18d43a2e2c7aef496b0a9e826448db27ca45f6aa2264385194aea +size 45511 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_9_de.png b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_9_de.png index 8138af4888..cd5134cc6b 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetailsDark_9_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetailsDark_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e338bc62994b247477cc169c0bcba1f8d428b590f2bba1966136f52ee7b7441 -size 44529 +oid sha256:49b6ff5a0187daf1902e3b79367edaf65a3cfb3a330bc7166914dbd9c8ca9606 +size 44460 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_0_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_0_de.png index 104a5e4e1e..48b9496226 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_0_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:49a21b1f181bd32b9895763b045952332225e71dada7fc155203bf74b1419aa0 -size 47522 +oid sha256:6fe2a7ab133e43acdc59731090b26cc91393e246c45fcc55e8f38df6e1f17521 +size 47500 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_10_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_10_de.png index e4504b8123..637ac4455f 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_10_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f588f9d3f51ba3ff764a9d4bb3b942e3ad396f939115f35da0a794a91b61aba -size 46342 +oid sha256:9a106b162fbf7e67b0534b71140e34ebacf801c08d1ebe17bbeb760b460f03df +size 46303 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_11_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_11_de.png index 455f0d9723..a9a740f83b 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_11_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f3ccc11ecf31afd2caea6af382d0d3b1ae58e90fbc4e506df5a49713fd0f698 -size 44840 +oid sha256:4dda5a34c3e225141274c0452fc20d9519c14f34fad41e8dec50e786d8104918 +size 44838 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_12_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_12_de.png index 9b95c8d176..f69d34b445 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_12_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_12_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f4ed50b1b0eaa7dbf2e050f4e1f82f46626a3b3bc986bf633fe3f5337a610c32 -size 48061 +oid sha256:c3c749f1109ea691f01daaa4cce07f4c131c5d13bce64c4116fe8b848966e590 +size 46648 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_13_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_13_de.png index 792e6f22fd..d87e06a028 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_13_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_13_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ffbe45a0a19c19ecc59259107b9f4acc58ed0b994abd0ac0603a7051d2188f4 -size 46596 +oid sha256:57053f9c361cf69db35174e3325b0b4753979f011b938648efc37500f0c1a185 +size 46560 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_14_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_14_de.png index bf121f9929..fad796a3c0 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_14_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_14_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5aa801587fef2a7131cd11e161969e028331be4b4a429a86d25bb3c777757144 -size 47119 +oid sha256:65382b546da02a6bd3cccfbaaf49eb08288448984a1d701587c8baf4fa538b74 +size 47093 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_15_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_15_de.png index 5f1495fb1f..b524ebe3d9 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_15_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_15_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:86b0e4d70d7dc9f5093e301ca1a7e5364a99c2c6f5608aaaf2fced230356faff -size 47703 +oid sha256:e770eb2039774eca98cbe08aa46af19cc72aab7ae764f969cd1cfa7fe23dc98a +size 47687 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_16_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_16_de.png index 67cb9400f5..6993668499 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_16_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_16_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f00897f7d6cf5c6a9d319bb69390cc10d4be20451850846b14521361f46c9267 -size 46942 +oid sha256:762116046969a7126042982e89b1b79dc82a4d1ff8763dcbf5b8343cdeca4612 +size 46909 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_17_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_17_de.png index 903e2989b7..0597569c93 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_17_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_17_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4aabc001f2f14832f71dc331bd53f001f67d4f2d248768e79f41708b46d671e4 -size 46431 +oid sha256:c1c87b29315c1aec93f8c0c231f241deed8dbd37384468474c4db0b976d6dda4 +size 46395 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_18_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_18_de.png index 9aefd7740d..97c48d314e 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_18_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_18_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e2c2bd15e9a027430346841bfc040f99f4e5545138a09fd01e9af64f19a1172a -size 43316 +oid sha256:d5033268749c18000be1899d82ebffdc412a0c810855dc542202bf39c354060a +size 43327 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_19_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_19_de.png index 7b51cdb172..49314777bc 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_19_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_19_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc24fd0400fefd433cff35b57599259e23bddf4bf24a884dca5194e4ec090eea -size 43196 +oid sha256:82c7d37bdf17f5115f3e839e61085db472cbc2144e34b0c42db8f68c6868e269 +size 43215 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_1_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_1_de.png index ebe6a3d972..008555c998 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_1_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1ff08b7776a8f9c772928d88028bb43da2854dbd140a16cae65b211bee05179 -size 43219 +oid sha256:4a79e3d57386eb05835352d931fd264bcf0303bd21fa15cf44e9a3690d1e537b +size 43131 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_2_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_2_de.png index c3b7fcf83a..ea3581bc36 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_2_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d102c08371deb5252914ef656b0645a071422af8dd4b425de9b542fa4d6593ec -size 40739 +oid sha256:5bcdcdad130bc08d5242a6f83286300455f7e42dd3eee37451dfb10d7b9d7655 +size 40708 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_3_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_3_de.png index 680b42a906..6bce89b21b 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_3_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73839237353e443c199c56b67aa4d51b1cdb9b6d08707bf31cc3376546f97cfd -size 46673 +oid sha256:5bb57d0c50fb54e3cf145cc614312a4c165118dfbfd7795b7bbfc06f4d4713d4 +size 46610 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_4_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_4_de.png index 55b3e290bd..0b00f96dc6 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_4_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d32fb0f4761cded0b823b4e221b8d920cae8cae79e39895b91d1f861ef5f89b5 -size 45887 +oid sha256:fee173f492c24aa69a4c64afba1f4cb856d742ac089a2bf25526a28e8fdaedae +size 45826 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_5_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_5_de.png index 074167caed..6bf05b9e6b 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_5_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7e5e5ad50149ea60474d93498cf7a671779d1ccf75f89c091f864fac94a5335 -size 42858 +oid sha256:2ed7b3bb40e0dfb76872d85c07e147f55964622bf102435ae8e98524c52ede06 +size 42872 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_6_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_6_de.png index b94d9ca17f..6649e7ee0c 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_6_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ecdfe8dc5fc2fb547ceb0babda9856b2b0300c8794e9f9201f93f58693fa1532 -size 46517 +oid sha256:016e70e04d4fa9512a72ab00f33578b59b3cb5ef27881558cc4842cf5e89b025 +size 46503 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_7_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_7_de.png index 8ae0e7bf9b..59d5e3ae1e 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_7_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7decfaeb8bd1929d6232a5148b8b7a958eeacc1bf5baf779f6f633c44dfa53e8 -size 47787 +oid sha256:a15fae40d21bb7729f1a0dd383533d1941423d0812cf51e67adc56f5054f09d7 +size 47769 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_8_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_8_de.png index 9096b37553..83df8713ae 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_8_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a212f79364028820a27448e6fe3849bbcf9a10a66d918eb347e546c65280bfca -size 46705 +oid sha256:908aa907fa85dc18825e3ecb5da8ad12916577df28045b256c95a21b524575a8 +size 46670 diff --git a/screenshots/de/features.roomdetails.impl_RoomDetails_9_de.png b/screenshots/de/features.roomdetails.impl_RoomDetails_9_de.png index 17d4a0a3c0..746f9ff2c7 100644 --- a/screenshots/de/features.roomdetails.impl_RoomDetails_9_de.png +++ b/screenshots/de/features.roomdetails.impl_RoomDetails_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58188951ba569cb9a9fafa778bbdf9f83f7a6d201975eb9c37c4f48473e8d331 -size 45567 +oid sha256:7776bbd14000795414995fc0428a80659cb6b45d06b35c6537eb66fd69b341c4 +size 45529 diff --git a/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_0_de.png b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_0_de.png new file mode 100644 index 0000000000..45d287b5ec --- /dev/null +++ b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c7a8a805ff40cfc6194a414a2d1c1babc5dc2335fb5c96bb72f6f2f412c91346 +size 30052 diff --git a/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_1_de.png b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_1_de.png new file mode 100644 index 0000000000..91845d6b8f --- /dev/null +++ b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6ccfe1ff08087bd75a8e0017f5d12540cc567423801050dc091005722f3d9b1 +size 24575 diff --git a/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_2_de.png b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_2_de.png new file mode 100644 index 0000000000..16fde1067e --- /dev/null +++ b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55711397eb8fa3717a3891e56c078cca643520b2d0fefeede118b19db8676341 +size 31204 diff --git a/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_3_de.png b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_3_de.png new file mode 100644 index 0000000000..b366ffe07c --- /dev/null +++ b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:89bcd852bb60044ba429d5c0a884b05d3a28403d985f95bd1582f1f45baca003 +size 54044 diff --git a/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_4_de.png b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_4_de.png new file mode 100644 index 0000000000..e8642f7bb8 --- /dev/null +++ b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f8fde96567da32ec9a97eecfb1224b5ae23aa0c7608792036f452eebbad466a +size 51217 diff --git a/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_5_de.png b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_5_de.png new file mode 100644 index 0000000000..f38d37867b --- /dev/null +++ b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_5_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e343b3df1a386702c6163393ff05d342abbc9c245ba93129d24e7be5acdaa39 +size 30079 diff --git a/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_6_de.png b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_6_de.png new file mode 100644 index 0000000000..60f40910a3 --- /dev/null +++ b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_6_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ffebfde2aa3d3f3a7e9f7a3dbf8b9e6b8044657815735d2d8c401d2caa8ab51 +size 30225 diff --git a/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_7_de.png b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_7_de.png new file mode 100644 index 0000000000..faa2a0b84a --- /dev/null +++ b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_7_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4253142dd34410c924bfd0499042a05b9ad59afa7073957835a8d4afc6a7d5a6 +size 25470 diff --git a/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_8_de.png b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_8_de.png new file mode 100644 index 0000000000..534874b570 --- /dev/null +++ b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_8_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4fae7c1d5e7bd4f10658bc1777af60ae110278e283a03ca52855e990448f8a4 +size 31022 diff --git a/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_9_de.png b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_9_de.png new file mode 100644 index 0000000000..e95bd5c15d --- /dev/null +++ b/screenshots/de/features.roomdetailsedit.impl_RoomDetailsEditView_Day_9_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da72832a3646008468a061de42964b7d98d563d3a2244f63d168c4a2f38247e6 +size 32513 diff --git a/screenshots/de/features.roomdirectory.impl.root_RoomDirectoryView_Day_0_de.png b/screenshots/de/features.roomdirectory.impl.root_RoomDirectoryView_Day_0_de.png index eccb0ac039..564d2206f3 100644 --- a/screenshots/de/features.roomdirectory.impl.root_RoomDirectoryView_Day_0_de.png +++ b/screenshots/de/features.roomdirectory.impl.root_RoomDirectoryView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ad3b20348912fd6f6c3e4a8e68e2ea19a491f61239c59643b0b247cd5284ef9b -size 13545 +oid sha256:b47ff7cc22cb1c7477cec1ba425feb97fa663af79455466a48a729cf13fb9445 +size 13544 diff --git a/screenshots/de/features.roomdirectory.impl.root_RoomDirectoryView_Day_1_de.png b/screenshots/de/features.roomdirectory.impl.root_RoomDirectoryView_Day_1_de.png index 84fd62c0f9..0392e16c98 100644 --- a/screenshots/de/features.roomdirectory.impl.root_RoomDirectoryView_Day_1_de.png +++ b/screenshots/de/features.roomdirectory.impl.root_RoomDirectoryView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5fb71ee98f44955ac96021846d6e0a9e6f8dc6fd0a1175e7ce974fe3adf95613 -size 28485 +oid sha256:da4a6565f96d0a8a5d96c383745338ff6cf0c91e7b352382e1414ae79735b538 +size 28445 diff --git a/screenshots/de/features.roomdirectory.impl.root_RoomDirectoryView_Day_2_de.png b/screenshots/de/features.roomdirectory.impl.root_RoomDirectoryView_Day_2_de.png index a332fab424..9533ef4fec 100644 --- a/screenshots/de/features.roomdirectory.impl.root_RoomDirectoryView_Day_2_de.png +++ b/screenshots/de/features.roomdirectory.impl.root_RoomDirectoryView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:362aa58b91f2ca04960fa1342f76ba28e28ddb8721acf92bf63ef0099d49c695 -size 30106 +oid sha256:dc7cab44849946fc957a8705046547355a9508b5a6e324a18b9c4848b29d4601 +size 30071 diff --git a/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_0_de.png b/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_0_de.png index 0b94bf888d..a5638d89f3 100644 --- a/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_0_de.png +++ b/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b9f27b5742eb06ee518512a2bfd2e0e01d3e70ca248370e7e9155427c6f1aa8 -size 18466 +oid sha256:90712ac1929c94927172c10a09ed8c9955e44aa4d87d02020f9bc8781d87f30c +size 18775 diff --git a/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_1_de.png b/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_1_de.png index 1c38d575cc..9c94b62b9c 100644 --- a/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_1_de.png +++ b/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ca13853e5ada913d20dba5e3eeb6965006d46b6d035498fd082afa4c78f12525 -size 22336 +oid sha256:e2871173d98a9add3a41617e6033ce4a293e7ee683997a2ffe9e60f4ecc5513b +size 22477 diff --git a/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_2_de.png b/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_2_de.png index 29e3b32504..207a840978 100644 --- a/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_2_de.png +++ b/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:84faad4490bfbf48481ba870bc34c5b99d72984292b3b33d2c6bd619e2b78621 -size 27786 +oid sha256:25800bbb0ebf715f7d8977ecd466c545c7b603703e145ba8d87e5572f4bf5421 +size 28052 diff --git a/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_3_de.png b/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_3_de.png index 5e828156fc..348bc07623 100644 --- a/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_3_de.png +++ b/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1937f15d45ff18da9b3203fe420aa6242114de82a9614e04eb99f5c36bc567a7 -size 28214 +oid sha256:3e3c9507e122bf1e90a459ec0a772baaf3811f7f7d41e9e48f20bc2528aee5d1 +size 28467 diff --git a/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_4_de.png b/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_4_de.png index eb8f0f1e95..1c00e239fa 100644 --- a/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_4_de.png +++ b/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dfe0aa4dc149bda0330dd662708f243d3a99d05a2d419e02dd364d0898c29365 -size 34129 +oid sha256:1937d993e7d8ec140c11e205de51d2d6773ea993546ad2e169186289f162583e +size 33529 diff --git a/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_6_de.png b/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_6_de.png index 9a626deaab..f0e3fc808b 100644 --- a/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_6_de.png +++ b/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5037873ed63f558691c06a63f93eca0d22dd2ec1f70aec2dcbf9481e73d16412 -size 32099 +oid sha256:3b301b181f1a295b4c1ee6bf60732e362f205249913b0b834dc2e4e1d06820b3 +size 31674 diff --git a/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_8_de.png b/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_8_de.png new file mode 100644 index 0000000000..bfdf967dcc --- /dev/null +++ b/screenshots/de/features.roommembermoderation.impl_RoomMemberModerationView_Day_8_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:43ff00a7191d7b427fe54d1cdf0e193722ee50d9481fde1725510722768422e7 +size 36305 diff --git a/screenshots/de/features.securebackup.impl.disable_SecureBackupDisableView_Day_0_de.png b/screenshots/de/features.securebackup.impl.disable_SecureBackupDisableView_Day_0_de.png index 2c946bc23e..cdfb6208ce 100644 --- a/screenshots/de/features.securebackup.impl.disable_SecureBackupDisableView_Day_0_de.png +++ b/screenshots/de/features.securebackup.impl.disable_SecureBackupDisableView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e82a7124397546cb3c2293736b743cf2c5ef943ba010c6d7b73b2d70a368a2ac -size 75425 +oid sha256:e3fef37fdd165e3d8a8bad0d2714e1902fa430e1fb5116fbfd98f0238a583951 +size 75446 diff --git a/screenshots/de/features.securebackup.impl.disable_SecureBackupDisableView_Day_1_de.png b/screenshots/de/features.securebackup.impl.disable_SecureBackupDisableView_Day_1_de.png index 2c946bc23e..cdfb6208ce 100644 --- a/screenshots/de/features.securebackup.impl.disable_SecureBackupDisableView_Day_1_de.png +++ b/screenshots/de/features.securebackup.impl.disable_SecureBackupDisableView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e82a7124397546cb3c2293736b743cf2c5ef943ba010c6d7b73b2d70a368a2ac -size 75425 +oid sha256:e3fef37fdd165e3d8a8bad0d2714e1902fa430e1fb5116fbfd98f0238a583951 +size 75446 diff --git a/screenshots/de/features.securebackup.impl.disable_SecureBackupDisableView_Day_2_de.png b/screenshots/de/features.securebackup.impl.disable_SecureBackupDisableView_Day_2_de.png index 6912ac7984..90c4f96ac7 100644 --- a/screenshots/de/features.securebackup.impl.disable_SecureBackupDisableView_Day_2_de.png +++ b/screenshots/de/features.securebackup.impl.disable_SecureBackupDisableView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9e43d692e1cd54898f38019f50c0d36b9575d4c2db534d898999eb9ed167bf26 -size 76051 +oid sha256:f15436b9be3f69f7b9fdddda24587a223d9fa72d8bbc208bff04004ff5b6aaa9 +size 76072 diff --git a/screenshots/de/features.securebackup.impl.disable_SecureBackupDisableView_Day_3_de.png b/screenshots/de/features.securebackup.impl.disable_SecureBackupDisableView_Day_3_de.png index ff25ff9a77..c962475c0f 100644 --- a/screenshots/de/features.securebackup.impl.disable_SecureBackupDisableView_Day_3_de.png +++ b/screenshots/de/features.securebackup.impl.disable_SecureBackupDisableView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3f99c41bdd09088037a6fc0e7436f71867e679e2d4f829156cf774cf5c06ec8 -size 46051 +oid sha256:4c85326d9afc3e9fc8c0924640614185ce7f23e4d499923da65b133ecaa04637 +size 46031 diff --git a/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_0_de.png b/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_0_de.png index f1c7d5fd03..39e20139cc 100644 --- a/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_0_de.png +++ b/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:82b6921b8e15336319f4ccc2a9f30f4e0f0bb8aac01bea84f2229d12eab87f22 -size 44547 +oid sha256:3c9d7b5a84939642a400c1e18429d3c1f8ae536487fc104caca1f385a4dea305 +size 44577 diff --git a/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_1_de.png b/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_1_de.png index b86d0f8ff7..73c20a9bca 100644 --- a/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_1_de.png +++ b/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6041785efd5e5f3551639d23ff6810169855efbc6de52343ff7bbdd5afc50db0 -size 53964 +oid sha256:1fa08f99a5a4e788be849ff982df1a1425dfaa9afbcb4bcbfbfcb95f2ffaf505 +size 53980 diff --git a/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_2_de.png b/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_2_de.png index bec4d30b3f..f07a6e1bf4 100644 --- a/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_2_de.png +++ b/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78438eb23e545b1ed8321502562eec75a720f8161ec96b80a45d2cf37e999a57 -size 53878 +oid sha256:31cf015c6ddd7d843a708a747e077e5c4f3c18e7571fa54281da42e7366be9a4 +size 53897 diff --git a/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_3_de.png b/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_3_de.png index c2cfc1532c..0b40e621ef 100644 --- a/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_3_de.png +++ b/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05096ace3fae4368464a78816164680ebecf5a65b03eb9bc6fc6eb06a0a43d81 -size 50930 +oid sha256:129bb03abd0a7ed366ad051da536d344466df5f42631ddaedea84d6117dea660 +size 50914 diff --git a/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_4_de.png b/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_4_de.png index 7e90bb8d4c..66b33674a6 100644 --- a/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_4_de.png +++ b/screenshots/de/features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eddffdae8d6b515a4140ad716e52c965b082f0fb1425aed9846c9d9ca35334eb -size 47357 +oid sha256:04820059ee8f505667f6647c63863777d525a1be0cbf77e5c61a615f721b7aef +size 47390 diff --git a/screenshots/de/features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_0_de.png b/screenshots/de/features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_0_de.png index 21c587b71d..3603bafeb3 100644 --- a/screenshots/de/features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_0_de.png +++ b/screenshots/de/features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce26976414de9975b52aab806a8ff3de282c9ae7e51999b5071d5e34be0c08bb -size 30651 +oid sha256:26b5ad528a635e90d966ece5564fe8f4e5549a81192aec7fc3f669ed6765388b +size 30667 diff --git a/screenshots/de/features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_1_de.png b/screenshots/de/features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_1_de.png index f246e38806..fb6e1215fc 100644 --- a/screenshots/de/features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_1_de.png +++ b/screenshots/de/features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba0700dea1c4dc744101290e093b205dc3f24b1dd0de8ac2ee14103d636b0d70 -size 29851 +oid sha256:22c266bd1863d9a30f26e88e93c9a6d3382f79180c370612e6c8b571fd640bd5 +size 29828 diff --git a/screenshots/de/features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_2_de.png b/screenshots/de/features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_2_de.png index f246e38806..fb6e1215fc 100644 --- a/screenshots/de/features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_2_de.png +++ b/screenshots/de/features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba0700dea1c4dc744101290e093b205dc3f24b1dd0de8ac2ee14103d636b0d70 -size 29851 +oid sha256:22c266bd1863d9a30f26e88e93c9a6d3382f79180c370612e6c8b571fd640bd5 +size 29828 diff --git a/screenshots/de/features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_3_de.png b/screenshots/de/features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_3_de.png index 2922220aac..50432f0f26 100644 --- a/screenshots/de/features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_3_de.png +++ b/screenshots/de/features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c710500bd2320a6df97c5d78d6fcf337e3ddd21115a2acc81e1de1fa477121e3 -size 44689 +oid sha256:80be84e4c2961dd852bda489e835d8458244d1e13ac3e7e15712133cd120b4a2 +size 44681 diff --git a/screenshots/de/features.securebackup.impl.reset.root_ResetIdentityRootView_Day_0_de.png b/screenshots/de/features.securebackup.impl.reset.root_ResetIdentityRootView_Day_0_de.png index f97909d2f8..27dab886a1 100644 --- a/screenshots/de/features.securebackup.impl.reset.root_ResetIdentityRootView_Day_0_de.png +++ b/screenshots/de/features.securebackup.impl.reset.root_ResetIdentityRootView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7aaffb9298241bf42c8b31806e039f20ef585f8ff51466b39ccf2bd19f7723f2 -size 70384 +oid sha256:2de82320a66c4e314a5f0e949f38f2afddfd55c3d75469392ff08eaf8dd69cc0 +size 70404 diff --git a/screenshots/de/features.securebackup.impl.reset.root_ResetIdentityRootView_Day_1_de.png b/screenshots/de/features.securebackup.impl.reset.root_ResetIdentityRootView_Day_1_de.png index 45e32ffc99..57989727ce 100644 --- a/screenshots/de/features.securebackup.impl.reset.root_ResetIdentityRootView_Day_1_de.png +++ b/screenshots/de/features.securebackup.impl.reset.root_ResetIdentityRootView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1468fd52b8e3448bd1f8e0f3791c574565adddeda13ea8a13b2332e132522d6 -size 50128 +oid sha256:f8d6f12969d9c00a53122c7f39981d86f924c920e043af3c8eb633f10fb2d0e1 +size 50114 diff --git a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_0_de.png b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_0_de.png index e8832f5e11..00b17b50eb 100644 --- a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_0_de.png +++ b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d3d2644cf615ad50bc8e38dc58da5598e248827a27f73f47967a05a392b31e5 -size 43560 +oid sha256:c201c990f9d5aea35722ed281a59e26516fd18281ef0c607fd14ffebf48d7e55 +size 43567 diff --git a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_10_de.png b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_10_de.png index e8832f5e11..00b17b50eb 100644 --- a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_10_de.png +++ b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d3d2644cf615ad50bc8e38dc58da5598e248827a27f73f47967a05a392b31e5 -size 43560 +oid sha256:c201c990f9d5aea35722ed281a59e26516fd18281ef0c607fd14ffebf48d7e55 +size 43567 diff --git a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_11_de.png b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_11_de.png index f4b594d1fe..ccb9fe1548 100644 --- a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_11_de.png +++ b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5de424d81283d12e82c18a61ad3f3089181fecfaa0767e7bca93306e0b392c1d -size 44472 +oid sha256:f634d1f0e2da9be8e1752dd79231e0e57abb0bfc42a5765caacdcaea02b3d50c +size 44479 diff --git a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_12_de.png b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_12_de.png index f4b594d1fe..ccb9fe1548 100644 --- a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_12_de.png +++ b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_12_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5de424d81283d12e82c18a61ad3f3089181fecfaa0767e7bca93306e0b392c1d -size 44472 +oid sha256:f634d1f0e2da9be8e1752dd79231e0e57abb0bfc42a5765caacdcaea02b3d50c +size 44479 diff --git a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_13_de.png b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_13_de.png index b088028261..7ee66ef000 100644 --- a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_13_de.png +++ b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_13_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:16caff51af4ff119b2497d40a3ccf28d6d5c042a905c089cabe956a4c7905ab9 -size 71290 +oid sha256:fb798e91bb9a8194f5789b22c61330d8a5c0928e2810ec403acc7b13794320df +size 71293 diff --git a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_14_de.png b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_14_de.png index 24aa5c3831..9ea17a4acc 100644 --- a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_14_de.png +++ b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_14_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f32adae89e7356af6fa8396774184617a71e3ada252662ee4b993c29dc46f70d -size 67033 +oid sha256:d1d050f330f40bfabd14506b88c297328f819018134340f739a4397cf1b94f77 +size 67036 diff --git a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_15_de.png b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_15_de.png index 8617b520b3..12a9c57dce 100644 --- a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_15_de.png +++ b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_15_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23b489947d0f9f3ece1aa514d1e74d9a2c355177052b48ea02a7d518955cd5a5 -size 57075 +oid sha256:2719dc35eef9ef31ed4d2a6a40b1efe50b58601725136d263842afd8d3a92a38 +size 57082 diff --git a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_16_de.png b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_16_de.png index a3036d76b3..28809cee2b 100644 --- a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_16_de.png +++ b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_16_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:47fcb7163ca9930ef299fe028821ae10f4d25baf2e09e8ed49a14a39c47738c0 -size 69177 +oid sha256:cce69137e95273283e21121fb737720010abf466d4b1abc27f7daa3e1772b751 +size 69182 diff --git a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_17_de.png b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_17_de.png index 92c9ac1578..fe6636b662 100644 --- a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_17_de.png +++ b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_17_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c60f76b4095bfab8c28ac4889af6b020d4d82eb73e2153d404a1f1229fe9c50c -size 53093 +oid sha256:16428e15c36f2ed0767cc8500fbf9bfc3f07e84c73ebe43e8b64ec474ca0355d +size 53056 diff --git a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_1_de.png b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_1_de.png index f4b594d1fe..ccb9fe1548 100644 --- a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_1_de.png +++ b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5de424d81283d12e82c18a61ad3f3089181fecfaa0767e7bca93306e0b392c1d -size 44472 +oid sha256:f634d1f0e2da9be8e1752dd79231e0e57abb0bfc42a5765caacdcaea02b3d50c +size 44479 diff --git a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_2_de.png b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_2_de.png index 3fc9c08e60..acad524964 100644 --- a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_2_de.png +++ b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54a67ca1f96e212760451beca08da149f5dd13d75a4fb5084088e09efb94f332 -size 44832 +oid sha256:4ed5300a2ac30da2c5d378ac49d3388a345798791dac6361e67895c6139bbc97 +size 44839 diff --git a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_3_de.png b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_3_de.png index cab4f598f3..52ade5b6d9 100644 --- a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_3_de.png +++ b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee4b0794963906b731f4991b23eeca85b2f19fc90634ab4917cb59d4fa65d8ad -size 45555 +oid sha256:57297a3efae6260c4b7f46004591d24b5a4628201e35f35eb5582e466c570f8d +size 45562 diff --git a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_4_de.png b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_4_de.png index e8832f5e11..00b17b50eb 100644 --- a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_4_de.png +++ b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d3d2644cf615ad50bc8e38dc58da5598e248827a27f73f47967a05a392b31e5 -size 43560 +oid sha256:c201c990f9d5aea35722ed281a59e26516fd18281ef0c607fd14ffebf48d7e55 +size 43567 diff --git a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_5_de.png b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_5_de.png index f4b594d1fe..ccb9fe1548 100644 --- a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_5_de.png +++ b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5de424d81283d12e82c18a61ad3f3089181fecfaa0767e7bca93306e0b392c1d -size 44472 +oid sha256:f634d1f0e2da9be8e1752dd79231e0e57abb0bfc42a5765caacdcaea02b3d50c +size 44479 diff --git a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_6_de.png b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_6_de.png index f36902ec07..49a7385d22 100644 --- a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_6_de.png +++ b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:818f5f1d73b601d026f513387d76e67628142609412c0afa055d530be94db263 -size 42157 +oid sha256:fa413e4bf0664d1e15f1a9378b51900f470282a815f8df44643caeac91e6aad2 +size 42120 diff --git a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_7_de.png b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_7_de.png index f4b594d1fe..ccb9fe1548 100644 --- a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_7_de.png +++ b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5de424d81283d12e82c18a61ad3f3089181fecfaa0767e7bca93306e0b392c1d -size 44472 +oid sha256:f634d1f0e2da9be8e1752dd79231e0e57abb0bfc42a5765caacdcaea02b3d50c +size 44479 diff --git a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_8_de.png b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_8_de.png index f4b594d1fe..ccb9fe1548 100644 --- a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_8_de.png +++ b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5de424d81283d12e82c18a61ad3f3089181fecfaa0767e7bca93306e0b392c1d -size 44472 +oid sha256:f634d1f0e2da9be8e1752dd79231e0e57abb0bfc42a5765caacdcaea02b3d50c +size 44479 diff --git a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_9_de.png b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_9_de.png index f4b594d1fe..ccb9fe1548 100644 --- a/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_9_de.png +++ b/screenshots/de/features.securebackup.impl.root_SecureBackupRootView_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5de424d81283d12e82c18a61ad3f3089181fecfaa0767e7bca93306e0b392c1d -size 44472 +oid sha256:f634d1f0e2da9be8e1752dd79231e0e57abb0bfc42a5765caacdcaea02b3d50c +size 44479 diff --git a/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_10_de.png b/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_10_de.png index 7359ced4e5..12eacfd76a 100644 --- a/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_10_de.png +++ b/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:303a052955062e205dfb8eb9184e45623b3fdb2a2c7a5c07a951df5f10d6bfae -size 29837 +oid sha256:6c8e18b81a18e00aac7a00ee212634149246cfae4e85d4183ceee0c82b1d6a45 +size 29844 diff --git a/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_11_de.png b/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_11_de.png index 8f943194b8..1c35d6eed8 100644 --- a/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_11_de.png +++ b/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8afdae33c4e7d344eb018392bdae4d3d42f7373d07230c2368f26b4ac2d6e7d -size 29444 +oid sha256:cdf1f3bdc8e5ba7312271de2aab3bca9888bfd649e9cd9f120fd2147c98cb24f +size 29443 diff --git a/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_12_de.png b/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_12_de.png index 7359ced4e5..12eacfd76a 100644 --- a/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_12_de.png +++ b/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_12_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:303a052955062e205dfb8eb9184e45623b3fdb2a2c7a5c07a951df5f10d6bfae -size 29837 +oid sha256:6c8e18b81a18e00aac7a00ee212634149246cfae4e85d4183ceee0c82b1d6a45 +size 29844 diff --git a/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_13_de.png b/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_13_de.png index 1b3dfeeecd..f5fea2a9ef 100644 --- a/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_13_de.png +++ b/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_13_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f288fa2dad455d1975650a1a6eccca2f2749540e77b5b89c836d69782db06719 +oid sha256:1c8c707aca707c711b256d0acdfbcb3a30718b7915d4360c3153ad09ea159384 size 25694 diff --git a/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_14_de.png b/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_14_de.png index 4e7459f850..d5cbaebaeb 100644 --- a/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_14_de.png +++ b/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_14_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa96943056b0df8b39a6e759fbbdee9a74e191fe72db78dd1ab7cfa8c0bc5f0d -size 23406 +oid sha256:5671c6f3b693c3b62d7809cf4323ad7857fc60cd48f01fcfb820d05a33edbf47 +size 23412 diff --git a/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_8_de.png b/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_8_de.png index 62e3e51642..77a20ecf91 100644 --- a/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_8_de.png +++ b/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19eca70164d75bad98c937f9dd6672fa318d1509d09f761b3be78c509e91edcc -size 22005 +oid sha256:5a65cfccb5a24924986f03117f044b0df5df7038919ca09b5a13c78948ed386f +size 22002 diff --git a/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_9_de.png b/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_9_de.png index 023e49e1ad..0f66553b10 100644 --- a/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_9_de.png +++ b/screenshots/de/features.securebackup.impl.setup.views_RecoveryKeyView_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2c99a457047cab44c2c4b56d1e0c9a31950cc7ce5f904665dfaccfa79afc128 -size 21672 +oid sha256:0c92eec8dbddb38348f17074ceb4f3631c9aa2e2f590b508e815da39c1c16371 +size 21679 diff --git a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_de.png b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_de.png index 22a3dd4e1b..54c3c9e53c 100644 --- a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_de.png +++ b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01ba9ac649bf68d555d5d840428fee0266584b96ce5fe36c0f9e2e1d6d0c4de1 -size 51074 +oid sha256:ee6feecd5eb5ab8e4c84f1a684babc9fae3ca5fd45980170dd19d935c8741c2f +size 51102 diff --git a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_de.png b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_de.png index 48b168dc44..2739203618 100644 --- a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_de.png +++ b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fcd83326c3c7f7b43243e904197e88e21bb8f128bdfbdce722e4b7daf6c9a31c -size 47543 +oid sha256:9539949c15d87063a74b365ba792b8cd0b3a063ff65376fc11781f8e97d99b2c +size 47571 diff --git a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_de.png b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_de.png index 6796840a99..7d8aab76e5 100644 --- a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_de.png +++ b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5cbfd2e861308c906dbb5df7949d10be226b6f2a7eecccd6387a1d2902b235c7 -size 63562 +oid sha256:351d3b0d759db267544bcf3d87a4e2e76db4d0b03931601a8396c12b3c6648e4 +size 63576 diff --git a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_de.png b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_de.png index 6796840a99..7d8aab76e5 100644 --- a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_de.png +++ b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5cbfd2e861308c906dbb5df7949d10be226b6f2a7eecccd6387a1d2902b235c7 -size 63562 +oid sha256:351d3b0d759db267544bcf3d87a4e2e76db4d0b03931601a8396c12b3c6648e4 +size 63576 diff --git a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_de.png b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_de.png index d58496d0b3..581db0065d 100644 --- a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_de.png +++ b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:edc3adcd73285dbaf217485d9e296a1af1ef592d65baca1fcf6ed75700d17eb3 -size 51568 +oid sha256:93c61f5c06e5805590a09c51c5c1d9fdfd5757061ca68069d83d8b8ba71ea925 +size 51584 diff --git a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_5_de.png b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_5_de.png index 66bc4a6c25..6d6128da51 100644 --- a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_5_de.png +++ b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3d720a5a7bd70867d8096a06b70be72d612f86ad8cbcdfa5521a2c33cee0235 -size 40595 +oid sha256:c1e9c58cb2ef045ae08825f985f757999086327117feb23e233a530fdd87e9c3 +size 40575 diff --git a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_0_de.png b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_0_de.png index cfd34858d6..c60f6bd523 100644 --- a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_0_de.png +++ b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c97aeaff0cd23f76604dcb60ae08ab50a48ef8249e83592327b84f8aefab0a52 -size 63490 +oid sha256:a7daa689a1e1ccd76aa353e7d2ae14380c63e2da3a838fcc5689dd55fb47d5e6 +size 63525 diff --git a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_1_de.png b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_1_de.png index 557a86b152..71a520d9f8 100644 --- a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_1_de.png +++ b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b3faa64a17045745be5e724be01eaad17f3f73d01b9fd6eeafa04a3b55c983c -size 60079 +oid sha256:da3ea533d43fe4103ac7209b374f6bc558ab4e033b2400bc95528429f00654d0 +size 60114 diff --git a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_2_de.png b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_2_de.png index 6796840a99..7d8aab76e5 100644 --- a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_2_de.png +++ b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5cbfd2e861308c906dbb5df7949d10be226b6f2a7eecccd6387a1d2902b235c7 -size 63562 +oid sha256:351d3b0d759db267544bcf3d87a4e2e76db4d0b03931601a8396c12b3c6648e4 +size 63576 diff --git a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_3_de.png b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_3_de.png index 6796840a99..7d8aab76e5 100644 --- a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_3_de.png +++ b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5cbfd2e861308c906dbb5df7949d10be226b6f2a7eecccd6387a1d2902b235c7 -size 63562 +oid sha256:351d3b0d759db267544bcf3d87a4e2e76db4d0b03931601a8396c12b3c6648e4 +size 63576 diff --git a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_4_de.png b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_4_de.png index d58496d0b3..581db0065d 100644 --- a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_4_de.png +++ b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:edc3adcd73285dbaf217485d9e296a1af1ef592d65baca1fcf6ed75700d17eb3 -size 51568 +oid sha256:93c61f5c06e5805590a09c51c5c1d9fdfd5757061ca68069d83d8b8ba71ea925 +size 51584 diff --git a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_5_de.png b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_5_de.png index 94bb8f0af8..6dc61fc33f 100644 --- a/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_5_de.png +++ b/screenshots/de/features.securebackup.impl.setup_SecureBackupSetupView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e6498b8afc9307a9276a8b767dd5b7275748c19da68827d034111f2a4690e7e7 -size 42653 +oid sha256:9e5cb38ad86656e98f1c2ca10b3542f6c6994626346c094aad0aeb1f637ae9ce +size 42638 diff --git a/screenshots/de/features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_0_de.png b/screenshots/de/features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_0_de.png new file mode 100644 index 0000000000..92a9ecfc08 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4463c9d76c95a063e481cd12d8a5c8432b06acf4072927cbe1cf659ca22759e3 +size 26490 diff --git a/screenshots/de/features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_1_de.png b/screenshots/de/features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_1_de.png new file mode 100644 index 0000000000..1232360bfd --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:16187cb579ae21c620fc41fc70b3ae17136ea423d7793263c4b943ca4b3e54cb +size 31781 diff --git a/screenshots/de/features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_2_de.png b/screenshots/de/features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_2_de.png new file mode 100644 index 0000000000..6d9d44a999 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59c6624f279aaa202ae5e85488dfa28893c13f02d2e76b0fbe6586b35a55181d +size 32515 diff --git a/screenshots/de/features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_3_de.png b/screenshots/de/features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_3_de.png new file mode 100644 index 0000000000..7451aa805a --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:805d79eefc2a5cb1dd4fbb7294301ad785bb9cfe04c8c80c102c4f8d6f51fa4d +size 26384 diff --git a/screenshots/de/features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_4_de.png b/screenshots/de/features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_4_de.png new file mode 100644 index 0000000000..ab750ae07a --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4725f3d6a5c06f692fec640eddfd5f6ecd1cb3fb38c9f137ffbdd7df3cadc27 +size 28437 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_0_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_0_de.png new file mode 100644 index 0000000000..bcdf552747 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31e3ddc060f3c87cd761dd58ff73ef00b0af8e2fba6bf06e27f0396e52a04df3 +size 54661 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_10_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_10_de.png new file mode 100644 index 0000000000..d8e327cec9 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_10_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7cc7260e1f01727bc96317f278158a36cdb0a6ee544937e1f7b2b2e5aee9b71f +size 51358 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_11_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_11_de.png new file mode 100644 index 0000000000..f584c028e1 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_11_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:407a338096b8e758eb4663016fc1448fffc7756b5055c5be015782d4f9d34de7 +size 51832 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_12_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_12_de.png new file mode 100644 index 0000000000..ff8e29ea59 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_12_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:044869f6f523e6ab43de53e7ff13a8c1771a929030f60a8191329f385be67835 +size 24752 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_13_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_13_de.png new file mode 100644 index 0000000000..eafb1f2805 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_13_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9027ed5e27a1d640ab50b4c544d622c718d3bef6df477bf66b99ee605ccec7c +size 51770 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_14_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_14_de.png new file mode 100644 index 0000000000..f584c028e1 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_14_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:407a338096b8e758eb4663016fc1448fffc7756b5055c5be015782d4f9d34de7 +size 51832 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_15_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_15_de.png new file mode 100644 index 0000000000..a7c70bf14d --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_15_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e771cd9a182dab3615661aebdeba6069cbf04218f60d709a529693e8ad6005e0 +size 52416 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_16_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_16_de.png new file mode 100644 index 0000000000..919ee0cbcd --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_16_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6df150ce5cdb2e3233444c122b6bbd0b71fcfb8819eccd15f7ddbafd7e2d4d6 +size 47665 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_17_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_17_de.png new file mode 100644 index 0000000000..bab63e3455 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_17_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f53320f4b69908a32f3723a1d098eac5c993574e877dd1ce5faac9028ad4fb4e +size 46743 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_18_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_18_de.png new file mode 100644 index 0000000000..a62aca41d7 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_18_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12d639b33a6424d0807bff2545b874b16abb2c0147662d04f21df28d915fe2e4 +size 49225 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_19_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_19_de.png new file mode 100644 index 0000000000..0cfbaa9762 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_19_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55626ae74436af585b326f621d6d5d75729d180c4d653882f81e1484dac753fe +size 52976 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_1_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_1_de.png new file mode 100644 index 0000000000..f7ed121780 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2cbad28899d0c9f55f4abcd04e5ea3fcd896d7d7e1649ce52eb9b23bbe7b6e91 +size 65600 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_2_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_2_de.png new file mode 100644 index 0000000000..a174686a2d --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f561351b6dbd13b6cd2f55f8df9f31c84453594acf511cdde27855639ba4ea7c +size 65148 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_3_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_3_de.png new file mode 100644 index 0000000000..44e0015abe --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d20dafcb3552d24835bbeec5a288cad29015ceaaee123d64fa32123a51c3cb4 +size 65727 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_4_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_4_de.png new file mode 100644 index 0000000000..04a8ae33bd --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd49f1b0f3dae2a57e4a63b54a2cb98dffab50ed3b2cb9d46f951eb1ea2f99c8 +size 51549 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_5_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_5_de.png new file mode 100644 index 0000000000..9a5f939b49 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_5_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92888d4d51a087f628d2c0604d0f02c3a58e2eca5c1e41d1dd01884afb1c1e6a +size 65549 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_6_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_6_de.png new file mode 100644 index 0000000000..3a76a16c3e --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_6_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1998f0764fd6706169015fecbe7958f5ea88ccf6718c6fcc31b50259213eda69 +size 65612 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_7_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_7_de.png new file mode 100644 index 0000000000..b307d30172 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_7_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dce718324779855609745640fccecda621d121e61186915c626b4f8cd4e8693e +size 62663 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_8_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_8_de.png new file mode 100644 index 0000000000..6432c76e48 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_8_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ffb5c13b625568456542d7405e9f8f05ebe986de32587c68fbcd7c356cd30cb3 +size 27683 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_9_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_9_de.png new file mode 100644 index 0000000000..5d8f1260f7 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_9_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57ba25b77977622a483522f5e360fb9763e4c089a8576d3b51b611a463e81d7c +size 51821 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_0_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_0_de.png new file mode 100644 index 0000000000..eb68edb577 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:851affa2b63b0c70a282c182bf1ce615283c6d0ed732ee3b3102d3dcb862349c +size 57340 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_10_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_10_de.png new file mode 100644 index 0000000000..510202c600 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_10_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8dc0ed7717de76041c6a5e9c32c3246039a8881ced121276b58b3012bbfb4096 +size 52718 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_11_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_11_de.png new file mode 100644 index 0000000000..fbbf3768e6 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_11_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4919373598bd77b7aa9e3aebad6abf4f8ddcf0123894507d76ce63157d347ea7 +size 53335 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_12_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_12_de.png new file mode 100644 index 0000000000..e0478af982 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_12_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b4a7e16a3c70933cc7f4ef68636df5d1669789500690f17a5a25f122f9f50b2 +size 25412 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_13_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_13_de.png new file mode 100644 index 0000000000..98d9beba84 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_13_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65020e3887540431951a3e99ce31e19dc401b4bbc232b16ded6e59d51f1712c9 +size 53206 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_14_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_14_de.png new file mode 100644 index 0000000000..fbbf3768e6 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_14_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4919373598bd77b7aa9e3aebad6abf4f8ddcf0123894507d76ce63157d347ea7 +size 53335 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_15_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_15_de.png new file mode 100644 index 0000000000..579756c731 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_15_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73fa1e4a25773720a0e9999b293488017c854576f40cc738fb3b67b59f2b1f05 +size 54167 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_16_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_16_de.png new file mode 100644 index 0000000000..386a05f453 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_16_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:711800e228ac74d8971a383ecd85f560d050000f886d6107aa7ea0c9d6099d95 +size 49348 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_17_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_17_de.png new file mode 100644 index 0000000000..ac8d242b19 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_17_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4624bd99900eb3d30256aa81dbe6c48f017480ebc4c97d3fbf47529ffd62e899 +size 49078 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_18_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_18_de.png new file mode 100644 index 0000000000..f70a5bf36d --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_18_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:612ffe06d38570e32215b9f117f979382832a9aed3fddcde38b0a461bf1b653d +size 51684 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_19_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_19_de.png new file mode 100644 index 0000000000..867cb18108 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_19_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11ebd0e210579c60b07136685c82f3d73648a3742344d4d15a5ed3df9b48ced9 +size 55476 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_1_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_1_de.png new file mode 100644 index 0000000000..d1ce6902de --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90db4ee471ec887f00a9e580ef839a4611216107ec615da90330b7d6be276b77 +size 68316 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_2_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_2_de.png new file mode 100644 index 0000000000..1d2eff636f --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9488bea7d8ea64ac2d2987dcf69b4a51bfb6b81e33b1035195860cc90ae9f291 +size 67707 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_3_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_3_de.png new file mode 100644 index 0000000000..6f1dca9c23 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbf7bda1bff05828fe71045a012b8bcc1f4c047256d8aea6f8f8579e08295dea +size 68546 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_4_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_4_de.png new file mode 100644 index 0000000000..6ec398b86b --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00b2576dacba4388bb8259a7c6fbacbe713c805695efc4ea6e98c50f0cd28196 +size 53621 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_5_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_5_de.png new file mode 100644 index 0000000000..3f1bf5f183 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_5_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d32e9542eb673f6e4f0f05ad017615f0c3274683e48f3ec13277e2d98d4db0f +size 68200 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_6_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_6_de.png new file mode 100644 index 0000000000..1b18135ef5 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_6_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4dd6427f42936ef21702492e5e96a7abaa6873edde04169603ed2124d76f4984 +size 68323 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_7_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_7_de.png new file mode 100644 index 0000000000..e04844391b --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_7_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:88d3cb4770828935c271cc10fc436c85f0af19bf77a5f58a6abd6fde1af367ec +size 65168 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_8_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_8_de.png new file mode 100644 index 0000000000..1e24950be3 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_8_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99ae8ce6973aeacb8d1054722038b9840365b239569f6dbee5c2f234e84c14d3 +size 28626 diff --git a/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_9_de.png b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_9_de.png new file mode 100644 index 0000000000..fa87fbe737 --- /dev/null +++ b/screenshots/de/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_9_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0c6701f9c4faa4495cd92e7414821f39f9adc3d3f755d21302a5c66cab6f5e3 +size 53331 diff --git a/screenshots/de/features.signedout.impl_SignedOutView_Day_0_de.png b/screenshots/de/features.signedout.impl_SignedOutView_Day_0_de.png index 86d209de85..698a4fdaf2 100644 --- a/screenshots/de/features.signedout.impl_SignedOutView_Day_0_de.png +++ b/screenshots/de/features.signedout.impl_SignedOutView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fcd03c7d8135700d4253d498fbd6b72a6ddde58aefef8bff60b162e44acfb530 -size 61373 +oid sha256:1cda94993c1238192fd0887052f1e66952a2bbad4faca1a46b7ff14e29e365fb +size 61365 diff --git a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_0_de.png b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_0_de.png index fd00f875fe..95ee3e178f 100644 --- a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_0_de.png +++ b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe4e5c4f1defe3b99adcf563eb389c943f93d5417f2344eff2312f2b809991c7 -size 15572 +oid sha256:4948d0fbeea2e410b1a289ab92ecbfa792ae5f0980c860590e53860203bc2a69 +size 15601 diff --git a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_1_de.png b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_1_de.png index a56d30a522..72bb1a9ec8 100644 --- a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_1_de.png +++ b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58812339f1ac01175b64421d0af685330c4bd95186eff5213194e573deeaa443 -size 18292 +oid sha256:eb862790afe9b86f7c100e4f24dc970cadd1e5bdd38020e97b7579984216069d +size 18316 diff --git a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_2_de.png b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_2_de.png index 8a3fdd24f4..c3ae616678 100644 --- a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_2_de.png +++ b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:565857c30ef349022e9cddfcd892a481858ba83bc3dacf884ae37be964fe327b -size 46496 +oid sha256:220225ed71bf4be469bffdec3e9569a63bb22d43b9ba0e80001c3605c9ad8136 +size 46516 diff --git a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_3_de.png b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_3_de.png index 94f10f54ac..dd0e2b67bc 100644 --- a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_3_de.png +++ b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5424035b81a1cf082a6a56450bfc444810ffb7d0af87d0803e99151c9cdb3cb4 -size 46000 +oid sha256:9fa10c7c7117706bdd362ccd8382f94ae7b2707b298ee5fd424f50f2c9102cd1 +size 46027 diff --git a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_4_de.png b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_4_de.png index 7f36068a32..74b7970d8e 100644 --- a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_4_de.png +++ b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a39c417a6d8f4d8303d9b2d5c229ffbc55921bb706161816c5f774b1aa5739f -size 36889 +oid sha256:3c39b35a474572085fce25aa0b7e2dbf1ac4b5a50d7cac7f5652eed45156c425 +size 36919 diff --git a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_5_de.png b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_5_de.png index a234c6588b..5938df709f 100644 --- a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_5_de.png +++ b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fba6f766f3c7cc257d4e199389f03290e63dd2caa040329648969caabfbb5869 -size 43746 +oid sha256:3f92161016b5a3f6f57b5f896bef4e370fff2acb6ce4e5c5bb466dc1eb3a8bd0 +size 43774 diff --git a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_6_de.png b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_6_de.png index f942304d6f..dc0b528672 100644 --- a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_6_de.png +++ b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:def46d1907ba93766cf6b4d465840820221800d2d4e34a14b757697d73b17623 -size 44009 +oid sha256:fc64b1af4a6fbef30bb36caa26a349885a3fdce232dbd7c9f00233e763dd352c +size 43997 diff --git a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_7_de.png b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_7_de.png index bc853d286e..d257ee7942 100644 --- a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_7_de.png +++ b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ec8a73647a8cadd203006313fb3baa79e27f9e8faaef991d7e37c0faccc67281 -size 42259 +oid sha256:ce48faa7c7698832c8ef1d6e6aa1563981d2f0df9f49b01b8c7b64ae7477a193 +size 42250 diff --git a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_8_de.png b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_8_de.png index 01c57b8085..e7d0e2ba5f 100644 --- a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_8_de.png +++ b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e86c1032701041744144e88185238129ed824b2e4cb0c4992d6cceae2322442 -size 20060 +oid sha256:68f100096a3f32591792e7816ce116e1e3bdb3b1eca7df6ab3a5473a29775e67 +size 20091 diff --git a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_9_de.png b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_9_de.png index 99d948fd8b..6b2e354a36 100644 --- a/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_9_de.png +++ b/screenshots/de/features.space.impl.leave_LeaveSpaceView_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:79eceaf8ae38ae53f4e2736f64628b36d5f4201d42206cfe48b547334800165f -size 30296 +oid sha256:2fb101aadb226cf66041f2abda6edc1fb9877765ad9d88607135503bdc2037dd +size 37509 diff --git a/screenshots/de/features.space.impl.root_SpaceView_Day_0_de.png b/screenshots/de/features.space.impl.root_SpaceView_Day_0_de.png index ee8e0d3535..66bcb88c50 100644 --- a/screenshots/de/features.space.impl.root_SpaceView_Day_0_de.png +++ b/screenshots/de/features.space.impl.root_SpaceView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:776ffc78757ec0ed2863407e91d6c2071dc40e945ef9448bc405a35abfd1c925 -size 19403 +oid sha256:6efe4f355c6372ad031136f6f2de51c3c6132e18ddc70f558c16ee921befefa0 +size 34819 diff --git a/screenshots/de/features.space.impl.root_SpaceView_Day_1_de.png b/screenshots/de/features.space.impl.root_SpaceView_Day_1_de.png index 8ca4805dc7..71f98c9def 100644 --- a/screenshots/de/features.space.impl.root_SpaceView_Day_1_de.png +++ b/screenshots/de/features.space.impl.root_SpaceView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b63f17d6bf0d3b18c0e405a4c14a4a68a8351c192fa788fc5135d4fbe8269d85 -size 20383 +oid sha256:3af3d6c1c57c37f88f37624614a1bca7e9fcae0042482d293f2eef69a7350713 +size 35864 diff --git a/screenshots/de/features.space.impl.root_SpaceView_Day_2_de.png b/screenshots/de/features.space.impl.root_SpaceView_Day_2_de.png index 411c33cc5c..68e0fdf009 100644 --- a/screenshots/de/features.space.impl.root_SpaceView_Day_2_de.png +++ b/screenshots/de/features.space.impl.root_SpaceView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba5eb40aa5ba4e2ebe6e68ad42d1b27080c3680a86a613d538344c2b8dadc6f6 -size 20710 +oid sha256:65fab252bd1c0920692404f0067323bfb33df9124ba5dc3ad5be5008295fbc0f +size 36158 diff --git a/screenshots/de/features.space.impl.root_SpaceView_Day_3_de.png b/screenshots/de/features.space.impl.root_SpaceView_Day_3_de.png index 0cca59b427..1c22e938cb 100644 --- a/screenshots/de/features.space.impl.root_SpaceView_Day_3_de.png +++ b/screenshots/de/features.space.impl.root_SpaceView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:95ef629af8452fe2576011db8c753b23d9b45b51d2a33bfa8a17c861474a99d2 -size 20869 +oid sha256:ecb9ad959737f80864480f7fa3cce8e31a1b0c2aa84d5b49ff8b119b1aa5bc1e +size 65190 diff --git a/screenshots/de/features.space.impl.root_SpaceView_Day_4_de.png b/screenshots/de/features.space.impl.root_SpaceView_Day_4_de.png index 6b09249a34..52dd6c93a0 100644 --- a/screenshots/de/features.space.impl.root_SpaceView_Day_4_de.png +++ b/screenshots/de/features.space.impl.root_SpaceView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7fa009eca8f6308faeca75f3abfdae4e1a2cb6835e4f21ce08eec3464f8be187 -size 55502 +oid sha256:fcdf0489810fe2035f072cdb5cc5e3c78cb3ee4e5d220148ed2721dbf324d7e6 +size 65805 diff --git a/screenshots/de/features.space.impl.root_SpaceView_Day_5_de.png b/screenshots/de/features.space.impl.root_SpaceView_Day_5_de.png index 1899ad0987..ff45615538 100644 --- a/screenshots/de/features.space.impl.root_SpaceView_Day_5_de.png +++ b/screenshots/de/features.space.impl.root_SpaceView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b6c0d140d5bbf6d4478403b72bb379a0aedce3a71e212b38a65ff12053ac6030 -size 54644 +oid sha256:7a9166782d34d5388b8cdcdf270f8ac854bad44e24a30f35661af2a93cdbeb4a +size 60269 diff --git a/screenshots/de/features.space.impl.settings_SpaceSettingsView_Day_0_de.png b/screenshots/de/features.space.impl.settings_SpaceSettingsView_Day_0_de.png new file mode 100644 index 0000000000..3247b48fde --- /dev/null +++ b/screenshots/de/features.space.impl.settings_SpaceSettingsView_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a032a5858b9d60a2da5007b56c9076933f69b1692f01b333f8288dc4f630c3d +size 23115 diff --git a/screenshots/de/features.space.impl.settings_SpaceSettingsView_Day_1_de.png b/screenshots/de/features.space.impl.settings_SpaceSettingsView_Day_1_de.png new file mode 100644 index 0000000000..6c2b4de79a --- /dev/null +++ b/screenshots/de/features.space.impl.settings_SpaceSettingsView_Day_1_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a53b1c137ad66aebdb9aea7b77535c632b1d78a828899249cf0298e08c5e77b +size 19533 diff --git a/screenshots/de/features.space.impl.settings_SpaceSettingsView_Day_2_de.png b/screenshots/de/features.space.impl.settings_SpaceSettingsView_Day_2_de.png new file mode 100644 index 0000000000..01640f6aed --- /dev/null +++ b/screenshots/de/features.space.impl.settings_SpaceSettingsView_Day_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e183f520739e31db29a8724dd5c916e6d3c60a174b7d17affaec48d897f7c3d6 +size 27943 diff --git a/screenshots/de/features.space.impl.settings_SpaceSettingsView_Day_3_de.png b/screenshots/de/features.space.impl.settings_SpaceSettingsView_Day_3_de.png new file mode 100644 index 0000000000..16e81d3cfe --- /dev/null +++ b/screenshots/de/features.space.impl.settings_SpaceSettingsView_Day_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8beaa101ec1ca725d309725bed0f6da3b58baf41fe99e3959b2acb2ad0d6c07a +size 28196 diff --git a/screenshots/de/features.startchat.impl.components_SearchMultipleUsersResultItem_de.png b/screenshots/de/features.startchat.impl.components_SearchMultipleUsersResultItem_de.png index c1cf8833e4..28418751a5 100644 --- a/screenshots/de/features.startchat.impl.components_SearchMultipleUsersResultItem_de.png +++ b/screenshots/de/features.startchat.impl.components_SearchMultipleUsersResultItem_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3cc5e887a1de69fc216a215680104098f1a689e5db992c3e65e5b66b0891d1cc -size 101103 +oid sha256:ecea6fc10d3d405e26f5ed488bc858b78e7dab1d6cafb76cc7a2feed3d3eddc6 +size 96954 diff --git a/screenshots/de/features.startchat.impl.components_SearchSingleUserResultItem_de.png b/screenshots/de/features.startchat.impl.components_SearchSingleUserResultItem_de.png index 6ba2bd1cc4..0bae881bb1 100644 --- a/screenshots/de/features.startchat.impl.components_SearchSingleUserResultItem_de.png +++ b/screenshots/de/features.startchat.impl.components_SearchSingleUserResultItem_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:108b4c1abb1f143171e35aebd055da2fd52a95e1df8fc8dab29a797da7288beb -size 51250 +oid sha256:feab59ca75900214799f9a81f50a96a5622224bacf6c236857d7c7ae267108e8 +size 52398 diff --git a/screenshots/de/features.startchat.impl.components_UserListView_Day_0_de.png b/screenshots/de/features.startchat.impl.components_UserListView_Day_0_de.png index 4484ebdad3..20761a5b3e 100644 --- a/screenshots/de/features.startchat.impl.components_UserListView_Day_0_de.png +++ b/screenshots/de/features.startchat.impl.components_UserListView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9401507c454cdb4babcc0bee4faa09f7199a64c5af349ce15a9df82956e10642 -size 9261 +oid sha256:03ef449ecfe5699f1135a83e128d6efb93efc53a84036ff364c6ea489ef9a781 +size 9269 diff --git a/screenshots/de/features.startchat.impl.components_UserListView_Day_1_de.png b/screenshots/de/features.startchat.impl.components_UserListView_Day_1_de.png index aaa5964462..1ff42f7afe 100644 --- a/screenshots/de/features.startchat.impl.components_UserListView_Day_1_de.png +++ b/screenshots/de/features.startchat.impl.components_UserListView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac691a431d544e0e4c33c62b51c86f26efb9088a00a05cb9d3151256e2290eed -size 21823 +oid sha256:058ac0f03151792e2a55090a4f170e5332e94339c56fbf029b05b6050c155549 +size 21841 diff --git a/screenshots/de/features.startchat.impl.components_UserListView_Day_2_de.png b/screenshots/de/features.startchat.impl.components_UserListView_Day_2_de.png index 6a4ec71b9a..05a41f4fd0 100644 --- a/screenshots/de/features.startchat.impl.components_UserListView_Day_2_de.png +++ b/screenshots/de/features.startchat.impl.components_UserListView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2297fbeb63dbbf5281266f90015e047528e3287c923e2207af061458fd12897e -size 7929 +oid sha256:f29343d52eb4641591c6e1fba1d889e5ff5d39839708cf79697fc1745ed90c67 +size 8077 diff --git a/screenshots/de/features.startchat.impl.components_UserListView_Day_7_de.png b/screenshots/de/features.startchat.impl.components_UserListView_Day_7_de.png index 8397efd92e..80bfc28b09 100644 --- a/screenshots/de/features.startchat.impl.components_UserListView_Day_7_de.png +++ b/screenshots/de/features.startchat.impl.components_UserListView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:31de17fb1f44c06ffd9ce5d7e59c91b7da986720f90074802b1b566b55859186 -size 12303 +oid sha256:dc767435f491d1700a220497c0cc6d73da3d823e9f042bb6e9c65eaa7fcc018c +size 12375 diff --git a/screenshots/de/features.startchat.impl.components_UserListView_Day_9_de.png b/screenshots/de/features.startchat.impl.components_UserListView_Day_9_de.png index 34ff3e91f3..1f2c501d1a 100644 --- a/screenshots/de/features.startchat.impl.components_UserListView_Day_9_de.png +++ b/screenshots/de/features.startchat.impl.components_UserListView_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fcd3d8e40e5d9f23c941efa17c37d0473df47d7924d2f51c94022a6c08f559a8 -size 37181 +oid sha256:59d6fcb404f29561e3e56ec4a76c07b8d88d813c790ce4badb54699415835a1d +size 38127 diff --git a/screenshots/de/features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_2_de.png b/screenshots/de/features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_2_de.png index ee46fd3751..dbdb084017 100644 --- a/screenshots/de/features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_2_de.png +++ b/screenshots/de/features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09ab5d26a3b18d9bdd503cdd62d7fd767088e286c5f72479fb422e24e26f4824 -size 18520 +oid sha256:b53363dd62f0a851f8c5eb0d32b6a553313ec988c56e5378d0fe5085dc5f4008 +size 18509 diff --git a/screenshots/de/features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_4_de.png b/screenshots/de/features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_4_de.png index 0ac0915508..3f6f4e2d8d 100644 --- a/screenshots/de/features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_4_de.png +++ b/screenshots/de/features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ad688fd9d4fd288022ec67b97fc1a73ccc769f7f8e5ef8622726543cf460ac82 -size 21516 +oid sha256:5ddb795a473f650502735a6831ff1ea849e5d97fb82e0f347abe63bd42acb886 +size 21513 diff --git a/screenshots/de/features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_5_de.png b/screenshots/de/features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_5_de.png index e785272441..2ea58a5b0d 100644 --- a/screenshots/de/features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_5_de.png +++ b/screenshots/de/features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:25aa162ebc1ac9bb3cf21e07414a0d405e7043b1f5acae646e1e57c9a63bd4d9 -size 21787 +oid sha256:da6faed3187b7a33f1955a6c0165838eb290d51e8e8b9acdf148d9c6f9055603 +size 21778 diff --git a/screenshots/de/features.startchat.impl.root_StartChatView_Day_0_de.png b/screenshots/de/features.startchat.impl.root_StartChatView_Day_0_de.png index 41c7fc6d86..bdc1faebb7 100644 --- a/screenshots/de/features.startchat.impl.root_StartChatView_Day_0_de.png +++ b/screenshots/de/features.startchat.impl.root_StartChatView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:50fe94aba6e76e638cfa9b5ffb574c46ff1757a0547a5c8ead0b9179c457792b -size 28100 +oid sha256:771be782de8b73b0bdb9ac6208102f2f773cee37e98fa614334c6ad9eb7f1299 +size 28118 diff --git a/screenshots/de/features.startchat.impl.root_StartChatView_Day_1_de.png b/screenshots/de/features.startchat.impl.root_StartChatView_Day_1_de.png index cfab50e88e..eddbdd659b 100644 --- a/screenshots/de/features.startchat.impl.root_StartChatView_Day_1_de.png +++ b/screenshots/de/features.startchat.impl.root_StartChatView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b9c75cdedd4c36873fcd9c7666e17915d2167b3471777201e4e6bf20dc5a0010 -size 21031 +oid sha256:a535b503bd51307d37e81071e90e993b9197beddb771e6164525f5d4ea30421a +size 21112 diff --git a/screenshots/de/features.startchat.impl.root_StartChatView_Day_2_de.png b/screenshots/de/features.startchat.impl.root_StartChatView_Day_2_de.png index 60aa2ae9c2..a970242e06 100644 --- a/screenshots/de/features.startchat.impl.root_StartChatView_Day_2_de.png +++ b/screenshots/de/features.startchat.impl.root_StartChatView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dbda304432217316c4cc028d98e6394619c9279a261a004466bd5ddf640f994f -size 31853 +oid sha256:e7698997e24292a0faf338ccdd386c95c475f7d0bc835e94a82703fcf257bab4 +size 31951 diff --git a/screenshots/de/features.startchat.impl.root_StartChatView_Day_3_de.png b/screenshots/de/features.startchat.impl.root_StartChatView_Day_3_de.png index a9d8d8ef9b..3e70e4d220 100644 --- a/screenshots/de/features.startchat.impl.root_StartChatView_Day_3_de.png +++ b/screenshots/de/features.startchat.impl.root_StartChatView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b9f41e1cd105f2c7b96ec1b3dc68008912442378075a93126d8b83e9c153dcb -size 55195 +oid sha256:16da63819524ef1e4535776caaa9e073731fe79f85ba798b57d8552999cc8789 +size 52219 diff --git a/screenshots/de/features.startchat.impl.root_StartChatView_Day_4_de.png b/screenshots/de/features.startchat.impl.root_StartChatView_Day_4_de.png index 84083f34cf..a0e0627c21 100644 --- a/screenshots/de/features.startchat.impl.root_StartChatView_Day_4_de.png +++ b/screenshots/de/features.startchat.impl.root_StartChatView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:edf88808f904a3cffd4c53e2d374ab3be9af91002b450de40d0e76b3f93d837c -size 45689 +oid sha256:f3a846c1d350efed3709f806d07983cf26fe9b6de78e4f3bce3df55002c2eb89 +size 45654 diff --git a/screenshots/de/features.startchat.impl.root_StartChatView_Day_5_de.png b/screenshots/de/features.startchat.impl.root_StartChatView_Day_5_de.png index ae5b3d5707..24e0ba7b39 100644 --- a/screenshots/de/features.startchat.impl.root_StartChatView_Day_5_de.png +++ b/screenshots/de/features.startchat.impl.root_StartChatView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6550e9568bf9360bdb33507122ac42220d4202cc691b01ed5f76ebe20beea12 +oid sha256:d81b395d52397f11e919d4f1de9cf64b4adb12a5c060c91ddbc91cce4bc71e2c size 32104 diff --git a/screenshots/de/features.userprofile.shared_UserProfileHeaderSection_Day_0_de.png b/screenshots/de/features.userprofile.shared_UserProfileHeaderSection_Day_0_de.png index 51436e8bbc..72323a22e6 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileHeaderSection_Day_0_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileHeaderSection_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17589eb4a584617876c67f4dc7c5009ebf119b0bcbc3c117fa04f8e5764ab71e -size 16043 +oid sha256:cdb2f46dd809a4d8e28be46dcd84fed0cae4c734a5d4b65b0dd8a00849fd9df0 +size 16091 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_0_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_0_de.png index 1b0c3f67bb..0802008049 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_0_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b193290d70ffa20cf5d4faac24d056a2d10e416d117ca29fbc007a5b1ad972e -size 25349 +oid sha256:7ef5c9662cd8e2d1593d976d84301385058ab2db89184b921c960304241dd7aa +size 25339 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_1_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_1_de.png index 4f743b5b98..71f6a43819 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_1_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fea501e631c2f1b84c1e8c61ad5267544f68e3c060a0fba575de8762fd7c8f8f -size 23281 +oid sha256:3d777d22a954a1661eb5ada2ef477f0efd2bab4753217ea6d279102344737536 +size 23287 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_2_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_2_de.png index 5783ca505d..0dd7f82cde 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_2_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da774c8c49bd4eaf0266a1e14fc612385219d5c261dd8870d1738b0d2d6d6a53 -size 25152 +oid sha256:71063d31e200aa93ae2c966c7291d61bc325f1dc658afcf96150ccc7ad8e3322 +size 25238 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_3_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_3_de.png index eb117a1f8b..26c6df8f58 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_3_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2fb468eee0edfe56d60c55532068ca3b538dee026dfde4fcf13ec7adef383070 -size 43518 +oid sha256:3a8dce781223c4b40a9c1757d29a368790545f790de0da65f2fa3002d629b9d7 +size 43489 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_4_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_4_de.png index 35577c491e..d9628e0708 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_4_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3cf73fa2cf91024cd576ccea663672257d5f9336b8ec41867e7bec87c96895cf -size 37141 +oid sha256:68e68757338ec82ecbd630e800e76579c8cec60242a626a113c27e4231a02a76 +size 37117 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_5_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_5_de.png index 6d117cdcbc..cab5fb1981 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_5_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e13a9114fdb169a8e84a2ca016ff89a107b563867b23da61e3b095a4f49b9047 -size 23266 +oid sha256:5c84058d388a98b1efad5633f621d1c18cbaa49a2f6d9dfec87b144be8950986 +size 23268 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_6_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_6_de.png index 0d726b86c0..7980de17eb 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_6_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:61a1d5027cce6975aacec3e0577b985180203ddeca950a8d9c4149138ece8a38 -size 25159 +oid sha256:f5691acea1598535fdaa00b0ae7fa1f8467597c70e96d4e9fe7c325d538e1f16 +size 25118 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_7_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_7_de.png index 40f3a93772..513dc09244 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_7_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3812e73653f9c5f20b6a6ebb7710095567a7024e3a2ed7af25dd313994568df -size 26456 +oid sha256:06b80ec5d10d83620bb3399a89c2f87682e223f46a276eaf57113fc600eb7188 +size 26450 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_8_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_8_de.png index f0650f7dd6..d02c6abd8a 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_8_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c93e7f3b1a7e00fe70d42d9325245e04557fbd5be6752bda30ed0d845c8a7583 -size 37238 +oid sha256:7d1e09a242bdb428a1a9719e46f58a41cd3ebed75e1a368c4348093c6951f515 +size 37237 diff --git a/screenshots/de/features.userprofile.shared_UserProfileView_Day_9_de.png b/screenshots/de/features.userprofile.shared_UserProfileView_Day_9_de.png index d726b2afae..97793bc9d0 100644 --- a/screenshots/de/features.userprofile.shared_UserProfileView_Day_9_de.png +++ b/screenshots/de/features.userprofile.shared_UserProfileView_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11820e6256d97c57f26f39b904618ee02ccbaf5fb8d1eb8c319ffb1116c8ce67 -size 34232 +oid sha256:d1ec66a16e6e4ce8af15f52831d2eee19122adca60496054738e33f09d3eab0a +size 34248 diff --git a/screenshots/de/features.verifysession.impl.incoming.ui_SessionDetailsView_Day_0_de.png b/screenshots/de/features.verifysession.impl.incoming.ui_SessionDetailsView_Day_0_de.png index 9741448fa7..e6a578c2cc 100644 --- a/screenshots/de/features.verifysession.impl.incoming.ui_SessionDetailsView_Day_0_de.png +++ b/screenshots/de/features.verifysession.impl.incoming.ui_SessionDetailsView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6d4b8d2bb20ecf61d7f288b25b542d3441335c765d47d21282a9464a591da12b -size 25133 +oid sha256:8603bccb22211bc691788e6b0a4b192182d0be431378f26981220807481a4b9b +size 25137 diff --git a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_0_de.png b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_0_de.png index f5576fd8a3..f1620e0b31 100644 --- a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_0_de.png +++ b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bcc6e14373fd9da583310dbbc759d0286a3b8ae36ddb0284709e3b937eb82d45 -size 48300 +oid sha256:e9a4da42e5fb1bdd969d83c99f39bdd160fd277124499420b5f39f5e081a9f0c +size 48298 diff --git a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_10_de.png b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_10_de.png index 88ce06753f..1d5e374608 100644 --- a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_10_de.png +++ b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:684222c49ca9fc06a9e63fcd46736b4c68b2f23f16e77228f78349f926514e6c -size 28094 +oid sha256:2afa86e0dc92132e23429bebe2d170977944012e3690d341e96c876e5ee8715a +size 28087 diff --git a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_11_de.png b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_11_de.png index f2f0404aad..73b5c74c39 100644 --- a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_11_de.png +++ b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8efa1d431472915948e96b97a6a1b77997359c8b7a2a11ca038dc25d36d7cdc4 -size 29436 +oid sha256:dad66c15257d320ba504995128924c88e7f121e0924ba66a5b789cd98819118b +size 29432 diff --git a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_12_de.png b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_12_de.png index 5df6ba2298..95b75812c3 100644 --- a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_12_de.png +++ b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_12_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:788f6e5a02171e885a321df9f523c9c0c87c86a183955936bfa1004027d1893a -size 32069 +oid sha256:aa8c90bc239d9e5b47cb6096efe3e976566fa9f0ee45dc1613db93c307b1e0dd +size 32088 diff --git a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_13_de.png b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_13_de.png index 5df6ba2298..95b75812c3 100644 --- a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_13_de.png +++ b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_13_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:788f6e5a02171e885a321df9f523c9c0c87c86a183955936bfa1004027d1893a -size 32069 +oid sha256:aa8c90bc239d9e5b47cb6096efe3e976566fa9f0ee45dc1613db93c307b1e0dd +size 32088 diff --git a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_1_de.png b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_1_de.png index f5576fd8a3..f1620e0b31 100644 --- a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_1_de.png +++ b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bcc6e14373fd9da583310dbbc759d0286a3b8ae36ddb0284709e3b937eb82d45 -size 48300 +oid sha256:e9a4da42e5fb1bdd969d83c99f39bdd160fd277124499420b5f39f5e081a9f0c +size 48298 diff --git a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_2_de.png b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_2_de.png index f07f83f945..2c2febe8f0 100644 --- a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_2_de.png +++ b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92f2c1c5cac7875798293f1daff35c4753bc1aec81ec42101288bab269c633e2 -size 43345 +oid sha256:9c2f88235b71a5e8dc496b54ae3d6abf60ffd99372ad952b2588e4eb3d3a4104 +size 43338 diff --git a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_3_de.png b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_3_de.png index 18c2ef6331..54c412001b 100644 --- a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_3_de.png +++ b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:261f85d80baf9785fc8df2c83ba9978b1ba422ff129d264e8126e649fc7b95a5 -size 42458 +oid sha256:28438f4a4ea6e9e101ecb016a96a6d8acffabe78531d140245aae599f170742d +size 42487 diff --git a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_4_de.png b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_4_de.png index aa41993ba1..ec4adfbf25 100644 --- a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_4_de.png +++ b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f309d9cdb5c9b5aec9f9736b045895c33a2db5873207c9b4f410b429e3cb4c2d -size 36239 +oid sha256:89d0fb5b05db746127691b5cb40c88276d140a572122bb943e2dc80a1f8b8eb3 +size 36261 diff --git a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_5_de.png b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_5_de.png index 095e794a2c..36939df978 100644 --- a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_5_de.png +++ b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:066c64055bc1034d2451d9d812b2b48692260062364424d2de5be2eca81d3922 -size 52848 +oid sha256:506ac23b74c38aa367f42dadad28fbbd8d1af9d6c7d460551d304f66136d2847 +size 52879 diff --git a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_6_de.png b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_6_de.png index 98f53283c1..e92ab5411b 100644 --- a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_6_de.png +++ b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ed6a28099f8902cf9174b3c9cdf4b7d154b623e7fe37b03c96813b708cb290b -size 53922 +oid sha256:7ba72d82fe78861c420b21f5c67132b09b1eef6a4bc29de38282c0c655c9ccd1 +size 53948 diff --git a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_7_de.png b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_7_de.png index a6b85f2af9..eb202b9f3e 100644 --- a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_7_de.png +++ b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:355c47c2dbed84f808637b0093e7245667ad2dbffeabf84ea7f1244d377beca6 -size 43916 +oid sha256:95807c82c054157fefd82e05648f5c099613c33d816280280f40a92479009cb2 +size 43937 diff --git a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_8_de.png b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_8_de.png index 956be93533..8854711555 100644 --- a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_8_de.png +++ b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b86b0973f12c87c98f5fa626d8761bf02372fe0820a5decf4947876adf1a2048 -size 45006 +oid sha256:fe27d8667d4a0220b4f1cee7b131793dad65469b79f7dc3929c782c9025b9750 +size 45031 diff --git a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_9_de.png b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_9_de.png index be0253d957..a556aaaea3 100644 --- a/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_9_de.png +++ b/screenshots/de/features.verifysession.impl.incoming_IncomingVerificationView_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f97f11e866e1615cb826cdb6feeffa8ff106c5f41d3c9db9a262df5432ea05f -size 36929 +oid sha256:76825dbfb2e6c4aaf8111a16ba3811d0ca84cd970681ee9ae3e602bb98acdc83 +size 36962 diff --git a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_0_de.png b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_0_de.png index 49557625f5..2c61c2c6d8 100644 --- a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_0_de.png +++ b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ea3c6696b24362b472019ca6fcee0012b3fa49eea419b8f8de93f34d8180ebc6 -size 34232 +oid sha256:2bb815af912813460623ccc8d0ca07db315b1521fd2623371daa5f2ff8344fbe +size 34267 diff --git a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_10_de.png b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_10_de.png index badc9d4606..dad036e985 100644 --- a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_10_de.png +++ b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c3c0a2ec93e0ccabcb1b529dfa6fb117ea572719670dec770a31789d32db131 -size 27229 +oid sha256:a44eb83631c42b8f0ed4a72aa7ccf0720484363b8b9dd45bc8b35908c94f0e0c +size 27220 diff --git a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_11_de.png b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_11_de.png index 7d1347e719..7efb530cfd 100644 --- a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_11_de.png +++ b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ccf15ad76ec6abb1da4f73efdc98ae0fa74dadb2dc250c48567d5bd5c24154d -size 29551 +oid sha256:c035769839244b97f2bab01baa770c8b3cfa9f30eccf7e66187301ed984df318 +size 29546 diff --git a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_1_de.png b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_1_de.png index 3f36913215..d991096b7a 100644 --- a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_1_de.png +++ b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b9e4fd416b89d9c79d4c9413a45bcb9c455ed71d1e07184830bd7b91b61053a9 -size 41184 +oid sha256:6cabad8bafdf955fd6d82d8b14aaa1bea56ea6a73f51a2bd6a82538c527b2fb5 +size 41205 diff --git a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_2_de.png b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_2_de.png index cc63d4d5a6..8a415cf1db 100644 --- a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_2_de.png +++ b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:842f46fe337f143251bd8c2ec1e58e0823bb46c89cf82ef7491310dbb066d253 -size 25439 +oid sha256:42a7336ec373fa8b8b92d75f13f9328dd3594017dda18096a4af1fddecb44484 +size 25461 diff --git a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_3_de.png b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_3_de.png index 66e03de18d..6ce10e25a8 100644 --- a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_3_de.png +++ b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f83a6533053cead60f5bae4072546acefd5686a5f602b145bd9223626a8d8ee2 -size 21474 +oid sha256:2518686a8c0d1b3fc0a345b3d778c2b15cdabf645835a565e2c31ab027571bef +size 21505 diff --git a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_4_de.png b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_4_de.png index 095e794a2c..36939df978 100644 --- a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_4_de.png +++ b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:066c64055bc1034d2451d9d812b2b48692260062364424d2de5be2eca81d3922 -size 52848 +oid sha256:506ac23b74c38aa367f42dadad28fbbd8d1af9d6c7d460551d304f66136d2847 +size 52879 diff --git a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_5_de.png b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_5_de.png index 98f53283c1..e92ab5411b 100644 --- a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_5_de.png +++ b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ed6a28099f8902cf9174b3c9cdf4b7d154b623e7fe37b03c96813b708cb290b -size 53922 +oid sha256:7ba72d82fe78861c420b21f5c67132b09b1eef6a4bc29de38282c0c655c9ccd1 +size 53948 diff --git a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_6_de.png b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_6_de.png index a6b85f2af9..eb202b9f3e 100644 --- a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_6_de.png +++ b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:355c47c2dbed84f808637b0093e7245667ad2dbffeabf84ea7f1244d377beca6 -size 43916 +oid sha256:95807c82c054157fefd82e05648f5c099613c33d816280280f40a92479009cb2 +size 43937 diff --git a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_7_de.png b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_7_de.png index 425ec30050..3869f9c1a3 100644 --- a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_7_de.png +++ b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf2239dea75e1981f0bd12c3dc0e1490b5ff9dadd29a12d605414d63bb4db6a6 -size 32068 +oid sha256:49a84ee8cce1ce9415e70f3641dcc0cfb95a7728cb4640fe38e3d1181f8d20ad +size 32087 diff --git a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_8_de.png b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_8_de.png index bf8eac000d..a55a070617 100644 --- a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_8_de.png +++ b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ab908e704aca311e6097d64c2bc8029c8921bd84538fc6df3c970640a9001a1 -size 21617 +oid sha256:84e144a3dc9c02eb3d7ddfaa244bb30af331f64acf41d73fdd780c8b24b53ad9 +size 21644 diff --git a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_9_de.png b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_9_de.png index be0253d957..a556aaaea3 100644 --- a/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_9_de.png +++ b/screenshots/de/features.verifysession.impl.outgoing_OutgoingVerificationView_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f97f11e866e1615cb826cdb6feeffa8ff106c5f41d3c9db9a262df5432ea05f -size 36929 +oid sha256:76825dbfb2e6c4aaf8111a16ba3811d0ca84cd970681ee9ae3e602bb98acdc83 +size 36962 diff --git a/screenshots/de/features.viewfolder.impl.file_ViewFileView_Day_3_de.png b/screenshots/de/features.viewfolder.impl.file_ViewFileView_Day_3_de.png index b51de1e807..ca35124e38 100644 --- a/screenshots/de/features.viewfolder.impl.file_ViewFileView_Day_3_de.png +++ b/screenshots/de/features.viewfolder.impl.file_ViewFileView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:839eef003e2f567c59fc8a8719ef3a862ab16760cc4ddc4d82b1a68620736924 -size 9219 +oid sha256:5c6452424f2e5702af749d126b402a6a2b8b6a6d089cd87f41c9ca6bcaaa5c38 +size 9247 diff --git a/screenshots/de/libraries.accountselect.impl_AccountSelectView_Day_0_de.png b/screenshots/de/libraries.accountselect.impl_AccountSelectView_Day_0_de.png index 49108ddb0e..f112fc32bb 100644 --- a/screenshots/de/libraries.accountselect.impl_AccountSelectView_Day_0_de.png +++ b/screenshots/de/libraries.accountselect.impl_AccountSelectView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6fb3f21b9b6460403f30709bc3464c8f584af34e7ac27083827a5c2c6121a2b1 -size 8243 +oid sha256:696490cd2be6c6c63e7546dcc5af66038fed23d6a04feac841fc44eda88bce07 +size 8273 diff --git a/screenshots/de/libraries.accountselect.impl_AccountSelectView_Day_1_de.png b/screenshots/de/libraries.accountselect.impl_AccountSelectView_Day_1_de.png index ce17201424..79b4891388 100644 --- a/screenshots/de/libraries.accountselect.impl_AccountSelectView_Day_1_de.png +++ b/screenshots/de/libraries.accountselect.impl_AccountSelectView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bf13a8c2d3b46a5f2e2b17b681b60ccc62dc886d137ec5464e35c80f4afe19fe -size 49022 +oid sha256:1a107feebf22d62e559f7ea31951c6924f8991244efa2a032961a2808c432bf6 +size 43697 diff --git a/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_0_de.png b/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_0_de.png index c324e85343..25fd18d210 100644 --- a/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_0_de.png +++ b/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b8fe687014ad3a25b2f3f4818a9f17cef25fba7b18dfd792374c1ee7f1dc14f -size 19922 +oid sha256:5b80e05ab2c4c446d5f23f3e148299e14f03fa06440e128de4fac41e2c7fd5d5 +size 17475 diff --git a/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_2_de.png b/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_2_de.png new file mode 100644 index 0000000000..0a2a94ffa7 --- /dev/null +++ b/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_2_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd9153d4cda8258ca62a16acdeaba023d1c10ea74b7f9e22d401751f1d2deae9 +size 19597 diff --git a/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_3_de.png b/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_3_de.png new file mode 100644 index 0000000000..0414a20c94 --- /dev/null +++ b/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_3_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b869af5e094555bae21115ddd8e637c463bf8d0e1f1ac9a88d0e84a7c0c191b +size 17324 diff --git a/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_4_de.png b/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_4_de.png new file mode 100644 index 0000000000..10531b43bf --- /dev/null +++ b/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_4_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc23094f3d51c108e64490f9ecc1f10c3ba8cb86f3273cd990181d21133dce4e +size 19320 diff --git a/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_5_de.png b/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_5_de.png new file mode 100644 index 0000000000..64a879ff74 --- /dev/null +++ b/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_5_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a4dc61d7b3fc791fe6362cabcbb67a15d85669d778c1b186c73fcd470df3b21 +size 19295 diff --git a/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_6_de.png b/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_6_de.png new file mode 100644 index 0000000000..4c9158442d --- /dev/null +++ b/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_6_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a26d18042ebe6fab5385a3d93fd9492894461ad07388f9f4f73b7c3d735398f +size 16996 diff --git a/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_7_de.png b/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_7_de.png new file mode 100644 index 0000000000..c324e85343 --- /dev/null +++ b/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_7_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b8fe687014ad3a25b2f3f4818a9f17cef25fba7b18dfd792374c1ee7f1dc14f +size 19922 diff --git a/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_8_de.png b/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_8_de.png new file mode 100644 index 0000000000..7eae77df84 --- /dev/null +++ b/screenshots/de/libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_8_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab51c4dff0246b002f828381e04d331c98aab98f4581c566ac957515d64589a5 +size 19721 diff --git a/screenshots/de/libraries.designsystem.components.dialogs_SaveChangesDialog_Day_0_de.png b/screenshots/de/libraries.designsystem.components.dialogs_SaveChangesDialog_Day_0_de.png new file mode 100644 index 0000000000..cd461a5e12 --- /dev/null +++ b/screenshots/de/libraries.designsystem.components.dialogs_SaveChangesDialog_Day_0_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0406efd19b2e8c9d83708af8669b8d2e8aaafef335eaef972e363e85589ccc56 +size 22171 diff --git a/screenshots/de/libraries.designsystem.components.dialogs_TextFieldDialogWithError_Day_0_de.png b/screenshots/de/libraries.designsystem.components.dialogs_TextFieldDialogWithError_Day_0_de.png index 2feea54379..504ec13509 100644 --- a/screenshots/de/libraries.designsystem.components.dialogs_TextFieldDialogWithError_Day_0_de.png +++ b/screenshots/de/libraries.designsystem.components.dialogs_TextFieldDialogWithError_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88f04fda2bd15ff245d888161f4e3dad21f550e6b2c7bd2a1026ca6e8eb450ca +oid sha256:342c22811c50c595f1288c0a24ef51d35814c703691dc8fe5da6cd97da89ff25 size 18763 diff --git a/screenshots/de/libraries.designsystem.theme.components.previews_TimePickerHorizontal_DateTime_pickers_de.png b/screenshots/de/libraries.designsystem.theme.components.previews_TimePickerHorizontal_DateTime_pickers_de.png index d72b78ab4c..056bccc5b5 100644 --- a/screenshots/de/libraries.designsystem.theme.components.previews_TimePickerHorizontal_DateTime_pickers_de.png +++ b/screenshots/de/libraries.designsystem.theme.components.previews_TimePickerHorizontal_DateTime_pickers_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:853edd40133d53f4109934cadfd7efd2cb6c6aeab6ad345b29a9008e902a9904 -size 36437 +oid sha256:ad5c8bbac1c770be5924fa44d79fb728b54593a6091b5a3fc78a69936668eab7 +size 34121 diff --git a/screenshots/de/libraries.designsystem.theme.components_SearchBarActiveWithNoResults_Search_views_de.png b/screenshots/de/libraries.designsystem.theme.components_SearchBarActiveWithNoResults_Search_views_de.png index f91d67848f..026aa22f79 100644 --- a/screenshots/de/libraries.designsystem.theme.components_SearchBarActiveWithNoResults_Search_views_de.png +++ b/screenshots/de/libraries.designsystem.theme.components_SearchBarActiveWithNoResults_Search_views_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a790bf2a4ddf074b1b040b8d3833fc3ccb7f37867e5ce98785cd6b80ed780ab4 -size 10421 +oid sha256:f846e8780ecc4d76c5623f67b1fb2637daa0dd3844464aa59ed122f553d6508f +size 17214 diff --git a/screenshots/de/libraries.matrix.ui.components_AvatarActionBottomSheet_Day_0_de.png b/screenshots/de/libraries.matrix.ui.components_AvatarActionBottomSheet_Day_0_de.png index 5e98741cdd..f35e18a131 100644 --- a/screenshots/de/libraries.matrix.ui.components_AvatarActionBottomSheet_Day_0_de.png +++ b/screenshots/de/libraries.matrix.ui.components_AvatarActionBottomSheet_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:37e098e3e33c4b518c6cfe5358f3b79b1bce2117cd968103cf325e44da95fda2 -size 14163 +oid sha256:a8b9ee22c008b5075d9d194a2fd915e9dee08407b837a61f4135c661944052f3 +size 14182 diff --git a/screenshots/de/libraries.matrix.ui.components_CheckableUnresolvedUserRow_de.png b/screenshots/de/libraries.matrix.ui.components_CheckableUnresolvedUserRow_de.png index b2f0e64495..262a2e6c45 100644 --- a/screenshots/de/libraries.matrix.ui.components_CheckableUnresolvedUserRow_de.png +++ b/screenshots/de/libraries.matrix.ui.components_CheckableUnresolvedUserRow_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fde26e4404bb2a324cc098fc0c16dbfeabaf40fad7fc59c279c756e200b3a03b -size 127545 +oid sha256:d4450043c6ecfee5bdbd017027e5b20372b35f01ac3821d5a73cfbf807799424 +size 116244 diff --git a/screenshots/de/libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_0_de.png b/screenshots/de/libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_0_de.png index 64377be81c..5c9d040b9c 100644 --- a/screenshots/de/libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_0_de.png +++ b/screenshots/de/libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:85f91b6c1665dba28fb428c0079a42b7ed3fc2f8e5f5b2f40df9c7f80bc47a3a -size 28681 +oid sha256:c7efe3fbc80ebe43ffe85f8aff63dcc2cb9dd9efc8f9f40b656be1741ad071fe +size 28679 diff --git a/screenshots/de/libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_1_de.png b/screenshots/de/libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_1_de.png index 41c764998a..fbfdbdbc67 100644 --- a/screenshots/de/libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_1_de.png +++ b/screenshots/de/libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bcb0503b81b31809515e2c38824813e99ab7f9bcc9b79ee5822796656955890c -size 26977 +oid sha256:5bb75f313e62e4ceb97f86ca19eab72c46d351d43bb16c19970ea50304255186 +size 26964 diff --git a/screenshots/de/libraries.matrix.ui.components_SpaceHeaderRootView_Day_0_de.png b/screenshots/de/libraries.matrix.ui.components_SpaceHeaderRootView_Day_0_de.png index 7e5de34316..2392471c84 100644 --- a/screenshots/de/libraries.matrix.ui.components_SpaceHeaderRootView_Day_0_de.png +++ b/screenshots/de/libraries.matrix.ui.components_SpaceHeaderRootView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0095fd43138cfbc55518c6aeb4a9e578d2ea299e8f55c658e80120076402847d -size 18263 +oid sha256:46aba86f8df1ba4cffab0a31b83df6ba5ff5cc04ba7ec636d50173160bee86a4 +size 18231 diff --git a/screenshots/de/libraries.matrix.ui.components_SpaceHeaderView_Day_0_de.png b/screenshots/de/libraries.matrix.ui.components_SpaceHeaderView_Day_0_de.png index 1f052f4c3d..45f40d68a3 100644 --- a/screenshots/de/libraries.matrix.ui.components_SpaceHeaderView_Day_0_de.png +++ b/screenshots/de/libraries.matrix.ui.components_SpaceHeaderView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0978f8363e8d5234bc19625156a572219a6cbab3573e2c024a1cf828d4446af9 -size 61237 +oid sha256:b599c588931b4cf33b2c2b501241d85618899abde7de94f369034d3b93e49ce3 +size 61245 diff --git a/screenshots/de/libraries.matrix.ui.components_SpaceInfoRow_Day_0_de.png b/screenshots/de/libraries.matrix.ui.components_SpaceInfoRow_Day_0_de.png index 1407cfcd80..b2b563a220 100644 --- a/screenshots/de/libraries.matrix.ui.components_SpaceInfoRow_Day_0_de.png +++ b/screenshots/de/libraries.matrix.ui.components_SpaceInfoRow_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20ab8a876550e5536c89b8b3dc7028444a9430694f3fc97522fef4d601ec2f87 -size 24582 +oid sha256:caf8e89b4d906f0e1535f437ec056b51447a3fcddd0ec8078cb269c4b875eec6 +size 24616 diff --git a/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_0_de.png b/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_0_de.png index 738e01fe3f..56832f5fa1 100644 --- a/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_0_de.png +++ b/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7d03c199e22eb0f0b6e34b42efc65d138ac32c743631f65553ba3497ded0362 -size 17452 +oid sha256:95cf04b7cedb5b2266587935fb3fc2a92b0260963c60115431faca31907c05e0 +size 17548 diff --git a/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_1_de.png b/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_1_de.png index 4f234befc6..733a6b5fd5 100644 --- a/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_1_de.png +++ b/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06ba4b65baf7638599ce0adefe25c3836c322168c5e0cdfe8f93162b2d3b3b8a -size 13462 +oid sha256:37d159879b3a4d2d5c9f9465c6499de34c94e81960d6a528628c455c024f42b5 +size 13386 diff --git a/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_2_de.png b/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_2_de.png index 1c3fc7c033..982e1c0c77 100644 --- a/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_2_de.png +++ b/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:59335d0ddefd6d4b668ce0004c992ef24f34268e3155ac8c41fe9418c700bf2b -size 10782 +oid sha256:adbd52c4c086e50126a5dad0317cb8cfa9d47bbbc8e4be8cbfdfd666935d45af +size 9001 diff --git a/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_3_de.png b/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_3_de.png index 388eaba735..848e75af76 100644 --- a/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_3_de.png +++ b/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:96aef4e08d3d1d21701a8c18832f60eca0317c2ced0c4f06d8d9a71231434bbd -size 24579 +oid sha256:cb274060c32afbbb140c124ee2ec4674883d8e9ef4106930d466d65499235f61 +size 24643 diff --git a/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_4_de.png b/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_4_de.png index 866b8156d9..4369dd3160 100644 --- a/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_4_de.png +++ b/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:52beb69cc61ef5a526b371c28859e9fd290ef709d2ee01a60a5343f3397acf52 -size 19003 +oid sha256:e4d0aaadc6bb2ed7157af0b843b62c66a49ad531a35e9f46947c6dc2bf428196 +size 19179 diff --git a/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_5_de.png b/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_5_de.png index fa8d622303..b704a287e8 100644 --- a/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_5_de.png +++ b/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5c913b95850bcd97e39dc415bfd130eddacb4b22119998eb083217804ed530d4 -size 14892 +oid sha256:4c295b1cc75ee1db2796b3b1586f2dbc7b56260ef1452bcf4cae751455a12a57 +size 14825 diff --git a/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_6_de.png b/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_6_de.png index 96ab4eec7d..048cb4eb18 100644 --- a/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_6_de.png +++ b/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e79cc9e9d2cb0918afcc6ae3c4590b7d4d79ae3fc3d6480f88797ea93cf64b1 -size 34985 +oid sha256:3ff635ac9744c957753dcd714d18502073a3bd97900e605290b08d9407531e34 +size 34979 diff --git a/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_7_de.png b/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_7_de.png index b74c0e675e..c7123ed2d8 100644 --- a/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_7_de.png +++ b/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ce56146c048dc9739a59564e6b53dfe8857f02489817a7e7931b00870b197b5 -size 39827 +oid sha256:5b9c6b8bc294d78559eaa9d187ec96891a45d9d930138787e259e3e03285dfd8 +size 39759 diff --git a/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_8_de.png b/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_8_de.png new file mode 100644 index 0000000000..b7e0334ce2 --- /dev/null +++ b/screenshots/de/libraries.matrix.ui.components_SpaceRoomItemView_Day_8_de.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5403c6fc3ca15b7c2099772257b42c2528d78993e8c9d83e0e20ba0f3e64f2e +size 11420 diff --git a/screenshots/de/libraries.matrix.ui.components_UnresolvedUserRow_de.png b/screenshots/de/libraries.matrix.ui.components_UnresolvedUserRow_de.png index 7718515f1f..f6d4ee1516 100644 --- a/screenshots/de/libraries.matrix.ui.components_UnresolvedUserRow_de.png +++ b/screenshots/de/libraries.matrix.ui.components_UnresolvedUserRow_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c9e55c46d0f6633650ea00b79a97e79f140e2ecc27e4af979a4d7a8ce059d1f7 -size 73790 +oid sha256:a2bde77f49fd90f85c376daaf41346838c2d3e91a8cf52d734c61c2f419aa369 +size 74298 diff --git a/screenshots/de/libraries.matrix.ui.messages.reply_InReplyToView_Day_4_de.png b/screenshots/de/libraries.matrix.ui.messages.reply_InReplyToView_Day_4_de.png index a63226c222..007cf090d8 100644 --- a/screenshots/de/libraries.matrix.ui.messages.reply_InReplyToView_Day_4_de.png +++ b/screenshots/de/libraries.matrix.ui.messages.reply_InReplyToView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef21868f9e2a8438c344597e575aa4f73ccc110c648d463ecfcc9167a844e3fb +oid sha256:5e8b2eac58fa493c2c7b91b064bacaffc6b942fc98defd9a348896e8b697973e size 8892 diff --git a/screenshots/de/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_de.png b/screenshots/de/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_de.png index 873c014b18..d53e274d51 100644 --- a/screenshots/de/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5a9b70ccd2182c71f91a41c4c4563719995c2b1735c576df4607685617052cc5 -size 35704 +oid sha256:ff6c6c7414b42557f253a0ac9ded918f2ea3085806e5858f63d9f2491817677b +size 35689 diff --git a/screenshots/de/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_de.png b/screenshots/de/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_de.png index 101f89bb83..eebead276c 100644 --- a/screenshots/de/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aecba3d40603a4023dfcc8507cf19adc983e9993544c618d4bb3b5635bbbf921 -size 43059 +oid sha256:bea0bbfeeffc6e4fcfcf5c2802e415eec785f923f114afcb31f8e80f49b97f01 +size 45514 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_de.png index d8ce49ed62..5ffc85b5e0 100644 --- a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8802d043fdcda3663a7703b1986a25043d299008b0eef8a220dad5ccfd164b4a -size 64756 +oid sha256:70195806d10af6be3257be03cde5d9ed35c5f2672567bb8541a081abcb162d89 +size 64770 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_de.png index 7e6337b642..943ab20818 100644 --- a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:471d9a1e70e019f373958aca834bf631874dc64fc8a2e04a5a8c36c11bf41770 -size 15256 +oid sha256:8749fa29c9d8d9f1672fad947dac4c48c694ef7827b5023bf691cdf458634a10 +size 15264 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_11_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_11_de.png index e2168318d1..20488edc7c 100644 --- a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_11_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b19feb9904ba5128133c60af1d6ffe5755b4ef009b9611f3af83d3a25ae495a6 -size 61884 +oid sha256:989062a145069bea7054d9683ca4c1f1b4a4c0a216dd704ea95b092b4e44e7d0 +size 61894 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_12_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_12_de.png index a8fd83c3db..63a3a14ce8 100644 --- a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_12_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_12_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fdb11e06a185f48bc899582c6fe38630d5b9a887b1f0908b586a43b17c0485cc -size 61885 +oid sha256:9f6fe12d0259d82919c721b9ed6239825e18723c04b501b270737e714ba46845 +size 61895 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_de.png index e2168318d1..20488edc7c 100644 --- a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b19feb9904ba5128133c60af1d6ffe5755b4ef009b9611f3af83d3a25ae495a6 -size 61884 +oid sha256:989062a145069bea7054d9683ca4c1f1b4a4c0a216dd704ea95b092b4e44e7d0 +size 61894 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_de.png index 7f5645467f..e4bba4e8f6 100644 --- a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c64b54d87184c26efafa8a527f4854251a08ebe099b80b8554baa5edd779dfe -size 75346 +oid sha256:d4fdd3fdfbf5bb289234fb740beaa3cfd9a39c964c7eab18086b9f69ecc24102 +size 75349 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_de.png index 81ab79ed90..5bdbee228b 100644 --- a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fdd36f2537be1e2e5c32fcba86e53478d7e99ce152c59141f78e71917a181364 -size 529161 +oid sha256:a64d9659245052e31109fd2ca96dade3c01302ebd9d2b48375fd90df732ce4e9 +size 529167 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_de.png index a8fd83c3db..63a3a14ce8 100644 --- a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fdb11e06a185f48bc899582c6fe38630d5b9a887b1f0908b586a43b17c0485cc -size 61885 +oid sha256:9f6fe12d0259d82919c721b9ed6239825e18723c04b501b270737e714ba46845 +size 61895 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_de.png index a8fd83c3db..63a3a14ce8 100644 --- a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fdb11e06a185f48bc899582c6fe38630d5b9a887b1f0908b586a43b17c0485cc -size 61885 +oid sha256:9f6fe12d0259d82919c721b9ed6239825e18723c04b501b270737e714ba46845 +size 61895 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_de.png index 779fd077ba..1831186d29 100644 --- a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6e640ef7595216d2c9365141799e3b5f4c636a2892fb0c21280ca7a9a87c0144 -size 82118 +oid sha256:d56515e555fe3b67b7bc9966f1b6081dac08c026056040d98adc924bee96ef8b +size 82125 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_de.png index 2c9dbc4886..d286963ece 100644 --- a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cfb983c7773607092a316ee712c0d50ec4e0bb2cc69e53d849d98b32c2037c99 -size 33321 +oid sha256:06e84aad3494762d9f82db6f89a1c82b29e9fac055ded4f92fe109a553eb7016 +size 33335 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_de.png index 8b2ea46507..90ed906e03 100644 --- a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2af40dcc68f10418ece9ebb6c5148bc2a3f339486f975dd2f6e82a3296554143 -size 45785 +oid sha256:be243fc9a55bd214187605b79226885382f0a92601ceeac4e6495179d982683d +size 45443 diff --git a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_de.png b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_de.png index b1428db8bf..7c3707060c 100644 --- a/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d422c54444ad386393580e35860ebb5a2e9121c5566b41455ab512cbb9042efb -size 15264 +oid sha256:8c125e94a03f510377e19bd583f2f76299ac1770377f6a70b07b721c7a8d2b98 +size 15271 diff --git a/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_11_de.png b/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_11_de.png index efe2112eca..212b565f9f 100644 --- a/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_11_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9035f7f5e0981bfec028980576ddf3cb25fd03fe3e9c824efbb6579ba4d5816b -size 42671 +oid sha256:1b8297bd3c5532a327728b0d517e6a634bf4f689df3f3e8a039814eb26f659af +size 43984 diff --git a/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_12_de.png b/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_12_de.png index 8d6b00f872..30cdca9a84 100644 --- a/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_12_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_12_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb5df77727252a85aca9913b465026521178c9da77e27738df3bd0a99876676c -size 35317 +oid sha256:19c61fdc86c8fbf8017c6f67fb9a99fb7d80a69e40ea92d3008918abce1c7b80 +size 35289 diff --git a/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_14_de.png b/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_14_de.png index a82c4fbb48..caa3ec4884 100644 --- a/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_14_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_14_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7353634dec22f3c4112c3d71290e8959775e97f7f1c140b0ec69c71f675c13c8 -size 8862 +oid sha256:c49b02ae7e627f0cb4aa2f1c6a031892296ba73e3595d3fbe7a95e508d42dc0f +size 8851 diff --git a/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_2_de.png b/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_2_de.png index 39dedcbc2a..2e686ea4ee 100644 --- a/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_2_de.png +++ b/screenshots/de/libraries.mediaviewer.impl.viewer_MediaViewerView_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3d6bc0b609f19136a6bdf9752d399047aac63f26e88bea363d823b5273f1657d -size 45992 +oid sha256:fd7994171f75b775cabe7e1be07a8b47a6ac502d9a4eaaded5696f446ca6c790 +size 46014 diff --git a/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_0_de.png b/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_0_de.png index c22f818b3b..6bfc8717fd 100644 --- a/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_0_de.png +++ b/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a89366ca6bc6a19e2661466d43f883532a84ad129bdc6f0249be8a0215f38184 -size 14046 +oid sha256:a0326603669450bb553227feb450a83c2c4f8a83a0b45fc00a0439b5ae6cee40 +size 14087 diff --git a/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_1_de.png b/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_1_de.png index 67b221f32d..b62ad00757 100644 --- a/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_1_de.png +++ b/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6388e6452d693c8675ef66e55c53aba8625c54ba3fa1667ee808f00a06259ef7 -size 12015 +oid sha256:4e1d912da6419dc1baec30171634ac9764862390d714191b71c2b94d896fa10a +size 12058 diff --git a/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_2_de.png b/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_2_de.png index b191e15948..5ce059f931 100644 --- a/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_2_de.png +++ b/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:726e9c2aa1399e8aca167d4dfc20684cbfa5ab0dae29465f5702ab1998887246 -size 32556 +oid sha256:53f60adb0f0abafad2a18536e13a64b6a9115f78c51de81fcf87521e14de79e9 +size 32590 diff --git a/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_3_de.png b/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_3_de.png index 6e92d8729f..4354ba502c 100644 --- a/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_3_de.png +++ b/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f5499db3bfde1f52df07f0378c506f45ba57bce0bcc5984627b9719d52efec8 -size 30902 +oid sha256:5171547f2ff09da353a6a2fb52a734930c165cdcf22b1dca58b89e47d2c3626d +size 30933 diff --git a/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_4_de.png b/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_4_de.png index b1e005c407..5704892c4a 100644 --- a/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_4_de.png +++ b/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:08cd625decbc86790b9d3ffa4dd250ddc45ab5e83758bc365b53dcc028a1ceb1 -size 35167 +oid sha256:ad039c7af5726a980a586bd483c4aa938dde1cc2f116882ad7b6ca343495805d +size 35196 diff --git a/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_5_de.png b/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_5_de.png index 6a53fe6dc6..30c7d47ede 100644 --- a/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_5_de.png +++ b/screenshots/de/libraries.roomselect.impl_RoomSelectView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d739c07ec0320b2f9cb5229cbe1f851548b60affd674ada974c38ae4f1e2efc4 -size 30220 +oid sha256:180014cd36d6721b426d212581f9cceb3ba55f6e3e11fd5357d9311f786526c0 +size 30257 diff --git a/screenshots/de/libraries.textcomposer_CaptionWarningBottomSheet_Day_0_de.png b/screenshots/de/libraries.textcomposer_CaptionWarningBottomSheet_Day_0_de.png index fddd65714a..ee6761f945 100644 --- a/screenshots/de/libraries.textcomposer_CaptionWarningBottomSheet_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_CaptionWarningBottomSheet_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36e21a9663aeed7a474d422ec0c67cd770bf0d68ffebadcd770d115cb361ed47 -size 21279 +oid sha256:c28a1a1ac113584aadf170b728574e4a34f888bf0220c2ce86a5911a94482371 +size 21284 diff --git a/screenshots/de/libraries.textcomposer_ComposerModeView_Day_0_de.png b/screenshots/de/libraries.textcomposer_ComposerModeView_Day_0_de.png index 8a66f0d314..58618a746a 100644 --- a/screenshots/de/libraries.textcomposer_ComposerModeView_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_ComposerModeView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa9ab0bf9088d4d19738eb2606b1f3e0dcf3aded754ee3a3ca5956d4a6a9fcde -size 6185 +oid sha256:4179ca2e2ea4337930c7d4fe0dac6f109488c33329742307aa9a1b56e55d7292 +size 6158 diff --git a/screenshots/de/libraries.textcomposer_MarkdownTextComposerEdit_Day_0_de.png b/screenshots/de/libraries.textcomposer_MarkdownTextComposerEdit_Day_0_de.png index d127fc83d7..98454d33f7 100644 --- a/screenshots/de/libraries.textcomposer_MarkdownTextComposerEdit_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_MarkdownTextComposerEdit_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60cc65fbeb8cda08f6ad79c30b0c67de60257a7993f9cf9a77a2e0b536ccf919 -size 55466 +oid sha256:4e9f5405a4d1174473d85c9fd83ed8637857a6871d74b3de5ba38e2365b8c001 +size 55433 diff --git a/screenshots/de/libraries.textcomposer_TextComposerAddCaption_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerAddCaption_Day_0_de.png index 40b5e4fcdd..1bd7ba7633 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerAddCaption_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerAddCaption_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fee1127041bff5a4047dac582bb8353d0b3bb4b2ef41f486d7bf7f646873591b -size 61474 +oid sha256:f6223ae2ec69da18619d6b0e8ec672ef5039cf4f8b4d90dd7ab8fa8f62e09f67 +size 61498 diff --git a/screenshots/de/libraries.textcomposer_TextComposerCaption_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerCaption_Day_0_de.png index 569d92dccb..b7d8a1c7b4 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerCaption_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerCaption_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:096a1828bc630757e6abd080624fd81b4d3e667f83ce428fbd7a9bc9f6c1f7b6 -size 46548 +oid sha256:dafda8e667309fc1ee09aeabc86332d1fc1aeb15000244239b5dc3db05a88fe0 +size 46550 diff --git a/screenshots/de/libraries.textcomposer_TextComposerEditCaption_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerEditCaption_Day_0_de.png index fe75c2ca42..67c758b8f6 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerEditCaption_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerEditCaption_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6a9f4274e261724436a37c9ca59796c412e2f4ec4711bf2bbb8d8f7b3e01543 -size 59501 +oid sha256:aabbb0a33bea214075a6d1f7115ce24096b1c022c1cd50733d0816f8e6ebaefe +size 59490 diff --git a/screenshots/de/libraries.textcomposer_TextComposerEditNotEncrypted_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerEditNotEncrypted_Day_0_de.png index fa69690dc0..6e1dd6cf39 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerEditNotEncrypted_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerEditNotEncrypted_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8acf506699000a01eb65fd8acab056d070c1599a59319a0aae6dd1e38023da83 -size 67973 +oid sha256:525aee31e2c09c8f7134e174de4e46f1913a5b4f3a88a4f2fba50dc615fdffc5 +size 68035 diff --git a/screenshots/de/libraries.textcomposer_TextComposerEdit_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerEdit_Day_0_de.png index d127fc83d7..98454d33f7 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerEdit_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerEdit_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60cc65fbeb8cda08f6ad79c30b0c67de60257a7993f9cf9a77a2e0b536ccf919 -size 55466 +oid sha256:4e9f5405a4d1174473d85c9fd83ed8637857a6871d74b3de5ba38e2365b8c001 +size 55433 diff --git a/screenshots/de/libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_de.png index fb8bea88f5..6dba6f2792 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4782b03a120bbb9c54ebd34bb5ee892686277301fb8f1f3131d86a987f42f8e -size 66107 +oid sha256:53c40e5833c999c8e3d49bde6f565d32a4381a40e7c0bcba845d16363c6f3faf +size 66163 diff --git a/screenshots/de/libraries.textcomposer_TextComposerFormatting_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerFormatting_Day_0_de.png index 9136cb6afb..45edbb487d 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerFormatting_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerFormatting_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:187aa4b837fcd244f4cef929c9dc093defdba5315025e5ee2faea34d62aaa1b4 -size 53588 +oid sha256:a46aa7eb61ef981a252fd8f02797adc603590fa25499921a6c8185078f4f5a81 +size 53554 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_0_de.png index a99064f50b..e56631e249 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40592dd5b485f10fb6b180e676f59bc2ea3946b7966a7d3b82e0b5e2b6f8f53c -size 75347 +oid sha256:68741be741a63204b1ea6099ae2833b0aeb5defc1367d97bada887d5b96a6531 +size 75368 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_10_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_10_de.png index 2979c82d8d..e92eaf2597 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_10_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:717054f6cf1deaccc69e0f23587d4362b3f473c36abca4b08ea665affd1caf38 -size 61932 +oid sha256:8fabc114c60514f7eb512c8992a82bf1c946592c6e8187c87215cdfe7fe21a5f +size 61956 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_11_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_11_de.png index 784c0fd618..c07587a3be 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_11_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e13c7bc1577ceb046e6eff0ae8750299ef7f652e5e8b8e4183bca5e9b5c3b225 -size 74758 +oid sha256:f3703422fb0a8c6fba5116f715743cfba7b4eb94139563e25d796ff51c6cbee7 +size 74797 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_1_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_1_de.png index def666e030..aebf3f050c 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_1_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51f6dfb47b2799974065516f6e111730da5d8b6e2c904e297e11c6acb2c2835c -size 83409 +oid sha256:41d290c3153b85b1d97f2dbb087041c1dc57db31bd8904d3919dcf0b178b2286 +size 83439 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_2_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_2_de.png index 543f397c18..1b83986771 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_2_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1eb54ffc3ab7ec3689ffabc896cb96e447b02784a68fd8c387ed89c62b07208a -size 64480 +oid sha256:4cccf5887c1fe53f317e6d1823e747e651d4b615e73f81a68e730ba1d3aacc6f +size 64490 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_3_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_3_de.png index ee37f89e24..f7a5b24bcc 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_3_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09aa66b3c268caa5bc6b26192f27000be4a4894b1f54e4e659e2b215ae537a54 -size 63354 +oid sha256:0296e105d089b5afb565bbac72f7a3cdd92c0381ab867494114f2ec8067af18c +size 63387 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_4_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_4_de.png index 51f6c059ba..1864d91132 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_4_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c39ce41b357199a9e571f86b45f60f1dd42adcba243ead70b617723b01766f0 -size 69003 +oid sha256:e97005de9f3b1ae2873888c3258aa39c92fefa98194c55ed9d6abc7c8850af27 +size 69035 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_5_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_5_de.png index 22eeb8d4b2..b5a3abc87e 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_5_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:237f46b3a247dfa29c545751d65a6a7317632edecd0daf81619912446b3900b2 -size 92282 +oid sha256:826550edf105257ff91ff2c9b798a128da4172125fc2d157bba6eaeb3f3cc60a +size 92294 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_6_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_6_de.png index dc35e95678..12a40cd0d9 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_6_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1e1d7bb5df8b681b4f68ca7dc8d4405c35f6a36fd40da84dd3f6ead8d601f28e -size 62803 +oid sha256:5d859305f9f78b7c969d2ea8a3f49fe00015d1dc3c49b24dcd3d228705688d4a +size 62833 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_7_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_7_de.png index ea7f60920a..95325575c3 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_7_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9e3cfa1d4e2c6f5b9a3eb0a72a65c00b1ed94491b6d10ea589e9706d55d8c2e4 -size 63905 +oid sha256:4cab4d95d4466412843b716e27d43507c06a777aba77c9b3df3350a268c7597d +size 63975 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_8_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_8_de.png index 642fff5c6e..b42dec9211 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_8_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7fdbb5f58bf14189d6d97269b70d31719c119598e9064f897a66b718d3b86a40 -size 71620 +oid sha256:b33f245c717a8c8865aafe9aa3a09ff1201a8532db5327c536410b11e4479f3c +size 71660 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_9_de.png b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_9_de.png index c60b586751..da763a7902 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_9_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReplyNotEncrypted_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7373c877c6c447cc9f436bd959dfb8e1f71bd61177b6d152353a2f5b43aa2c8e -size 62337 +oid sha256:6a9589f57428065452bcfa119b12d3d5ac37376771ab4c47378b458b470785a2 +size 62360 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_0_de.png index 2d07064de8..db3424e835 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60c4d9f1473045065241e3b0c09852671de07e1a31cd7eb696e6ac7fdfb09e51 -size 75722 +oid sha256:633eafbaca68ce66d4dc8d6f14f3b27c461bfc2ffd80dc390d667535e64c98e7 +size 75740 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_10_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_10_de.png index 7f892e2d62..494d25ffd8 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_10_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_10_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d92f14b72f92753cb46b4ad46d395412a7155c2a9074c57562ab00e6e3c73c03 -size 59288 +oid sha256:46def0920f70388300ded71846b5bb42fc4df6fe87547adefe089e4b6c83f0e9 +size 59293 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_11_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_11_de.png index 4bde8bd861..90728f2c41 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_11_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_11_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:759ec0718213e6b6ee6fa41cbbce455687a353b98a3091cc3726007066cabd0f -size 73946 +oid sha256:cf467fdb5211d4d4993b9ae83161ffeb90a5d910663eb4914eda60a391219d24 +size 73967 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_1_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_1_de.png index ad5146d4cb..ee70c8765f 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_1_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff399c6e701123d42878b6a0ef382691d7087ec5bdafe15b1ec757d702a7ac5d -size 85209 +oid sha256:024ac538e1778cf6c8fa3e489602e19efe64e2262c1b0b3f6988191a9483a500 +size 85227 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_2_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_2_de.png index 60803e9382..79deb8e87f 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_2_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b47d98d581d278f6e7afaccb590056bafc306b6c92b2f05c7c284d132e819e0 -size 62301 +oid sha256:ab37052348d8d881d7bd6b41edc715db442cf726453df047b8c0fe0d9d55125d +size 62309 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_3_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_3_de.png index 34851b527b..83f104ba46 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_3_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c407a602fc180ff841ae62405140da8978694b1b6f5f46c08a774607fbf3d3db -size 61365 +oid sha256:dd08e08441c5b058f7d17c77a609ea118d2d10f7364b596dd18d161f2729d639 +size 61369 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_4_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_4_de.png index d79e8b441d..693fec3170 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_4_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:195e435fcaebd76fedb1cf41f03776e1e68eb94f745c26b46d112d2d3fd86701 -size 68837 +oid sha256:f565b9afb2124d4dd215bc7c0971c5fe8010e19f5a6c21ebea05af1da1c9a4db +size 68852 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_5_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_5_de.png index 46158a9f2e..865c8c2504 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_5_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e4937bac91e2ce148abd5b09699291ca700d976069df2d39e62c86ef5acce36f -size 103915 +oid sha256:7a3335a970e8fbf1cae6573a258f8343db844c457a6abf202f0a03e9cb691557 +size 103914 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_6_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_6_de.png index de116d7eaa..51c97c7187 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_6_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6256e13d0a7fc9b7416a4c24fd0a6e807cbd26c50e5b3794ddfc88bc3477aa9e -size 60552 +oid sha256:a52b25f13f88db723c0d397c9b76bec08cc29154b4b0802a365205c87caf0124 +size 60555 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_7_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_7_de.png index 05cc000090..3fd37f9bdf 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_7_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:30eda7acc6b1f2bd984f8502ec88a0c9b8d79823e29b078228268a295efee748 -size 62485 +oid sha256:018c8fbff159cc5d5e5b356b1262f4ef50fae39536bcb8f57233a975ccc587b5 +size 62474 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_8_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_8_de.png index 059aa4abb3..c23aaf541c 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_8_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_8_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a5b0960c05c047ae832a61fca7cf9593cc0ad24decd2eab363e652c1e3d962a -size 70887 +oid sha256:4628c587c1a4f81fe55560e858ae006af3b9d59b87d224e6b5c35fb9e8963b64 +size 70891 diff --git a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_9_de.png b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_9_de.png index 19ef265ff6..7a88a63738 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerReply_Day_9_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerReply_Day_9_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eda41a9560b0acdc6fcaace4d1bb274d5a65f1b5c95073ee86d828ad4b1fa33f -size 59925 +oid sha256:3329174b443e45a350b10fd4424ab6cd7d918e461861b30a7bcc6f504d322f20 +size 59931 diff --git a/screenshots/de/libraries.textcomposer_TextComposerSimpleNotEncrypted_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerSimpleNotEncrypted_Day_0_de.png index dad38da8c2..da1d3f39eb 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerSimpleNotEncrypted_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerSimpleNotEncrypted_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53e771de4de90be6356b2a218d58ca658dc2895c32c63eafe8316557f259e8ac -size 58303 +oid sha256:fc72269a07c163bc490a590d73dabb1d698e28298a69df89a058d20c8051d89f +size 58391 diff --git a/screenshots/de/libraries.textcomposer_TextComposerSimple_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerSimple_Day_0_de.png index cb60634ec8..777b608e19 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerSimple_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerSimple_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:52a0b4d220040f7115109ec3434f4d0067c753608b7cc4736c634752caf1cfe2 -size 46254 +oid sha256:0c0e5f446f2265f5eda7f381216c18d85b3eeed55b47e4a8617fe4234d49804d +size 46264 diff --git a/screenshots/de/libraries.textcomposer_TextComposerVoiceNotEncrypted_Day_0_de.png b/screenshots/de/libraries.textcomposer_TextComposerVoiceNotEncrypted_Day_0_de.png index 278db7b093..26cd7d10c5 100644 --- a/screenshots/de/libraries.textcomposer_TextComposerVoiceNotEncrypted_Day_0_de.png +++ b/screenshots/de/libraries.textcomposer_TextComposerVoiceNotEncrypted_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:83d2db1b20827de777296617927146c7142b21bf8efbfdf4aa40b21979b7aee7 -size 39694 +oid sha256:1656124036ba98fb559a863aba4d81fefcd9b4f52ab9cab852520dfa67bd5373 +size 38156 diff --git a/screenshots/de/libraries.troubleshoot.impl.history_PushHistoryView_Day_0_de.png b/screenshots/de/libraries.troubleshoot.impl.history_PushHistoryView_Day_0_de.png index cbf965f243..42d9d2a4c8 100644 --- a/screenshots/de/libraries.troubleshoot.impl.history_PushHistoryView_Day_0_de.png +++ b/screenshots/de/libraries.troubleshoot.impl.history_PushHistoryView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33421dbf0f3d14fee61a8cafc251ab1f84d81efcba9dfaadc07f80bb320aa607 -size 13136 +oid sha256:a73117e9f32d5c9cffafef70457d9941fb4d3c2aef864c518c68ce8ee9c370f6 +size 13163 diff --git a/screenshots/de/libraries.troubleshoot.impl.history_PushHistoryView_Day_1_de.png b/screenshots/de/libraries.troubleshoot.impl.history_PushHistoryView_Day_1_de.png index 0296d9668b..f5949c0da2 100644 --- a/screenshots/de/libraries.troubleshoot.impl.history_PushHistoryView_Day_1_de.png +++ b/screenshots/de/libraries.troubleshoot.impl.history_PushHistoryView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77b59c5016827c32d9c1e028050bfa662406cfe5df67d039a4b919c6e66a4b62 -size 45170 +oid sha256:639065890260286d563517940ef3d3f91e12d50724610893ebab3e84cb7c2d3b +size 45206 diff --git a/screenshots/de/libraries.troubleshoot.impl.history_PushHistoryView_Day_2_de.png b/screenshots/de/libraries.troubleshoot.impl.history_PushHistoryView_Day_2_de.png index 5c252f34f6..4ea936e138 100644 --- a/screenshots/de/libraries.troubleshoot.impl.history_PushHistoryView_Day_2_de.png +++ b/screenshots/de/libraries.troubleshoot.impl.history_PushHistoryView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:036931ca88ce36585a3377476821ad5e61221354f16adea22738b2588bdafe88 -size 23452 +oid sha256:479aca0d8555e2f186ec34eb9d64561a720482d52c0734cdca275b4fa13bad7a +size 23426 diff --git a/screenshots/de/libraries.troubleshoot.impl.history_PushHistoryView_Day_3_de.png b/screenshots/de/libraries.troubleshoot.impl.history_PushHistoryView_Day_3_de.png index f4c4da3dc5..c82e51c493 100644 --- a/screenshots/de/libraries.troubleshoot.impl.history_PushHistoryView_Day_3_de.png +++ b/screenshots/de/libraries.troubleshoot.impl.history_PushHistoryView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2fe5fd358af75454841c8648f64bdece8225e4327f5ec0d3e112d7b38da97fb1 -size 23951 +oid sha256:ae5eefdea8c937d368fe04245c8b6c51280e15394463f885489ba10e8d6e6321 +size 23925 diff --git a/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_0_de.png b/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_0_de.png index 6911e830d7..48833748f2 100644 --- a/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_0_de.png +++ b/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_0_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:292f03d853dc79b3cfdaf60383feb0bfb28621f2ca222f861e0b8545e949626d -size 35608 +oid sha256:928ac6c2eb31b854990ab0c58b3e7eb16976eb747b7424b7935242663200df52 +size 35619 diff --git a/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_1_de.png b/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_1_de.png index 4d4dc2092b..0f953c700c 100644 --- a/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_1_de.png +++ b/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_1_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e9c5c2385a62311805385152b8986df99cdc7c0cc6558d774a01487c6591471b -size 20399 +oid sha256:4f83b6f6ff8ab7bbbaffd39b21d9f9e4f830b520d82178ff6c6eb143b76bbf63 +size 20408 diff --git a/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_2_de.png b/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_2_de.png index 3b828cfa21..619cbb8f6e 100644 --- a/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_2_de.png +++ b/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_2_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4143aa80f5e04d2eb1a150bcf2ffd54eee46662e2d142bf4931b7ec2e877a913 -size 25803 +oid sha256:1b5c8a957d70fa4b21002ce7801c55478744bc7563bc7e1605588680f042349b +size 25815 diff --git a/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_3_de.png b/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_3_de.png index 00b513f519..3aab032174 100644 --- a/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_3_de.png +++ b/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_3_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e4994247b57d45a9129a3ff1ff7ffc4d325153a76024f8ab853f68494fabbb2b -size 32984 +oid sha256:91258609ab9996464c8e77ababcc4e4b11f98c8eb1484c36fe4e615d7773d96f +size 33005 diff --git a/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_4_de.png b/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_4_de.png index f338319996..0ee53f096d 100644 --- a/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_4_de.png +++ b/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_4_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:33e4eb4ceb6b959dc41aab717565b85fbd8837ed3318ffae42633d44ae9f0484 -size 25633 +oid sha256:18e255c2924df939b1889405d1c39f7a787b0465242c800f140b954aa7eb4518 +size 25644 diff --git a/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_5_de.png b/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_5_de.png index 4c9c562a35..d3d0c34a2a 100644 --- a/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_5_de.png +++ b/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_5_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7e2ae21ea5a0ba84a46f04660ccbf6f6a7badbb6cd3e5ce3d00f2ab75ff33e9 -size 41747 +oid sha256:3998239c48f1ea84b0a0e327f39168ed5f5e541cec2526a77e24a57715be5085 +size 41768 diff --git a/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_6_de.png b/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_6_de.png index 3830d4d901..4e0b11ff6e 100644 --- a/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_6_de.png +++ b/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_6_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:697be9acc98d36ebdc113f3f8ca05268c5fdf1ee19c9268abff5c37e284b19e4 -size 27473 +oid sha256:56fead606ebad9e08b1bd85a1c376881b5f88e30eaef776304faf5ca341d2868 +size 27461 diff --git a/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_7_de.png b/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_7_de.png index 652a34b261..541ba1165b 100644 --- a/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_7_de.png +++ b/screenshots/de/libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_7_de.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d992f1d26392afd09825656106a004f7b50b4a03896138730a15ccaf45296106 -size 26586 +oid sha256:cb9ab2813c21190074f80f1128897064267b6922fc6bb70ffcec9dcb487a3c02 +size 26580 diff --git a/screenshots/html/data.js b/screenshots/html/data.js index 2bdbeece94..42d529aec6 100644 --- a/screenshots/html/data.js +++ b/screenshots/html/data.js @@ -1,79 +1,80 @@ // Generated file, do not edit export const screenshots = [ ["en","en-dark","de",], -["features.preferences.impl.about_AboutView_Day_0_en","features.preferences.impl.about_AboutView_Night_0_en",20369,], +["features.preferences.impl.about_AboutView_Day_0_en","features.preferences.impl.about_AboutView_Night_0_en",20445,], ["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_0_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_0_en",0,], -["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_1_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_1_en",20369,], -["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_2_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_2_en",20369,], -["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_3_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_3_en",20369,], -["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_4_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_4_en",20369,], -["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_5_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_5_en",20369,], -["features.logout.impl_AccountDeactivationView_Day_0_en","features.logout.impl_AccountDeactivationView_Night_0_en",20369,], -["features.logout.impl_AccountDeactivationView_Day_1_en","features.logout.impl_AccountDeactivationView_Night_1_en",20369,], -["features.logout.impl_AccountDeactivationView_Day_2_en","features.logout.impl_AccountDeactivationView_Night_2_en",20369,], -["features.logout.impl_AccountDeactivationView_Day_3_en","features.logout.impl_AccountDeactivationView_Night_3_en",20369,], -["features.logout.impl_AccountDeactivationView_Day_4_en","features.logout.impl_AccountDeactivationView_Night_4_en",20369,], -["features.login.impl.accountprovider_AccountProviderOtherView_Day_0_en","features.login.impl.accountprovider_AccountProviderOtherView_Night_0_en",20369,], +["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_1_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_1_en",20445,], +["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_2_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_2_en",20445,], +["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_3_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_3_en",20445,], +["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_4_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_4_en",20445,], +["features.invite.impl.acceptdecline_AcceptDeclineInviteView_Day_5_en","features.invite.impl.acceptdecline_AcceptDeclineInviteView_Night_5_en",20445,], +["features.logout.impl_AccountDeactivationView_Day_0_en","features.logout.impl_AccountDeactivationView_Night_0_en",20445,], +["features.logout.impl_AccountDeactivationView_Day_1_en","features.logout.impl_AccountDeactivationView_Night_1_en",20445,], +["features.logout.impl_AccountDeactivationView_Day_2_en","features.logout.impl_AccountDeactivationView_Night_2_en",20445,], +["features.logout.impl_AccountDeactivationView_Day_3_en","features.logout.impl_AccountDeactivationView_Night_3_en",20445,], +["features.logout.impl_AccountDeactivationView_Day_4_en","features.logout.impl_AccountDeactivationView_Night_4_en",20445,], +["features.login.impl.accountprovider_AccountProviderOtherView_Day_0_en","features.login.impl.accountprovider_AccountProviderOtherView_Night_0_en",20445,], ["features.login.impl.accountprovider_AccountProviderView_Day_0_en","features.login.impl.accountprovider_AccountProviderView_Night_0_en",0,], ["features.login.impl.accountprovider_AccountProviderView_Day_1_en","features.login.impl.accountprovider_AccountProviderView_Night_1_en",0,], ["features.login.impl.accountprovider_AccountProviderView_Day_2_en","features.login.impl.accountprovider_AccountProviderView_Night_2_en",0,], ["features.login.impl.accountprovider_AccountProviderView_Day_3_en","features.login.impl.accountprovider_AccountProviderView_Night_3_en",0,], -["libraries.accountselect.impl_AccountSelectView_Day_0_en","libraries.accountselect.impl_AccountSelectView_Night_0_en",20369,], -["libraries.accountselect.impl_AccountSelectView_Day_1_en","libraries.accountselect.impl_AccountSelectView_Night_1_en",20369,], +["libraries.accountselect.impl_AccountSelectView_Day_0_en","libraries.accountselect.impl_AccountSelectView_Night_0_en",20445,], +["libraries.accountselect.impl_AccountSelectView_Day_1_en","libraries.accountselect.impl_AccountSelectView_Night_1_en",20445,], ["features.messages.impl.actionlist_ActionListViewContent_Day_0_en","features.messages.impl.actionlist_ActionListViewContent_Night_0_en",0,], -["features.messages.impl.actionlist_ActionListViewContent_Day_10_en","features.messages.impl.actionlist_ActionListViewContent_Night_10_en",20369,], -["features.messages.impl.actionlist_ActionListViewContent_Day_11_en","features.messages.impl.actionlist_ActionListViewContent_Night_11_en",20369,], -["features.messages.impl.actionlist_ActionListViewContent_Day_12_en","features.messages.impl.actionlist_ActionListViewContent_Night_12_en",20369,], +["features.messages.impl.actionlist_ActionListViewContent_Day_10_en","features.messages.impl.actionlist_ActionListViewContent_Night_10_en",20445,], +["features.messages.impl.actionlist_ActionListViewContent_Day_11_en","features.messages.impl.actionlist_ActionListViewContent_Night_11_en",20445,], +["features.messages.impl.actionlist_ActionListViewContent_Day_12_en","features.messages.impl.actionlist_ActionListViewContent_Night_12_en",20445,], ["features.messages.impl.actionlist_ActionListViewContent_Day_1_en","features.messages.impl.actionlist_ActionListViewContent_Night_1_en",0,], -["features.messages.impl.actionlist_ActionListViewContent_Day_2_en","features.messages.impl.actionlist_ActionListViewContent_Night_2_en",20369,], -["features.messages.impl.actionlist_ActionListViewContent_Day_3_en","features.messages.impl.actionlist_ActionListViewContent_Night_3_en",20369,], -["features.messages.impl.actionlist_ActionListViewContent_Day_4_en","features.messages.impl.actionlist_ActionListViewContent_Night_4_en",20369,], -["features.messages.impl.actionlist_ActionListViewContent_Day_5_en","features.messages.impl.actionlist_ActionListViewContent_Night_5_en",20369,], -["features.messages.impl.actionlist_ActionListViewContent_Day_6_en","features.messages.impl.actionlist_ActionListViewContent_Night_6_en",20369,], -["features.messages.impl.actionlist_ActionListViewContent_Day_7_en","features.messages.impl.actionlist_ActionListViewContent_Night_7_en",20369,], -["features.messages.impl.actionlist_ActionListViewContent_Day_8_en","features.messages.impl.actionlist_ActionListViewContent_Night_8_en",20369,], -["features.messages.impl.actionlist_ActionListViewContent_Day_9_en","features.messages.impl.actionlist_ActionListViewContent_Night_9_en",20369,], -["features.createroom.impl.addpeople_AddPeopleView_Day_0_en","features.createroom.impl.addpeople_AddPeopleView_Night_0_en",20369,], -["features.createroom.impl.addpeople_AddPeopleView_Day_1_en","features.createroom.impl.addpeople_AddPeopleView_Night_1_en",20369,], -["features.createroom.impl.addpeople_AddPeopleView_Day_2_en","features.createroom.impl.addpeople_AddPeopleView_Night_2_en",20369,], -["features.createroom.impl.addpeople_AddPeopleView_Day_3_en","features.createroom.impl.addpeople_AddPeopleView_Night_3_en",20369,], -["features.preferences.impl.advanced_AdvancedSettingsViewDark_0_en","",20369,], -["features.preferences.impl.advanced_AdvancedSettingsViewDark_1_en","",20369,], -["features.preferences.impl.advanced_AdvancedSettingsViewDark_2_en","",20369,], -["features.preferences.impl.advanced_AdvancedSettingsViewDark_3_en","",20369,], -["features.preferences.impl.advanced_AdvancedSettingsViewDark_4_en","",20369,], -["features.preferences.impl.advanced_AdvancedSettingsViewDark_5_en","",20369,], -["features.preferences.impl.advanced_AdvancedSettingsViewDark_6_en","",20369,], -["features.preferences.impl.advanced_AdvancedSettingsViewDark_7_en","",20369,], -["features.preferences.impl.advanced_AdvancedSettingsViewDark_8_en","",20369,], -["features.preferences.impl.advanced_AdvancedSettingsViewLight_0_en","",20369,], -["features.preferences.impl.advanced_AdvancedSettingsViewLight_1_en","",20369,], -["features.preferences.impl.advanced_AdvancedSettingsViewLight_2_en","",20369,], -["features.preferences.impl.advanced_AdvancedSettingsViewLight_3_en","",20369,], -["features.preferences.impl.advanced_AdvancedSettingsViewLight_4_en","",20369,], -["features.preferences.impl.advanced_AdvancedSettingsViewLight_5_en","",20369,], -["features.preferences.impl.advanced_AdvancedSettingsViewLight_6_en","",20369,], -["features.preferences.impl.advanced_AdvancedSettingsViewLight_7_en","",20369,], -["features.preferences.impl.advanced_AdvancedSettingsViewLight_8_en","",20369,], -["libraries.designsystem.components.dialogs_AlertDialogContent_Dialogs_en","",20369,], -["libraries.designsystem.components.dialogs_AlertDialog_Day_0_en","libraries.designsystem.components.dialogs_AlertDialog_Night_0_en",20369,], -["features.analytics.impl_AnalyticsOptInView_Day_0_en","features.analytics.impl_AnalyticsOptInView_Night_0_en",20369,], -["features.analytics.impl_AnalyticsOptInView_Day_1_en","features.analytics.impl_AnalyticsOptInView_Night_1_en",20369,], -["features.analytics.api.preferences_AnalyticsPreferencesView_Day_0_en","features.analytics.api.preferences_AnalyticsPreferencesView_Night_0_en",20369,], -["features.analytics.api.preferences_AnalyticsPreferencesView_Day_1_en","features.analytics.api.preferences_AnalyticsPreferencesView_Night_1_en",20369,], -["features.preferences.impl.analytics_AnalyticsSettingsView_Day_0_en","features.preferences.impl.analytics_AnalyticsSettingsView_Night_0_en",20369,], +["features.messages.impl.actionlist_ActionListViewContent_Day_2_en","features.messages.impl.actionlist_ActionListViewContent_Night_2_en",20445,], +["features.messages.impl.actionlist_ActionListViewContent_Day_3_en","features.messages.impl.actionlist_ActionListViewContent_Night_3_en",20445,], +["features.messages.impl.actionlist_ActionListViewContent_Day_4_en","features.messages.impl.actionlist_ActionListViewContent_Night_4_en",20445,], +["features.messages.impl.actionlist_ActionListViewContent_Day_5_en","features.messages.impl.actionlist_ActionListViewContent_Night_5_en",20445,], +["features.messages.impl.actionlist_ActionListViewContent_Day_6_en","features.messages.impl.actionlist_ActionListViewContent_Night_6_en",20445,], +["features.messages.impl.actionlist_ActionListViewContent_Day_7_en","features.messages.impl.actionlist_ActionListViewContent_Night_7_en",20445,], +["features.messages.impl.actionlist_ActionListViewContent_Day_8_en","features.messages.impl.actionlist_ActionListViewContent_Night_8_en",20445,], +["features.messages.impl.actionlist_ActionListViewContent_Day_9_en","features.messages.impl.actionlist_ActionListViewContent_Night_9_en",20445,], +["features.createroom.impl.addpeople_AddPeopleView_Day_0_en","features.createroom.impl.addpeople_AddPeopleView_Night_0_en",20445,], +["features.createroom.impl.addpeople_AddPeopleView_Day_1_en","features.createroom.impl.addpeople_AddPeopleView_Night_1_en",20445,], +["features.createroom.impl.addpeople_AddPeopleView_Day_2_en","features.createroom.impl.addpeople_AddPeopleView_Night_2_en",20445,], +["features.createroom.impl.addpeople_AddPeopleView_Day_3_en","features.createroom.impl.addpeople_AddPeopleView_Night_3_en",20445,], +["features.preferences.impl.advanced_AdvancedSettingsViewDark_0_en","",20445,], +["features.preferences.impl.advanced_AdvancedSettingsViewDark_1_en","",20445,], +["features.preferences.impl.advanced_AdvancedSettingsViewDark_2_en","",20445,], +["features.preferences.impl.advanced_AdvancedSettingsViewDark_3_en","",20445,], +["features.preferences.impl.advanced_AdvancedSettingsViewDark_4_en","",20445,], +["features.preferences.impl.advanced_AdvancedSettingsViewDark_5_en","",20445,], +["features.preferences.impl.advanced_AdvancedSettingsViewDark_6_en","",20445,], +["features.preferences.impl.advanced_AdvancedSettingsViewDark_7_en","",20445,], +["features.preferences.impl.advanced_AdvancedSettingsViewDark_8_en","",20445,], +["features.preferences.impl.advanced_AdvancedSettingsViewLight_0_en","",20445,], +["features.preferences.impl.advanced_AdvancedSettingsViewLight_1_en","",20445,], +["features.preferences.impl.advanced_AdvancedSettingsViewLight_2_en","",20445,], +["features.preferences.impl.advanced_AdvancedSettingsViewLight_3_en","",20445,], +["features.preferences.impl.advanced_AdvancedSettingsViewLight_4_en","",20445,], +["features.preferences.impl.advanced_AdvancedSettingsViewLight_5_en","",20445,], +["features.preferences.impl.advanced_AdvancedSettingsViewLight_6_en","",20445,], +["features.preferences.impl.advanced_AdvancedSettingsViewLight_7_en","",20445,], +["features.preferences.impl.advanced_AdvancedSettingsViewLight_8_en","",20445,], +["libraries.designsystem.components.dialogs_AlertDialogContent_Dialogs_en","",20445,], +["libraries.designsystem.components.dialogs_AlertDialog_Day_0_en","libraries.designsystem.components.dialogs_AlertDialog_Night_0_en",20445,], +["libraries.designsystem.theme.components_AllIcons_Icons_en","",0,], +["features.analytics.impl_AnalyticsOptInView_Day_0_en","features.analytics.impl_AnalyticsOptInView_Night_0_en",20445,], +["features.analytics.impl_AnalyticsOptInView_Day_1_en","features.analytics.impl_AnalyticsOptInView_Night_1_en",20445,], +["features.analytics.api.preferences_AnalyticsPreferencesView_Day_0_en","features.analytics.api.preferences_AnalyticsPreferencesView_Night_0_en",20445,], +["features.analytics.api.preferences_AnalyticsPreferencesView_Day_1_en","features.analytics.api.preferences_AnalyticsPreferencesView_Night_1_en",20445,], +["features.preferences.impl.analytics_AnalyticsSettingsView_Day_0_en","features.preferences.impl.analytics_AnalyticsSettingsView_Night_0_en",20445,], ["libraries.designsystem.components_Announcement_Day_0_en","libraries.designsystem.components_Announcement_Night_0_en",0,], -["services.apperror.impl_AppErrorView_Day_0_en","services.apperror.impl_AppErrorView_Night_0_en",20369,], +["services.apperror.impl_AppErrorView_Day_0_en","services.apperror.impl_AppErrorView_Night_0_en",20445,], ["libraries.designsystem.components.async_AsyncActionView_Day_0_en","libraries.designsystem.components.async_AsyncActionView_Night_0_en",0,], -["libraries.designsystem.components.async_AsyncActionView_Day_1_en","libraries.designsystem.components.async_AsyncActionView_Night_1_en",20369,], +["libraries.designsystem.components.async_AsyncActionView_Day_1_en","libraries.designsystem.components.async_AsyncActionView_Night_1_en",20445,], ["libraries.designsystem.components.async_AsyncActionView_Day_2_en","libraries.designsystem.components.async_AsyncActionView_Night_2_en",0,], -["libraries.designsystem.components.async_AsyncActionView_Day_3_en","libraries.designsystem.components.async_AsyncActionView_Night_3_en",20369,], +["libraries.designsystem.components.async_AsyncActionView_Day_3_en","libraries.designsystem.components.async_AsyncActionView_Night_3_en",20445,], ["libraries.designsystem.components.async_AsyncActionView_Day_4_en","libraries.designsystem.components.async_AsyncActionView_Night_4_en",0,], -["libraries.designsystem.components.async_AsyncFailure_Day_0_en","libraries.designsystem.components.async_AsyncFailure_Night_0_en",20369,], +["libraries.designsystem.components.async_AsyncFailure_Day_0_en","libraries.designsystem.components.async_AsyncFailure_Night_0_en",20445,], ["libraries.designsystem.components.async_AsyncIndicatorFailure_Day_0_en","libraries.designsystem.components.async_AsyncIndicatorFailure_Night_0_en",0,], ["libraries.designsystem.components.async_AsyncIndicatorLoading_Day_0_en","libraries.designsystem.components.async_AsyncIndicatorLoading_Night_0_en",0,], ["libraries.designsystem.components.async_AsyncLoading_Day_0_en","libraries.designsystem.components.async_AsyncLoading_Night_0_en",0,], -["features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Day_0_en","features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Night_0_en",20369,], +["features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Day_0_en","features.messages.impl.messagecomposer_AttachmentSourcePickerMenu_Night_0_en",20445,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_0_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_0_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_1_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_1_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_2_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_2_en",0,], @@ -83,19 +84,19 @@ export const screenshots = [ ["libraries.matrix.ui.components_AttachmentThumbnail_Day_6_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_6_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_7_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_7_en",0,], ["libraries.matrix.ui.components_AttachmentThumbnail_Day_8_en","libraries.matrix.ui.components_AttachmentThumbnail_Night_8_en",0,], -["features.messages.impl.attachments.preview_AttachmentsView_0_en","",20369,], -["features.messages.impl.attachments.preview_AttachmentsView_1_en","",20369,], -["features.messages.impl.attachments.preview_AttachmentsView_2_en","",20369,], -["features.messages.impl.attachments.preview_AttachmentsView_3_en","",20369,], -["features.messages.impl.attachments.preview_AttachmentsView_4_en","",20369,], -["features.messages.impl.attachments.preview_AttachmentsView_5_en","",20369,], -["features.messages.impl.attachments.preview_AttachmentsView_6_en","",20369,], -["features.messages.impl.attachments.preview_AttachmentsView_7_en","",20369,], -["features.messages.impl.attachments.preview_AttachmentsView_8_en","",20369,], +["features.messages.impl.attachments.preview_AttachmentsPreviewView_0_en","",20445,], +["features.messages.impl.attachments.preview_AttachmentsPreviewView_1_en","",20445,], +["features.messages.impl.attachments.preview_AttachmentsPreviewView_2_en","",20445,], +["features.messages.impl.attachments.preview_AttachmentsPreviewView_3_en","",20445,], +["features.messages.impl.attachments.preview_AttachmentsPreviewView_4_en","",20445,], +["features.messages.impl.attachments.preview_AttachmentsPreviewView_5_en","",20445,], +["features.messages.impl.attachments.preview_AttachmentsPreviewView_6_en","",20445,], +["features.messages.impl.attachments.preview_AttachmentsPreviewView_7_en","",20445,], +["features.messages.impl.attachments.preview_AttachmentsPreviewView_8_en","",20445,], ["libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_0_en","libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_0_en",0,], ["libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_1_en","libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_1_en",0,], ["libraries.mediaviewer.impl.gallery.ui_AudioItemView_Day_2_en","libraries.mediaviewer.impl.gallery.ui_AudioItemView_Night_2_en",0,], -["libraries.matrix.ui.components_AvatarActionBottomSheet_Day_0_en","libraries.matrix.ui.components_AvatarActionBottomSheet_Night_0_en",20369,], +["libraries.matrix.ui.components_AvatarActionBottomSheet_Day_0_en","libraries.matrix.ui.components_AvatarActionBottomSheet_Night_0_en",20445,], ["libraries.designsystem.components.avatar.internal_AvatarCluster_Avatars_en","",0,], ["libraries.designsystem.components.avatar_AvatarRowLastOnTopRtl_Day_0_en","libraries.designsystem.components.avatar_AvatarRowLastOnTopRtl_Night_0_en",0,], ["libraries.designsystem.components.avatar_AvatarRowLastOnTopRtl_Day_1_en","libraries.designsystem.components.avatar_AvatarRowLastOnTopRtl_Night_1_en",0,], @@ -117,272 +118,167 @@ export const screenshots = [ ["libraries.designsystem.components.avatar_AvatarRow_Day_2_en","libraries.designsystem.components.avatar_AvatarRow_Night_2_en",0,], ["libraries.designsystem.components.avatar_AvatarRow_Day_3_en","libraries.designsystem.components.avatar_AvatarRow_Night_3_en",0,], ["libraries.designsystem.components.avatar_AvatarRow_Day_4_en","libraries.designsystem.components.avatar_AvatarRow_Night_4_en",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_0_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_100_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_101_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_102_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_103_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_104_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_105_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_106_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_107_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_108_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_109_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_10_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_110_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_111_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_112_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_113_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_114_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_115_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_116_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_117_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_118_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_119_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_11_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_12_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_13_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_14_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_15_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_16_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_17_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_18_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_19_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_1_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_20_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_21_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_22_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_23_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_24_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_25_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_26_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_27_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_28_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_29_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_2_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_30_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_31_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_32_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_33_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_34_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_35_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_36_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_37_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_38_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_39_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_3_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_40_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_41_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_42_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_43_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_44_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_45_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_46_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_47_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_48_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_49_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_4_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_50_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_51_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_52_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_53_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_54_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_55_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_56_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_57_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_58_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_59_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_5_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_60_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_61_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_62_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_63_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_64_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_65_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_66_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_67_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_68_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_69_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_6_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_70_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_71_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_72_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_73_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_74_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_75_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_76_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_77_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_78_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_79_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_7_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_80_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_81_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_82_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_83_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_84_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_85_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_86_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_87_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_88_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_89_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_8_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_90_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_91_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_92_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_93_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_94_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_95_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_96_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_97_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_98_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_99_en","",0,], -["libraries.designsystem.components.avatar_Avatar_Avatars_9_en","",0,], +["libraries.designsystem.components.avatar_Avatar_Avatars_en","",0,], ["libraries.designsystem.components.button_BackButton_Buttons_en","",0,], ["libraries.designsystem.modifiers_BackgroundVerticalGradientDisabled_Day_0_en","libraries.designsystem.modifiers_BackgroundVerticalGradientDisabled_Night_0_en",0,], -["libraries.designsystem.modifiers_BackgroundVerticalGradientEnterprise_Day_0_en","libraries.designsystem.modifiers_BackgroundVerticalGradientEnterprise_Night_0_en",0,], ["libraries.designsystem.modifiers_BackgroundVerticalGradient_Day_0_en","libraries.designsystem.modifiers_BackgroundVerticalGradient_Night_0_en",0,], ["libraries.designsystem.components_Badge_Day_0_en","libraries.designsystem.components_Badge_Night_0_en",0,], -["features.home.impl.components_BatteryOptimizationBanner_Day_0_en","features.home.impl.components_BatteryOptimizationBanner_Night_0_en",20369,], +["features.home.impl.components_BatteryOptimizationBanner_Day_0_en","features.home.impl.components_BatteryOptimizationBanner_Night_0_en",20445,], ["libraries.designsystem.atomic.atoms_BetaLabel_Day_0_en","libraries.designsystem.atomic.atoms_BetaLabel_Night_0_en",0,], ["libraries.designsystem.components_BigIcon_Day_0_en","libraries.designsystem.components_BigIcon_Night_0_en",0,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_0_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_0_en",20369,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_1_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_1_en",20369,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_2_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_2_en",20369,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_3_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_3_en",20369,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en",20369,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en",20369,], -["features.preferences.impl.blockedusers_BlockedUsersView_Day_6_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_6_en",20369,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_0_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_0_en",20445,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_1_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_1_en",20445,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_2_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_2_en",20445,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_3_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_3_en",20445,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_4_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_4_en",20445,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_5_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_5_en",20445,], +["features.preferences.impl.blockedusers_BlockedUsersView_Day_6_en","features.preferences.impl.blockedusers_BlockedUsersView_Night_6_en",20445,], ["libraries.designsystem.theme.components_BottomSheetDragHandle_Day_0_en","libraries.designsystem.theme.components_BottomSheetDragHandle_Night_0_en",0,], -["features.rageshake.impl.bugreport_BugReportView_Day_0_en","features.rageshake.impl.bugreport_BugReportView_Night_0_en",20369,], -["features.rageshake.impl.bugreport_BugReportView_Day_1_en","features.rageshake.impl.bugreport_BugReportView_Night_1_en",20369,], -["features.rageshake.impl.bugreport_BugReportView_Day_2_en","features.rageshake.impl.bugreport_BugReportView_Night_2_en",20369,], -["features.rageshake.impl.bugreport_BugReportView_Day_3_en","features.rageshake.impl.bugreport_BugReportView_Night_3_en",20369,], -["features.rageshake.impl.bugreport_BugReportView_Day_4_en","features.rageshake.impl.bugreport_BugReportView_Night_4_en",20369,], +["features.rageshake.impl.bugreport_BugReportViewDay_0_en","",20445,], +["features.rageshake.impl.bugreport_BugReportViewDay_1_en","",20445,], +["features.rageshake.impl.bugreport_BugReportViewDay_2_en","",20445,], +["features.rageshake.impl.bugreport_BugReportViewDay_3_en","",20445,], +["features.rageshake.impl.bugreport_BugReportViewDay_4_en","",20445,], +["features.rageshake.impl.bugreport_BugReportViewNight_0_en","",0,], +["features.rageshake.impl.bugreport_BugReportViewNight_1_en","",0,], +["features.rageshake.impl.bugreport_BugReportViewNight_2_en","",0,], +["features.rageshake.impl.bugreport_BugReportViewNight_3_en","",0,], +["features.rageshake.impl.bugreport_BugReportViewNight_4_en","",0,], ["libraries.designsystem.atomic.molecules_ButtonColumnMolecule_Day_0_en","libraries.designsystem.atomic.molecules_ButtonColumnMolecule_Night_0_en",0,], ["libraries.designsystem.atomic.molecules_ButtonRowMolecule_Day_0_en","libraries.designsystem.atomic.molecules_ButtonRowMolecule_Night_0_en",0,], ["features.messages.impl.timeline.components_CallMenuItem_Day_0_en","features.messages.impl.timeline.components_CallMenuItem_Night_0_en",0,], ["features.messages.impl.timeline.components_CallMenuItem_Day_1_en","features.messages.impl.timeline.components_CallMenuItem_Night_1_en",0,], -["features.messages.impl.timeline.components_CallMenuItem_Day_2_en","features.messages.impl.timeline.components_CallMenuItem_Night_2_en",20369,], -["features.messages.impl.timeline.components_CallMenuItem_Day_3_en","features.messages.impl.timeline.components_CallMenuItem_Night_3_en",20369,], +["features.messages.impl.timeline.components_CallMenuItem_Day_2_en","features.messages.impl.timeline.components_CallMenuItem_Night_2_en",20445,], +["features.messages.impl.timeline.components_CallMenuItem_Day_3_en","features.messages.impl.timeline.components_CallMenuItem_Night_3_en",20445,], ["features.messages.impl.timeline.components_CallMenuItem_Day_4_en","features.messages.impl.timeline.components_CallMenuItem_Night_4_en",0,], ["features.messages.impl.timeline.components_CallMenuItem_Day_5_en","features.messages.impl.timeline.components_CallMenuItem_Night_5_en",0,], ["features.call.impl.ui_CallScreenView_Day_0_en","features.call.impl.ui_CallScreenView_Night_0_en",0,], -["features.call.impl.ui_CallScreenView_Day_1_en","features.call.impl.ui_CallScreenView_Night_1_en",20369,], -["features.call.impl.ui_CallScreenView_Day_2_en","features.call.impl.ui_CallScreenView_Night_2_en",20369,], -["features.call.impl.ui_CallScreenView_Day_3_en","features.call.impl.ui_CallScreenView_Night_3_en",20369,], -["libraries.textcomposer_CaptionWarningBottomSheet_Day_0_en","libraries.textcomposer_CaptionWarningBottomSheet_Night_0_en",20369,], -["features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_0_en","features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Night_0_en",20369,], -["features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_1_en","features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Night_1_en",20369,], -["features.changeroommemberroles.impl_ChangeRolesView_Day_0_en","features.changeroommemberroles.impl_ChangeRolesView_Night_0_en",20369,], -["features.changeroommemberroles.impl_ChangeRolesView_Day_10_en","features.changeroommemberroles.impl_ChangeRolesView_Night_10_en",20369,], -["features.changeroommemberroles.impl_ChangeRolesView_Day_11_en","features.changeroommemberroles.impl_ChangeRolesView_Night_11_en",20369,], -["features.changeroommemberroles.impl_ChangeRolesView_Day_12_en","features.changeroommemberroles.impl_ChangeRolesView_Night_12_en",20369,], -["features.changeroommemberroles.impl_ChangeRolesView_Day_1_en","features.changeroommemberroles.impl_ChangeRolesView_Night_1_en",20369,], -["features.changeroommemberroles.impl_ChangeRolesView_Day_2_en","features.changeroommemberroles.impl_ChangeRolesView_Night_2_en",20369,], -["features.changeroommemberroles.impl_ChangeRolesView_Day_3_en","features.changeroommemberroles.impl_ChangeRolesView_Night_3_en",20369,], -["features.changeroommemberroles.impl_ChangeRolesView_Day_4_en","features.changeroommemberroles.impl_ChangeRolesView_Night_4_en",20369,], -["features.changeroommemberroles.impl_ChangeRolesView_Day_5_en","features.changeroommemberroles.impl_ChangeRolesView_Night_5_en",0,], -["features.changeroommemberroles.impl_ChangeRolesView_Day_6_en","features.changeroommemberroles.impl_ChangeRolesView_Night_6_en",20369,], -["features.changeroommemberroles.impl_ChangeRolesView_Day_7_en","features.changeroommemberroles.impl_ChangeRolesView_Night_7_en",20369,], -["features.changeroommemberroles.impl_ChangeRolesView_Day_8_en","features.changeroommemberroles.impl_ChangeRolesView_Night_8_en",20369,], -["features.changeroommemberroles.impl_ChangeRolesView_Day_9_en","features.changeroommemberroles.impl_ChangeRolesView_Night_9_en",20369,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_0_en",20369,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_1_en",20369,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_2_en",20369,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_3_en",20369,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_4_en",20369,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_5_en",20369,], -["features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions.permissions_ChangeRoomPermissionsView_Night_6_en",20369,], +["features.call.impl.ui_CallScreenView_Day_1_en","features.call.impl.ui_CallScreenView_Night_1_en",20445,], +["features.call.impl.ui_CallScreenView_Day_2_en","features.call.impl.ui_CallScreenView_Night_2_en",20445,], +["features.call.impl.ui_CallScreenView_Day_3_en","features.call.impl.ui_CallScreenView_Night_3_en",20445,], +["libraries.textcomposer_CaptionWarningBottomSheet_Day_0_en","libraries.textcomposer_CaptionWarningBottomSheet_Night_0_en",20445,], +["features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_0_en","features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Night_0_en",20445,], +["features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Day_1_en","features.login.impl.screens.changeaccountprovider_ChangeAccountProviderView_Night_1_en",20445,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_0_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_0_en",20445,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_10_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_10_en",20445,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_11_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_11_en",20445,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_12_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_12_en",20445,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_13_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_13_en",20445,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_1_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_1_en",20445,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_2_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_2_en",20445,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_3_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_3_en",20445,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_4_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_4_en",20445,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_5_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_5_en",0,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_6_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_6_en",20445,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_7_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_7_en",20445,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_8_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_8_en",20445,], +["features.rolesandpermissions.impl.roles_ChangeRolesView_Day_9_en","features.rolesandpermissions.impl.roles_ChangeRolesView_Night_9_en",20445,], +["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_0_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_0_en",20445,], +["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_1_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_1_en",20445,], +["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_2_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_2_en",20445,], +["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_3_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_3_en",20445,], +["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_4_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_4_en",20445,], +["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_5_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_5_en",20445,], +["features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Day_6_en","features.rolesandpermissions.impl.permissions_ChangeRoomPermissionsView_Night_6_en",0,], ["features.login.impl.changeserver_ChangeServerView_Day_0_en","features.login.impl.changeserver_ChangeServerView_Night_0_en",0,], -["features.login.impl.changeserver_ChangeServerView_Day_1_en","features.login.impl.changeserver_ChangeServerView_Night_1_en",20369,], -["features.login.impl.changeserver_ChangeServerView_Day_2_en","features.login.impl.changeserver_ChangeServerView_Night_2_en",20369,], -["features.login.impl.changeserver_ChangeServerView_Day_3_en","features.login.impl.changeserver_ChangeServerView_Night_3_en",20369,], -["features.login.impl.changeserver_ChangeServerView_Day_4_en","features.login.impl.changeserver_ChangeServerView_Night_4_en",20369,], +["features.login.impl.changeserver_ChangeServerView_Day_1_en","features.login.impl.changeserver_ChangeServerView_Night_1_en",20445,], +["features.login.impl.changeserver_ChangeServerView_Day_2_en","features.login.impl.changeserver_ChangeServerView_Night_2_en",20445,], +["features.login.impl.changeserver_ChangeServerView_Day_3_en","features.login.impl.changeserver_ChangeServerView_Night_3_en",20445,], +["features.login.impl.changeserver_ChangeServerView_Day_4_en","features.login.impl.changeserver_ChangeServerView_Night_4_en",20445,], +["features.login.impl.changeserver_ChangeServerView_Day_5_en","features.login.impl.changeserver_ChangeServerView_Night_5_en",20445,], ["libraries.matrix.ui.components_CheckableResolvedUserRow_en","",0,], -["libraries.matrix.ui.components_CheckableUnresolvedUserRow_en","",20369,], +["libraries.matrix.ui.components_CheckableUnresolvedUserRow_en","",20445,], ["libraries.designsystem.theme.components_Checkboxes_Toggles_en","",0,], -["features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_0_en","features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_0_en",20369,], -["features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_1_en","features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_1_en",20369,], -["features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_2_en","features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_2_en",20369,], -["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_0_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_0_en",20369,], -["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_1_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_1_en",20369,], -["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_2_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_2_en",20369,], -["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_3_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_3_en",20369,], +["features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_0_en","features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_0_en",20445,], +["features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_1_en","features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_1_en",20445,], +["features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Day_2_en","features.login.impl.screens.chooseaccountprovider_ChooseAccountProviderView_Night_2_en",20445,], +["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_0_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_0_en",20445,], +["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_1_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_1_en",20445,], +["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_2_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_2_en",20445,], +["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_3_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_3_en",20445,], +["features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Day_4_en","features.ftue.impl.sessionverification.choosemode_ChooseSelfVerificationModeView_Night_4_en",20445,], ["libraries.designsystem.theme.components_CircularProgressIndicator_Progress_Indicators_en","",0,], ["libraries.designsystem.components_ClickableLinkText_Text_en","",0,], ["libraries.designsystem.theme_ColorAliases_Day_0_en","libraries.designsystem.theme_ColorAliases_Night_0_en",0,], -["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_0_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_0_en",20369,], -["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_1_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_1_en",20369,], -["libraries.textcomposer_ComposerModeView_Day_0_en","libraries.textcomposer_ComposerModeView_Night_0_en",20369,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_0_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_0_en",20445,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_1_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_1_en",20445,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_2_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_2_en",20445,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_3_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_3_en",20445,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_4_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_4_en",20445,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_5_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_5_en",20445,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_6_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_6_en",20445,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_7_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_7_en",20445,], +["libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Day_8_en","libraries.designsystem.atomic.molecules_ComposerAlertMolecule_Night_8_en",20445,], +["libraries.textcomposer_ComposerModeView_Day_0_en","libraries.textcomposer_ComposerModeView_Night_0_en",20445,], ["libraries.textcomposer_ComposerModeView_Day_1_en","libraries.textcomposer_ComposerModeView_Night_1_en",0,], ["libraries.textcomposer_ComposerModeView_Day_2_en","libraries.textcomposer_ComposerModeView_Night_2_en",0,], ["libraries.textcomposer_ComposerModeView_Day_3_en","libraries.textcomposer_ComposerModeView_Night_3_en",0,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_0_en","",20369,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_1_en","",20369,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_2_en","",20369,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_3_en","",20369,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_4_en","",20369,], -["features.createroom.impl.configureroom_ConfigureRoomViewDark_5_en","",20369,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_0_en","",20369,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_1_en","",20369,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_2_en","",20369,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_3_en","",20369,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_4_en","",20369,], -["features.createroom.impl.configureroom_ConfigureRoomViewLight_5_en","",20369,], -["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_0_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_0_en",20369,], -["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_1_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_1_en",20369,], -["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_2_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_2_en",20369,], -["features.home.impl.components_ConfirmRecoveryKeyBanner_Day_0_en","features.home.impl.components_ConfirmRecoveryKeyBanner_Night_0_en",20369,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_0_en","",20445,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_1_en","",20445,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_2_en","",20445,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_3_en","",20445,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_4_en","",20445,], +["features.createroom.impl.configureroom_ConfigureRoomViewDark_5_en","",20445,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_0_en","",20445,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_1_en","",20445,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_2_en","",20445,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_3_en","",20445,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_4_en","",20445,], +["features.createroom.impl.configureroom_ConfigureRoomViewLight_5_en","",20445,], +["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_0_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_0_en",20445,], +["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_1_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_1_en",20445,], +["features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Day_2_en","features.login.impl.screens.confirmaccountprovider_ConfirmAccountProviderView_Night_2_en",20445,], +["features.home.impl.components_ConfirmRecoveryKeyBanner_Day_0_en","features.home.impl.components_ConfirmRecoveryKeyBanner_Night_0_en",20445,], ["libraries.designsystem.components.dialogs_ConfirmationDialogContent_Dialogs_en","",0,], ["libraries.designsystem.components.dialogs_ConfirmationDialog_Day_0_en","libraries.designsystem.components.dialogs_ConfirmationDialog_Night_0_en",0,], -["features.networkmonitor.api.ui_ConnectivityIndicatorView_Day_0_en","features.networkmonitor.api.ui_ConnectivityIndicatorView_Night_0_en",0,], +["features.networkmonitor.api.ui_ConnectivityIndicator_Day_0_en","features.networkmonitor.api.ui_ConnectivityIndicator_Night_0_en",0,], ["libraries.designsystem.atomic.atoms_CounterAtom_Day_0_en","libraries.designsystem.atomic.atoms_CounterAtom_Night_0_en",0,], -["features.rageshake.api.crash_CrashDetectionView_Day_0_en","features.rageshake.api.crash_CrashDetectionView_Night_0_en",20369,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_0_en","features.login.impl.screens.createaccount_CreateAccountView_Night_0_en",20369,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_1_en","features.login.impl.screens.createaccount_CreateAccountView_Night_1_en",20369,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_2_en","features.login.impl.screens.createaccount_CreateAccountView_Night_2_en",20369,], -["features.login.impl.screens.createaccount_CreateAccountView_Day_3_en","features.login.impl.screens.createaccount_CreateAccountView_Night_3_en",20369,], -["libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_0_en","libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Night_0_en",20369,], -["libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_1_en","libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Night_1_en",20369,], -["features.poll.impl.create_CreatePollView_Day_0_en","features.poll.impl.create_CreatePollView_Night_0_en",20369,], -["features.poll.impl.create_CreatePollView_Day_1_en","features.poll.impl.create_CreatePollView_Night_1_en",20369,], -["features.poll.impl.create_CreatePollView_Day_2_en","features.poll.impl.create_CreatePollView_Night_2_en",20369,], -["features.poll.impl.create_CreatePollView_Day_3_en","features.poll.impl.create_CreatePollView_Night_3_en",20369,], -["features.poll.impl.create_CreatePollView_Day_4_en","features.poll.impl.create_CreatePollView_Night_4_en",20369,], -["features.poll.impl.create_CreatePollView_Day_5_en","features.poll.impl.create_CreatePollView_Night_5_en",20369,], -["features.poll.impl.create_CreatePollView_Day_6_en","features.poll.impl.create_CreatePollView_Night_6_en",20369,], -["features.poll.impl.create_CreatePollView_Day_7_en","features.poll.impl.create_CreatePollView_Night_7_en",20369,], -["libraries.dateformatter.impl.previews_DateFormatterModeView_0_en","",20369,], -["libraries.dateformatter.impl.previews_DateFormatterModeView_1_en","",20369,], -["libraries.dateformatter.impl.previews_DateFormatterModeView_2_en","",20369,], -["libraries.dateformatter.impl.previews_DateFormatterModeView_3_en","",20369,], -["libraries.dateformatter.impl.previews_DateFormatterModeView_4_en","",20369,], +["features.rageshake.api.crash_CrashDetectionView_Day_0_en","features.rageshake.api.crash_CrashDetectionView_Night_0_en",20445,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_0_en","features.login.impl.screens.createaccount_CreateAccountView_Night_0_en",20445,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_1_en","features.login.impl.screens.createaccount_CreateAccountView_Night_1_en",20445,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_2_en","features.login.impl.screens.createaccount_CreateAccountView_Night_2_en",20445,], +["features.login.impl.screens.createaccount_CreateAccountView_Day_3_en","features.login.impl.screens.createaccount_CreateAccountView_Night_3_en",20445,], +["libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_0_en","libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Night_0_en",20445,], +["libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Day_1_en","libraries.matrix.ui.components_CreateDmConfirmationBottomSheet_Night_1_en",20445,], +["features.poll.impl.create_CreatePollView_Day_0_en","features.poll.impl.create_CreatePollView_Night_0_en",20445,], +["features.poll.impl.create_CreatePollView_Day_1_en","features.poll.impl.create_CreatePollView_Night_1_en",20445,], +["features.poll.impl.create_CreatePollView_Day_2_en","features.poll.impl.create_CreatePollView_Night_2_en",20445,], +["features.poll.impl.create_CreatePollView_Day_3_en","features.poll.impl.create_CreatePollView_Night_3_en",20445,], +["features.poll.impl.create_CreatePollView_Day_4_en","features.poll.impl.create_CreatePollView_Night_4_en",20445,], +["features.poll.impl.create_CreatePollView_Day_5_en","features.poll.impl.create_CreatePollView_Night_5_en",20445,], +["features.poll.impl.create_CreatePollView_Day_6_en","features.poll.impl.create_CreatePollView_Night_6_en",20445,], +["features.poll.impl.create_CreatePollView_Day_7_en","features.poll.impl.create_CreatePollView_Night_7_en",20445,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_0_en","",20445,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_1_en","",20445,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_2_en","",20445,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_3_en","",20445,], +["libraries.dateformatter.impl.previews_DateFormatterModeView_4_en","",20445,], ["libraries.mediaviewer.impl.gallery.ui_DateItemView_Day_0_en","libraries.mediaviewer.impl.gallery.ui_DateItemView_Night_0_en",0,], ["libraries.mediaviewer.impl.gallery.ui_DateItemView_Day_1_en","libraries.mediaviewer.impl.gallery.ui_DateItemView_Night_1_en",0,], -["libraries.designsystem.theme.components.previews_DatePickerDark_DateTime_pickers_en","",20369,], -["libraries.designsystem.theme.components.previews_DatePickerLight_DateTime_pickers_en","",20369,], -["features.invite.impl.declineandblock_DeclineAndBlockView_Day_0_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_0_en",20369,], -["features.invite.impl.declineandblock_DeclineAndBlockView_Day_1_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_1_en",20369,], -["features.invite.impl.declineandblock_DeclineAndBlockView_Day_2_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_2_en",20369,], -["features.invite.impl.declineandblock_DeclineAndBlockView_Day_3_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_3_en",20369,], -["features.invite.impl.declineandblock_DeclineAndBlockView_Day_4_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_4_en",20369,], +["libraries.designsystem.theme.components.previews_DatePickerDark_DateTime_pickers_en","",20445,], +["libraries.designsystem.theme.components.previews_DatePickerLight_DateTime_pickers_en","",20445,], +["features.invite.impl.declineandblock_DeclineAndBlockView_Day_0_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_0_en",20445,], +["features.invite.impl.declineandblock_DeclineAndBlockView_Day_1_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_1_en",20445,], +["features.invite.impl.declineandblock_DeclineAndBlockView_Day_2_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_2_en",20445,], +["features.invite.impl.declineandblock_DeclineAndBlockView_Day_3_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_3_en",20445,], +["features.invite.impl.declineandblock_DeclineAndBlockView_Day_4_en","features.invite.impl.declineandblock_DeclineAndBlockView_Night_4_en",20445,], ["features.logout.impl.direct_DefaultDirectLogoutView_Day_0_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_0_en",0,], -["features.logout.impl.direct_DefaultDirectLogoutView_Day_1_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_1_en",20369,], -["features.logout.impl.direct_DefaultDirectLogoutView_Day_2_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_2_en",20369,], -["features.logout.impl.direct_DefaultDirectLogoutView_Day_3_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_3_en",20369,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_1_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_1_en",20445,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_2_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_2_en",20445,], +["features.logout.impl.direct_DefaultDirectLogoutView_Day_3_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_3_en",20445,], ["features.logout.impl.direct_DefaultDirectLogoutView_Day_4_en","features.logout.impl.direct_DefaultDirectLogoutView_Night_4_en",0,], -["features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Day_0_en","features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Night_0_en",20369,], -["features.home.impl.components_DefaultRoomListTopBarMultiAccount_Day_0_en","features.home.impl.components_DefaultRoomListTopBarMultiAccount_Night_0_en",20369,], -["features.home.impl.components_DefaultRoomListTopBarWithIndicator_Day_0_en","features.home.impl.components_DefaultRoomListTopBarWithIndicator_Night_0_en",20369,], -["features.home.impl.components_DefaultRoomListTopBar_Day_0_en","features.home.impl.components_DefaultRoomListTopBar_Night_0_en",20369,], +["features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Day_0_en","features.preferences.impl.notifications.edit_DefaultNotificationSettingOption_Night_0_en",20445,], ["features.licenses.impl.details_DependenciesDetailsView_Day_0_en","features.licenses.impl.details_DependenciesDetailsView_Night_0_en",0,], -["features.licenses.impl.list_DependencyLicensesListView_Day_0_en","features.licenses.impl.list_DependencyLicensesListView_Night_0_en",20369,], -["features.licenses.impl.list_DependencyLicensesListView_Day_1_en","features.licenses.impl.list_DependencyLicensesListView_Night_1_en",20369,], -["features.licenses.impl.list_DependencyLicensesListView_Day_2_en","features.licenses.impl.list_DependencyLicensesListView_Night_2_en",20369,], -["features.licenses.impl.list_DependencyLicensesListView_Day_3_en","features.licenses.impl.list_DependencyLicensesListView_Night_3_en",20369,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_0_en","features.preferences.impl.developer_DeveloperSettingsView_Night_0_en",20369,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_1_en","features.preferences.impl.developer_DeveloperSettingsView_Night_1_en",20369,], -["features.preferences.impl.developer_DeveloperSettingsView_Day_2_en","features.preferences.impl.developer_DeveloperSettingsView_Night_2_en",20369,], +["features.licenses.impl.list_DependencyLicensesListView_Day_0_en","features.licenses.impl.list_DependencyLicensesListView_Night_0_en",20445,], +["features.licenses.impl.list_DependencyLicensesListView_Day_1_en","features.licenses.impl.list_DependencyLicensesListView_Night_1_en",20445,], +["features.licenses.impl.list_DependencyLicensesListView_Day_2_en","features.licenses.impl.list_DependencyLicensesListView_Night_2_en",20445,], +["features.licenses.impl.list_DependencyLicensesListView_Day_3_en","features.licenses.impl.list_DependencyLicensesListView_Night_3_en",20445,], +["features.linknewdevice.impl.screens.desktop_DesktopNoticeView_Day_0_en","features.linknewdevice.impl.screens.desktop_DesktopNoticeView_Night_0_en",20445,], +["features.linknewdevice.impl.screens.desktop_DesktopNoticeView_Day_1_en","features.linknewdevice.impl.screens.desktop_DesktopNoticeView_Night_1_en",20445,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_0_en","features.preferences.impl.developer_DeveloperSettingsView_Night_0_en",20445,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_1_en","features.preferences.impl.developer_DeveloperSettingsView_Night_1_en",20445,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_2_en","features.preferences.impl.developer_DeveloperSettingsView_Night_2_en",20445,], +["features.preferences.impl.developer_DeveloperSettingsView_Day_3_en","features.preferences.impl.developer_DeveloperSettingsView_Night_3_en",20445,], ["libraries.designsystem.theme.components_DialogWithDestructiveButton_Dialog_with_destructive_button_Dialogs_en","",0,], ["libraries.designsystem.theme.components_DialogWithOnlyMessageAndOkButton_Dialog_with_only_message_and_ok_button_Dialogs_en","",0,], ["libraries.designsystem.theme.components_DialogWithThirdButton_Dialog_with_third_button_Dialogs_en","",0,], @@ -397,18 +293,19 @@ export const screenshots = [ ["libraries.designsystem.text_DpScale_1_0f__en","",0,], ["libraries.designsystem.text_DpScale_1_5f__en","",0,], ["libraries.designsystem.theme.components_DropdownMenuItem_Menus_en","",0,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_0_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_0_en",20369,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_1_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_1_en",20369,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_2_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_2_en",20369,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_3_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_3_en",20369,], -["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_4_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_4_en",20369,], -["features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_0_en","features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Night_0_en",20369,], -["features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_1_en","features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Night_1_en",20369,], -["features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_2_en","features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Night_2_en",20369,], -["features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_3_en","features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Night_3_en",20369,], -["features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Day_4_en","features.roomdetails.impl.securityandprivacy.editroomaddress_EditRoomAddressView_Night_4_en",20369,], -["features.preferences.impl.user.editprofile_EditUserProfileView_Day_0_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_0_en",20369,], -["features.preferences.impl.user.editprofile_EditUserProfileView_Day_1_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_1_en",20369,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_0_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_0_en",20445,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_1_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_1_en",20445,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_2_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_2_en",20445,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_3_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_3_en",20445,], +["features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Day_4_en","features.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_Night_4_en",20445,], +["features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_0_en","features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Night_0_en",20445,], +["features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_1_en","features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Night_1_en",20445,], +["features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_2_en","features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Night_2_en",20445,], +["features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_3_en","features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Night_3_en",20445,], +["features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Day_4_en","features.securityandprivacy.impl.editroomaddress_EditRoomAddressView_Night_4_en",20445,], +["features.preferences.impl.user.editprofile_EditUserProfileView_Day_0_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_0_en",20445,], +["features.preferences.impl.user.editprofile_EditUserProfileView_Day_1_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_1_en",20445,], +["features.preferences.impl.user.editprofile_EditUserProfileView_Day_2_en","features.preferences.impl.user.editprofile_EditUserProfileView_Night_2_en",20445,], ["libraries.matrix.ui.components_EditableAvatarView_Day_0_en","libraries.matrix.ui.components_EditableAvatarView_Night_0_en",0,], ["libraries.matrix.ui.components_EditableAvatarView_Day_1_en","libraries.matrix.ui.components_EditableAvatarView_Night_1_en",0,], ["libraries.matrix.ui.components_EditableAvatarView_Day_2_en","libraries.matrix.ui.components_EditableAvatarView_Night_2_en",0,], @@ -419,14 +316,28 @@ export const screenshots = [ ["libraries.designsystem.atomic.atoms_ElementLogoAtomMediumNoBlurShadow_Day_0_en","libraries.designsystem.atomic.atoms_ElementLogoAtomMediumNoBlurShadow_Night_0_en",0,], ["libraries.designsystem.atomic.atoms_ElementLogoAtomMedium_Day_0_en","libraries.designsystem.atomic.atoms_ElementLogoAtomMedium_Night_0_en",0,], ["features.messages.impl.timeline.components.customreaction_EmojiItem_Day_0_en","features.messages.impl.timeline.components.customreaction_EmojiItem_Night_0_en",0,], -["features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_0_en","features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Night_0_en",20369,], -["features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_1_en","features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Night_1_en",20369,], +["features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_0_en","features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Night_0_en",20445,], +["features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_1_en","features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Night_1_en",20445,], ["features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_2_en","features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Night_2_en",0,], ["features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Day_3_en","features.messages.impl.timeline.components.customreaction.picker_EmojiPicker_Night_3_en",0,], ["libraries.ui.common.nodes_EmptyView_Day_0_en","libraries.ui.common.nodes_EmptyView_Night_0_en",0,], -["libraries.designsystem.components.dialogs_ErrorDialogContent_Dialogs_en","",20369,], -["libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Night_0_en",20369,], -["libraries.designsystem.components.dialogs_ErrorDialog_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialog_Night_0_en",20369,], +["features.linknewdevice.impl.screens.number_EnterNumberView_Day_0_en","features.linknewdevice.impl.screens.number_EnterNumberView_Night_0_en",20445,], +["features.linknewdevice.impl.screens.number_EnterNumberView_Day_1_en","features.linknewdevice.impl.screens.number_EnterNumberView_Night_1_en",20445,], +["features.linknewdevice.impl.screens.number_EnterNumberView_Day_2_en","features.linknewdevice.impl.screens.number_EnterNumberView_Night_2_en",20445,], +["features.linknewdevice.impl.screens.number_EnterNumberView_Day_3_en","features.linknewdevice.impl.screens.number_EnterNumberView_Night_3_en",20445,], +["features.linknewdevice.impl.screens.number_EnterNumberView_Day_4_en","features.linknewdevice.impl.screens.number_EnterNumberView_Night_4_en",20445,], +["features.linknewdevice.impl.screens.number_EnterNumberView_Day_5_en","features.linknewdevice.impl.screens.number_EnterNumberView_Night_5_en",20445,], +["libraries.designsystem.components.dialogs_ErrorDialogContent_Dialogs_en","",20445,], +["libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialogWithDoNotShowAgain_Night_0_en",20445,], +["libraries.designsystem.components.dialogs_ErrorDialog_Day_0_en","libraries.designsystem.components.dialogs_ErrorDialog_Night_0_en",20445,], +["features.linknewdevice.impl.screens.error_ErrorView_Day_0_en","features.linknewdevice.impl.screens.error_ErrorView_Night_0_en",20445,], +["features.linknewdevice.impl.screens.error_ErrorView_Day_1_en","features.linknewdevice.impl.screens.error_ErrorView_Night_1_en",20445,], +["features.linknewdevice.impl.screens.error_ErrorView_Day_2_en","features.linknewdevice.impl.screens.error_ErrorView_Night_2_en",20445,], +["features.linknewdevice.impl.screens.error_ErrorView_Day_3_en","features.linknewdevice.impl.screens.error_ErrorView_Night_3_en",20445,], +["features.linknewdevice.impl.screens.error_ErrorView_Day_4_en","features.linknewdevice.impl.screens.error_ErrorView_Night_4_en",20445,], +["features.linknewdevice.impl.screens.error_ErrorView_Day_5_en","features.linknewdevice.impl.screens.error_ErrorView_Night_5_en",20445,], +["features.linknewdevice.impl.screens.error_ErrorView_Day_6_en","features.linknewdevice.impl.screens.error_ErrorView_Night_6_en",20445,], +["features.linknewdevice.impl.screens.error_ErrorView_Day_7_en","features.linknewdevice.impl.screens.error_ErrorView_Night_7_en",20445,], ["features.messages.impl.timeline.debug_EventDebugInfoView_Day_0_en","features.messages.impl.timeline.debug_EventDebugInfoView_Night_0_en",0,], ["libraries.designsystem.components_ExpandableBottomSheetLayout_en","",0,], ["libraries.featureflag.ui_FeatureListView_Day_0_en","libraries.featureflag.ui_FeatureListView_Night_0_en",0,], @@ -445,41 +356,44 @@ export const screenshots = [ ["libraries.designsystem.theme.components_FloatingActionButton_Floating_Action_Buttons_en","",0,], ["libraries.designsystem.atomic.pages_FlowStepPage_Day_0_en","libraries.designsystem.atomic.pages_FlowStepPage_Night_0_en",0,], ["features.messages.impl.timeline.focus_FocusRequestStateView_Day_0_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_0_en",0,], -["features.messages.impl.timeline.focus_FocusRequestStateView_Day_1_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_1_en",20369,], -["features.messages.impl.timeline.focus_FocusRequestStateView_Day_2_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_2_en",20369,], -["features.messages.impl.timeline.focus_FocusRequestStateView_Day_3_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_3_en",20369,], -["features.messages.impl.timeline.components_FocusedEventEnterprise_Day_0_en","features.messages.impl.timeline.components_FocusedEventEnterprise_Night_0_en",0,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_1_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_1_en",20445,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_2_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_2_en",20445,], +["features.messages.impl.timeline.focus_FocusRequestStateView_Day_3_en","features.messages.impl.timeline.focus_FocusRequestStateView_Night_3_en",20445,], ["features.messages.impl.timeline.components_FocusedEvent_Day_0_en","features.messages.impl.timeline.components_FocusedEvent_Night_0_en",0,], ["libraries.textcomposer.components_FormattingOption_Day_0_en","libraries.textcomposer.components_FormattingOption_Night_0_en",0,], -["features.messages.impl.forward_ForwardMessagesView_Day_0_en","features.messages.impl.forward_ForwardMessagesView_Night_0_en",0,], -["features.messages.impl.forward_ForwardMessagesView_Day_1_en","features.messages.impl.forward_ForwardMessagesView_Night_1_en",0,], -["features.messages.impl.forward_ForwardMessagesView_Day_2_en","features.messages.impl.forward_ForwardMessagesView_Night_2_en",0,], -["features.messages.impl.forward_ForwardMessagesView_Day_3_en","features.messages.impl.forward_ForwardMessagesView_Night_3_en",20369,], -["features.home.impl.components_FullScreenIntentPermissionBanner_Day_0_en","features.home.impl.components_FullScreenIntentPermissionBanner_Night_0_en",20369,], +["features.forward.impl_ForwardMessagesView_Day_0_en","features.forward.impl_ForwardMessagesView_Night_0_en",0,], +["features.forward.impl_ForwardMessagesView_Day_1_en","features.forward.impl_ForwardMessagesView_Night_1_en",0,], +["features.forward.impl_ForwardMessagesView_Day_2_en","features.forward.impl_ForwardMessagesView_Night_2_en",0,], +["features.forward.impl_ForwardMessagesView_Day_3_en","features.forward.impl_ForwardMessagesView_Night_3_en",20445,], +["features.home.impl.components_FullScreenIntentPermissionBanner_Day_0_en","features.home.impl.components_FullScreenIntentPermissionBanner_Night_0_en",20445,], ["libraries.designsystem.components.button_GradientFloatingActionButtonCircleShape_Day_0_en","libraries.designsystem.components.button_GradientFloatingActionButtonCircleShape_Night_0_en",0,], ["libraries.designsystem.components.button_GradientFloatingActionButton_Day_0_en","libraries.designsystem.components.button_GradientFloatingActionButton_Night_0_en",0,], ["features.messages.impl.timeline.components.group_GroupHeaderView_Day_0_en","features.messages.impl.timeline.components.group_GroupHeaderView_Night_0_en",0,], ["libraries.designsystem.atomic.pages_HeaderFooterPageScrollable_Day_0_en","libraries.designsystem.atomic.pages_HeaderFooterPageScrollable_Night_0_en",0,], ["libraries.designsystem.atomic.pages_HeaderFooterPage_Day_0_en","libraries.designsystem.atomic.pages_HeaderFooterPage_Night_0_en",0,], -["features.home.impl.spaces_HomeSpacesView_Day_0_en","features.home.impl.spaces_HomeSpacesView_Night_0_en",20369,], -["features.home.impl.spaces_HomeSpacesView_Day_1_en","features.home.impl.spaces_HomeSpacesView_Night_1_en",20369,], +["features.messages.impl.crypto.historyvisible_HistoryVisibleStateView_Day_0_en","features.messages.impl.crypto.historyvisible_HistoryVisibleStateView_Night_0_en",20445,], +["features.home.impl.spaces_HomeSpacesView_Day_0_en","features.home.impl.spaces_HomeSpacesView_Night_0_en",20445,], +["features.home.impl.spaces_HomeSpacesView_Day_1_en","features.home.impl.spaces_HomeSpacesView_Night_1_en",20445,], +["features.home.impl.components_HomeTopBarMultiAccount_Day_0_en","features.home.impl.components_HomeTopBarMultiAccount_Night_0_en",20445,], +["features.home.impl.components_HomeTopBarWithIndicator_Day_0_en","features.home.impl.components_HomeTopBarWithIndicator_Night_0_en",20445,], +["features.home.impl.components_HomeTopBar_Day_0_en","features.home.impl.components_HomeTopBar_Night_0_en",20445,], ["features.home.impl_HomeViewA11y_en","",0,], -["features.home.impl_HomeView_Day_0_en","features.home.impl_HomeView_Night_0_en",20369,], -["features.home.impl_HomeView_Day_10_en","features.home.impl_HomeView_Night_10_en",20369,], +["features.home.impl_HomeView_Day_0_en","features.home.impl_HomeView_Night_0_en",20445,], +["features.home.impl_HomeView_Day_10_en","features.home.impl_HomeView_Night_10_en",20445,], ["features.home.impl_HomeView_Day_11_en","features.home.impl_HomeView_Night_11_en",0,], ["features.home.impl_HomeView_Day_12_en","features.home.impl_HomeView_Night_12_en",0,], -["features.home.impl_HomeView_Day_13_en","features.home.impl_HomeView_Night_13_en",20369,], -["features.home.impl_HomeView_Day_14_en","features.home.impl_HomeView_Night_14_en",20369,], -["features.home.impl_HomeView_Day_15_en","features.home.impl_HomeView_Night_15_en",20369,], -["features.home.impl_HomeView_Day_1_en","features.home.impl_HomeView_Night_1_en",20369,], -["features.home.impl_HomeView_Day_2_en","features.home.impl_HomeView_Night_2_en",20369,], -["features.home.impl_HomeView_Day_3_en","features.home.impl_HomeView_Night_3_en",20369,], -["features.home.impl_HomeView_Day_4_en","features.home.impl_HomeView_Night_4_en",20369,], -["features.home.impl_HomeView_Day_5_en","features.home.impl_HomeView_Night_5_en",20369,], -["features.home.impl_HomeView_Day_6_en","features.home.impl_HomeView_Night_6_en",20369,], -["features.home.impl_HomeView_Day_7_en","features.home.impl_HomeView_Night_7_en",20369,], -["features.home.impl_HomeView_Day_8_en","features.home.impl_HomeView_Night_8_en",20369,], -["features.home.impl_HomeView_Day_9_en","features.home.impl_HomeView_Night_9_en",20369,], +["features.home.impl_HomeView_Day_13_en","features.home.impl_HomeView_Night_13_en",20445,], +["features.home.impl_HomeView_Day_14_en","features.home.impl_HomeView_Night_14_en",20445,], +["features.home.impl_HomeView_Day_15_en","features.home.impl_HomeView_Night_15_en",20445,], +["features.home.impl_HomeView_Day_1_en","features.home.impl_HomeView_Night_1_en",20445,], +["features.home.impl_HomeView_Day_2_en","features.home.impl_HomeView_Night_2_en",20445,], +["features.home.impl_HomeView_Day_3_en","features.home.impl_HomeView_Night_3_en",20445,], +["features.home.impl_HomeView_Day_4_en","features.home.impl_HomeView_Night_4_en",20445,], +["features.home.impl_HomeView_Day_5_en","features.home.impl_HomeView_Night_5_en",20445,], +["features.home.impl_HomeView_Day_6_en","features.home.impl_HomeView_Night_6_en",20445,], +["features.home.impl_HomeView_Day_7_en","features.home.impl_HomeView_Night_7_en",20445,], +["features.home.impl_HomeView_Day_8_en","features.home.impl_HomeView_Night_8_en",20445,], +["features.home.impl_HomeView_Day_9_en","features.home.impl_HomeView_Night_9_en",20445,], ["libraries.designsystem.theme.components_HorizontalDivider_Dividers_en","",0,], ["libraries.designsystem.ruler_HorizontalRuler_Day_0_en","libraries.designsystem.ruler_HorizontalRuler_Night_0_en",0,], ["libraries.designsystem.theme.components_IconButton_Buttons_en","",0,], @@ -490,16 +404,10 @@ export const screenshots = [ ["libraries.designsystem.theme.components_IconToggleButton_Toggles_en","",0,], ["appicon.element_Icon_en","",0,], ["appicon.enterprise_Icon_en","",0,], -["libraries.designsystem.icons_IconsCompound_Day_0_en","libraries.designsystem.icons_IconsCompound_Night_0_en",0,], -["libraries.designsystem.icons_IconsCompound_Day_1_en","libraries.designsystem.icons_IconsCompound_Night_1_en",0,], -["libraries.designsystem.icons_IconsCompound_Day_2_en","libraries.designsystem.icons_IconsCompound_Night_2_en",0,], -["libraries.designsystem.icons_IconsCompound_Day_3_en","libraries.designsystem.icons_IconsCompound_Night_3_en",0,], -["libraries.designsystem.icons_IconsCompound_Day_4_en","libraries.designsystem.icons_IconsCompound_Night_4_en",0,], -["libraries.designsystem.icons_IconsCompound_Day_5_en","libraries.designsystem.icons_IconsCompound_Night_5_en",0,], ["libraries.designsystem.icons_IconsOther_Day_0_en","libraries.designsystem.icons_IconsOther_Night_0_en",0,], ["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_0_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_0_en",0,], -["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_1_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_1_en",20369,], -["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_2_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_2_en",20369,], +["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_1_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_1_en",20445,], +["features.messages.impl.crypto.identity_IdentityChangeStateView_Day_2_en","features.messages.impl.crypto.identity_IdentityChangeStateView_Night_2_en",20445,], ["libraries.mediaviewer.impl.gallery.ui_ImageItemView_Day_0_en","libraries.mediaviewer.impl.gallery.ui_ImageItemView_Night_0_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_0_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_0_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_10_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_10_en",0,], @@ -507,110 +415,115 @@ export const screenshots = [ ["libraries.matrix.ui.messages.reply_InReplyToView_Day_1_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_1_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_2_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_2_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_3_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_3_en",0,], -["libraries.matrix.ui.messages.reply_InReplyToView_Day_4_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_4_en",20369,], +["libraries.matrix.ui.messages.reply_InReplyToView_Day_4_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_4_en",20445,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_5_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_5_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_6_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_6_en",0,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_7_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_7_en",0,], -["libraries.matrix.ui.messages.reply_InReplyToView_Day_8_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_8_en",20369,], +["libraries.matrix.ui.messages.reply_InReplyToView_Day_8_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_8_en",20445,], ["libraries.matrix.ui.messages.reply_InReplyToView_Day_9_en","libraries.matrix.ui.messages.reply_InReplyToView_Night_9_en",0,], -["features.call.impl.ui_IncomingCallScreen_Day_0_en","features.call.impl.ui_IncomingCallScreen_Night_0_en",20369,], +["features.call.impl.ui_IncomingCallScreen_Day_0_en","features.call.impl.ui_IncomingCallScreen_Night_0_en",20445,], ["features.verifysession.impl.incoming_IncomingVerificationViewA11y_en","",0,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_0_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_0_en",20369,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_10_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_10_en",20369,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_11_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_11_en",20369,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_12_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_12_en",20369,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_13_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_13_en",20369,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_1_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_1_en",20369,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_2_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_2_en",20369,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_3_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_3_en",20369,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_4_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_4_en",20369,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_5_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_5_en",20369,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_6_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_6_en",20369,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_7_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_7_en",20369,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_8_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_8_en",20369,], -["features.verifysession.impl.incoming_IncomingVerificationView_Day_9_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_9_en",20369,], -["features.networkmonitor.api.ui_Indicator_Day_0_en","features.networkmonitor.api.ui_Indicator_Night_0_en",0,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_0_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_0_en",20445,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_10_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_10_en",20445,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_11_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_11_en",20445,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_12_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_12_en",20445,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_13_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_13_en",20445,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_1_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_1_en",20445,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_2_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_2_en",20445,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_3_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_3_en",20445,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_4_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_4_en",20445,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_5_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_5_en",20445,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_6_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_6_en",20445,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_7_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_7_en",20445,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_8_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_8_en",20445,], +["features.verifysession.impl.incoming_IncomingVerificationView_Day_9_en","features.verifysession.impl.incoming_IncomingVerificationView_Night_9_en",20445,], ["libraries.designsystem.atomic.molecules_InfoListItemMolecule_Day_0_en","libraries.designsystem.atomic.molecules_InfoListItemMolecule_Night_0_en",0,], ["libraries.designsystem.atomic.organisms_InfoListOrganism_Day_0_en","libraries.designsystem.atomic.organisms_InfoListOrganism_Night_0_en",0,], ["libraries.matrix.ui.media_InitialsAvatarBitmapGenerator_Day_0_en","libraries.matrix.ui.media_InitialsAvatarBitmapGenerator_Night_0_en",0,], -["features.call.impl.ui_InvalidAudioDeviceDialog_Day_0_en","features.call.impl.ui_InvalidAudioDeviceDialog_Night_0_en",20369,], -["features.invitepeople.impl_InvitePeopleView_Day_0_en","features.invitepeople.impl_InvitePeopleView_Night_0_en",20369,], -["features.invitepeople.impl_InvitePeopleView_Day_1_en","features.invitepeople.impl_InvitePeopleView_Night_1_en",20369,], +["features.call.impl.ui_InvalidAudioDeviceDialog_Day_0_en","features.call.impl.ui_InvalidAudioDeviceDialog_Night_0_en",20445,], +["features.invitepeople.impl_InvitePeopleView_Day_0_en","features.invitepeople.impl_InvitePeopleView_Night_0_en",20445,], +["features.invitepeople.impl_InvitePeopleView_Day_1_en","features.invitepeople.impl_InvitePeopleView_Night_1_en",20445,], ["features.invitepeople.impl_InvitePeopleView_Day_2_en","features.invitepeople.impl_InvitePeopleView_Night_2_en",0,], ["features.invitepeople.impl_InvitePeopleView_Day_3_en","features.invitepeople.impl_InvitePeopleView_Night_3_en",0,], -["features.invitepeople.impl_InvitePeopleView_Day_4_en","features.invitepeople.impl_InvitePeopleView_Night_4_en",20369,], -["features.invitepeople.impl_InvitePeopleView_Day_5_en","features.invitepeople.impl_InvitePeopleView_Night_5_en",20369,], -["features.invitepeople.impl_InvitePeopleView_Day_6_en","features.invitepeople.impl_InvitePeopleView_Night_6_en",20369,], -["features.invitepeople.impl_InvitePeopleView_Day_7_en","features.invitepeople.impl_InvitePeopleView_Night_7_en",20369,], +["features.invitepeople.impl_InvitePeopleView_Day_4_en","features.invitepeople.impl_InvitePeopleView_Night_4_en",20445,], +["features.invitepeople.impl_InvitePeopleView_Day_5_en","features.invitepeople.impl_InvitePeopleView_Night_5_en",20445,], +["features.invitepeople.impl_InvitePeopleView_Day_6_en","features.invitepeople.impl_InvitePeopleView_Night_6_en",20445,], +["features.invitepeople.impl_InvitePeopleView_Day_7_en","features.invitepeople.impl_InvitePeopleView_Night_7_en",20445,], ["features.invitepeople.impl_InvitePeopleView_Day_8_en","features.invitepeople.impl_InvitePeopleView_Night_8_en",0,], -["features.invitepeople.impl_InvitePeopleView_Day_9_en","features.invitepeople.impl_InvitePeopleView_Night_9_en",20369,], -["libraries.matrix.ui.components_InviteSenderView_Day_0_en","libraries.matrix.ui.components_InviteSenderView_Night_0_en",20369,], -["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_0_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_0_en",20369,], -["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_1_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_1_en",20369,], -["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_2_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_2_en",20369,], -["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_3_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_3_en",20369,], -["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_4_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_4_en",20369,], -["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_5_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_5_en",20369,], +["features.invitepeople.impl_InvitePeopleView_Day_9_en","features.invitepeople.impl_InvitePeopleView_Night_9_en",20445,], +["libraries.matrix.ui.components_InviteSenderView_Day_0_en","libraries.matrix.ui.components_InviteSenderView_Night_0_en",20445,], +["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_0_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_0_en",20445,], +["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_1_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_1_en",20445,], +["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_2_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_2_en",20445,], +["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_3_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_3_en",20445,], +["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_4_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_4_en",20445,], +["features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Day_5_en","features.startchat.impl.joinbyaddress_JoinRoomByAddressView_Night_5_en",20445,], ["features.joinroom.impl_JoinRoomView_Day_0_en","features.joinroom.impl_JoinRoomView_Night_0_en",0,], -["features.joinroom.impl_JoinRoomView_Day_10_en","features.joinroom.impl_JoinRoomView_Night_10_en",20369,], -["features.joinroom.impl_JoinRoomView_Day_11_en","features.joinroom.impl_JoinRoomView_Night_11_en",20369,], -["features.joinroom.impl_JoinRoomView_Day_12_en","features.joinroom.impl_JoinRoomView_Night_12_en",20369,], -["features.joinroom.impl_JoinRoomView_Day_13_en","features.joinroom.impl_JoinRoomView_Night_13_en",20369,], -["features.joinroom.impl_JoinRoomView_Day_14_en","features.joinroom.impl_JoinRoomView_Night_14_en",20369,], -["features.joinroom.impl_JoinRoomView_Day_15_en","features.joinroom.impl_JoinRoomView_Night_15_en",20369,], -["features.joinroom.impl_JoinRoomView_Day_16_en","features.joinroom.impl_JoinRoomView_Night_16_en",20369,], -["features.joinroom.impl_JoinRoomView_Day_1_en","features.joinroom.impl_JoinRoomView_Night_1_en",20369,], -["features.joinroom.impl_JoinRoomView_Day_2_en","features.joinroom.impl_JoinRoomView_Night_2_en",20369,], -["features.joinroom.impl_JoinRoomView_Day_3_en","features.joinroom.impl_JoinRoomView_Night_3_en",20369,], -["features.joinroom.impl_JoinRoomView_Day_4_en","features.joinroom.impl_JoinRoomView_Night_4_en",20369,], -["features.joinroom.impl_JoinRoomView_Day_5_en","features.joinroom.impl_JoinRoomView_Night_5_en",20369,], -["features.joinroom.impl_JoinRoomView_Day_6_en","features.joinroom.impl_JoinRoomView_Night_6_en",20369,], -["features.joinroom.impl_JoinRoomView_Day_7_en","features.joinroom.impl_JoinRoomView_Night_7_en",20369,], -["features.joinroom.impl_JoinRoomView_Day_8_en","features.joinroom.impl_JoinRoomView_Night_8_en",20369,], -["features.joinroom.impl_JoinRoomView_Day_9_en","features.joinroom.impl_JoinRoomView_Night_9_en",20369,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en",20369,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en",20369,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en",20369,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_3_en",20369,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en",20369,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en",20369,], -["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en",20369,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_0_en","features.knockrequests.impl.list_KnockRequestsListView_Night_0_en",20369,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_10_en","features.knockrequests.impl.list_KnockRequestsListView_Night_10_en",20369,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_1_en","features.knockrequests.impl.list_KnockRequestsListView_Night_1_en",20369,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_2_en","features.knockrequests.impl.list_KnockRequestsListView_Night_2_en",20369,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_3_en","features.knockrequests.impl.list_KnockRequestsListView_Night_3_en",20369,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_4_en","features.knockrequests.impl.list_KnockRequestsListView_Night_4_en",20369,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_5_en","features.knockrequests.impl.list_KnockRequestsListView_Night_5_en",20369,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_6_en","features.knockrequests.impl.list_KnockRequestsListView_Night_6_en",20369,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_7_en","features.knockrequests.impl.list_KnockRequestsListView_Night_7_en",20369,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_8_en","features.knockrequests.impl.list_KnockRequestsListView_Night_8_en",20369,], -["features.knockrequests.impl.list_KnockRequestsListView_Day_9_en","features.knockrequests.impl.list_KnockRequestsListView_Night_9_en",20369,], +["features.joinroom.impl_JoinRoomView_Day_10_en","features.joinroom.impl_JoinRoomView_Night_10_en",20445,], +["features.joinroom.impl_JoinRoomView_Day_11_en","features.joinroom.impl_JoinRoomView_Night_11_en",20445,], +["features.joinroom.impl_JoinRoomView_Day_12_en","features.joinroom.impl_JoinRoomView_Night_12_en",20445,], +["features.joinroom.impl_JoinRoomView_Day_13_en","features.joinroom.impl_JoinRoomView_Night_13_en",20445,], +["features.joinroom.impl_JoinRoomView_Day_14_en","features.joinroom.impl_JoinRoomView_Night_14_en",20445,], +["features.joinroom.impl_JoinRoomView_Day_15_en","features.joinroom.impl_JoinRoomView_Night_15_en",20445,], +["features.joinroom.impl_JoinRoomView_Day_16_en","features.joinroom.impl_JoinRoomView_Night_16_en",20445,], +["features.joinroom.impl_JoinRoomView_Day_1_en","features.joinroom.impl_JoinRoomView_Night_1_en",20445,], +["features.joinroom.impl_JoinRoomView_Day_2_en","features.joinroom.impl_JoinRoomView_Night_2_en",20445,], +["features.joinroom.impl_JoinRoomView_Day_3_en","features.joinroom.impl_JoinRoomView_Night_3_en",20445,], +["features.joinroom.impl_JoinRoomView_Day_4_en","features.joinroom.impl_JoinRoomView_Night_4_en",20445,], +["features.joinroom.impl_JoinRoomView_Day_5_en","features.joinroom.impl_JoinRoomView_Night_5_en",20445,], +["features.joinroom.impl_JoinRoomView_Day_6_en","features.joinroom.impl_JoinRoomView_Night_6_en",20445,], +["features.joinroom.impl_JoinRoomView_Day_7_en","features.joinroom.impl_JoinRoomView_Night_7_en",20445,], +["features.joinroom.impl_JoinRoomView_Day_8_en","features.joinroom.impl_JoinRoomView_Night_8_en",20445,], +["features.joinroom.impl_JoinRoomView_Day_9_en","features.joinroom.impl_JoinRoomView_Night_9_en",20445,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en",20445,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en",20445,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en",20445,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_3_en",20445,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en",20445,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en",20445,], +["features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en","features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en",20445,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_0_en","features.knockrequests.impl.list_KnockRequestsListView_Night_0_en",20445,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_10_en","features.knockrequests.impl.list_KnockRequestsListView_Night_10_en",20445,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_1_en","features.knockrequests.impl.list_KnockRequestsListView_Night_1_en",20445,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_2_en","features.knockrequests.impl.list_KnockRequestsListView_Night_2_en",20445,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_3_en","features.knockrequests.impl.list_KnockRequestsListView_Night_3_en",20445,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_4_en","features.knockrequests.impl.list_KnockRequestsListView_Night_4_en",20445,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_5_en","features.knockrequests.impl.list_KnockRequestsListView_Night_5_en",20445,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_6_en","features.knockrequests.impl.list_KnockRequestsListView_Night_6_en",20445,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_7_en","features.knockrequests.impl.list_KnockRequestsListView_Night_7_en",20445,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_8_en","features.knockrequests.impl.list_KnockRequestsListView_Night_8_en",20445,], +["features.knockrequests.impl.list_KnockRequestsListView_Day_9_en","features.knockrequests.impl.list_KnockRequestsListView_Night_9_en",20445,], ["libraries.designsystem.components_LabelledCheckbox_Toggles_en","",0,], -["features.preferences.impl.labs_LabsView_Day_0_en","features.preferences.impl.labs_LabsView_Night_0_en",20369,], -["features.preferences.impl.labs_LabsView_Day_1_en","features.preferences.impl.labs_LabsView_Night_1_en",20369,], +["features.preferences.impl.labs_LabsView_Day_0_en","features.preferences.impl.labs_LabsView_Night_0_en",20445,], +["features.preferences.impl.labs_LabsView_Day_1_en","features.preferences.impl.labs_LabsView_Night_1_en",20445,], ["features.leaveroom.impl_LeaveRoomView_Day_0_en","features.leaveroom.impl_LeaveRoomView_Night_0_en",0,], -["features.leaveroom.impl_LeaveRoomView_Day_1_en","features.leaveroom.impl_LeaveRoomView_Night_1_en",20369,], -["features.leaveroom.impl_LeaveRoomView_Day_2_en","features.leaveroom.impl_LeaveRoomView_Night_2_en",20369,], -["features.leaveroom.impl_LeaveRoomView_Day_3_en","features.leaveroom.impl_LeaveRoomView_Night_3_en",20369,], -["features.leaveroom.impl_LeaveRoomView_Day_4_en","features.leaveroom.impl_LeaveRoomView_Night_4_en",20369,], -["features.leaveroom.impl_LeaveRoomView_Day_5_en","features.leaveroom.impl_LeaveRoomView_Night_5_en",20369,], -["features.leaveroom.impl_LeaveRoomView_Day_6_en","features.leaveroom.impl_LeaveRoomView_Night_6_en",20369,], -["features.leaveroom.impl_LeaveRoomView_Day_7_en","features.leaveroom.impl_LeaveRoomView_Night_7_en",20369,], -["features.space.impl.leave_LeaveSpaceView_Day_0_en","features.space.impl.leave_LeaveSpaceView_Night_0_en",20369,], -["features.space.impl.leave_LeaveSpaceView_Day_1_en","features.space.impl.leave_LeaveSpaceView_Night_1_en",20369,], -["features.space.impl.leave_LeaveSpaceView_Day_2_en","features.space.impl.leave_LeaveSpaceView_Night_2_en",20369,], -["features.space.impl.leave_LeaveSpaceView_Day_3_en","features.space.impl.leave_LeaveSpaceView_Night_3_en",20369,], -["features.space.impl.leave_LeaveSpaceView_Day_4_en","features.space.impl.leave_LeaveSpaceView_Night_4_en",20369,], -["features.space.impl.leave_LeaveSpaceView_Day_5_en","features.space.impl.leave_LeaveSpaceView_Night_5_en",20369,], -["features.space.impl.leave_LeaveSpaceView_Day_6_en","features.space.impl.leave_LeaveSpaceView_Night_6_en",20369,], -["features.space.impl.leave_LeaveSpaceView_Day_7_en","features.space.impl.leave_LeaveSpaceView_Night_7_en",20369,], -["features.space.impl.leave_LeaveSpaceView_Day_8_en","features.space.impl.leave_LeaveSpaceView_Night_8_en",20369,], -["features.space.impl.leave_LeaveSpaceView_Day_9_en","features.space.impl.leave_LeaveSpaceView_Night_9_en",20369,], +["features.leaveroom.impl_LeaveRoomView_Day_1_en","features.leaveroom.impl_LeaveRoomView_Night_1_en",20445,], +["features.leaveroom.impl_LeaveRoomView_Day_2_en","features.leaveroom.impl_LeaveRoomView_Night_2_en",20445,], +["features.leaveroom.impl_LeaveRoomView_Day_3_en","features.leaveroom.impl_LeaveRoomView_Night_3_en",20445,], +["features.leaveroom.impl_LeaveRoomView_Day_4_en","features.leaveroom.impl_LeaveRoomView_Night_4_en",20445,], +["features.leaveroom.impl_LeaveRoomView_Day_5_en","features.leaveroom.impl_LeaveRoomView_Night_5_en",20445,], +["features.leaveroom.impl_LeaveRoomView_Day_6_en","features.leaveroom.impl_LeaveRoomView_Night_6_en",20445,], +["features.leaveroom.impl_LeaveRoomView_Day_7_en","features.leaveroom.impl_LeaveRoomView_Night_7_en",20445,], +["features.space.impl.leave_LeaveSpaceView_Day_0_en","features.space.impl.leave_LeaveSpaceView_Night_0_en",20445,], +["features.space.impl.leave_LeaveSpaceView_Day_1_en","features.space.impl.leave_LeaveSpaceView_Night_1_en",20445,], +["features.space.impl.leave_LeaveSpaceView_Day_2_en","features.space.impl.leave_LeaveSpaceView_Night_2_en",20445,], +["features.space.impl.leave_LeaveSpaceView_Day_3_en","features.space.impl.leave_LeaveSpaceView_Night_3_en",20445,], +["features.space.impl.leave_LeaveSpaceView_Day_4_en","features.space.impl.leave_LeaveSpaceView_Night_4_en",20445,], +["features.space.impl.leave_LeaveSpaceView_Day_5_en","features.space.impl.leave_LeaveSpaceView_Night_5_en",20445,], +["features.space.impl.leave_LeaveSpaceView_Day_6_en","features.space.impl.leave_LeaveSpaceView_Night_6_en",20445,], +["features.space.impl.leave_LeaveSpaceView_Day_7_en","features.space.impl.leave_LeaveSpaceView_Night_7_en",20445,], +["features.space.impl.leave_LeaveSpaceView_Day_8_en","features.space.impl.leave_LeaveSpaceView_Night_8_en",20445,], +["features.space.impl.leave_LeaveSpaceView_Day_9_en","features.space.impl.leave_LeaveSpaceView_Night_9_en",20445,], ["libraries.designsystem.background_LightGradientBackground_Day_0_en","libraries.designsystem.background_LightGradientBackground_Night_0_en",0,], ["libraries.designsystem.theme.components_LinearProgressIndicator_Progress_Indicators_en","",0,], +["features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_0_en","features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Night_0_en",20445,], +["features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_1_en","features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Night_1_en",0,], +["features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_2_en","features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Night_2_en",20445,], +["features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_3_en","features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Night_3_en",20445,], +["features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_4_en","features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Night_4_en",0,], +["features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Day_5_en","features.linknewdevice.impl.screens.root_LinkNewDeviceRootView_Night_5_en",20445,], ["features.messages.impl.link_LinkView_Day_0_en","features.messages.impl.link_LinkView_Night_0_en",0,], -["features.messages.impl.link_LinkView_Day_1_en","features.messages.impl.link_LinkView_Night_1_en",20369,], +["features.messages.impl.link_LinkView_Day_1_en","features.messages.impl.link_LinkView_Night_1_en",20445,], ["libraries.designsystem.components.dialogs_ListDialogContent_Dialogs_en","",0,], ["libraries.designsystem.components.dialogs_ListDialog_Day_0_en","libraries.designsystem.components.dialogs_ListDialog_Night_0_en",0,], ["libraries.designsystem.theme.components_ListItemPrimaryActionWithIcon_List_item_-_Primary_action_&_Icon_List_items_en","",0,], @@ -665,37 +578,38 @@ export const screenshots = [ ["libraries.designsystem.theme.components_ListSupportingTextSmallPadding_List_supporting_text_-_small_padding_List_sections_en","",0,], ["libraries.textcomposer.components_LiveWaveformView_Day_0_en","libraries.textcomposer.components_LiveWaveformView_Night_0_en",0,], ["appnav.room.joined_LoadingRoomNodeView_Day_0_en","appnav.room.joined_LoadingRoomNodeView_Night_0_en",0,], -["appnav.room.joined_LoadingRoomNodeView_Day_1_en","appnav.room.joined_LoadingRoomNodeView_Night_1_en",20369,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_0_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_0_en",20369,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_1_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_1_en",20369,], -["features.lockscreen.impl.settings_LockScreenSettingsView_Day_2_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_2_en",20369,], +["appnav.room.joined_LoadingRoomNodeView_Day_1_en","appnav.room.joined_LoadingRoomNodeView_Night_1_en",20445,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_0_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_0_en",20445,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_1_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_1_en",20445,], +["features.lockscreen.impl.settings_LockScreenSettingsView_Day_2_en","features.lockscreen.impl.settings_LockScreenSettingsView_Night_2_en",20445,], ["appnav.loggedin_LoggedInView_Day_0_en","appnav.loggedin_LoggedInView_Night_0_en",0,], -["appnav.loggedin_LoggedInView_Day_1_en","appnav.loggedin_LoggedInView_Night_1_en",20369,], -["appnav.loggedin_LoggedInView_Day_2_en","appnav.loggedin_LoggedInView_Night_2_en",20369,], -["appnav.loggedin_LoggedInView_Day_3_en","appnav.loggedin_LoggedInView_Night_3_en",20369,], -["features.login.impl.login_LoginModeView_Day_0_en","features.login.impl.login_LoginModeView_Night_0_en",20369,], -["features.login.impl.login_LoginModeView_Day_1_en","features.login.impl.login_LoginModeView_Night_1_en",20369,], -["features.login.impl.login_LoginModeView_Day_2_en","features.login.impl.login_LoginModeView_Night_2_en",20369,], -["features.login.impl.login_LoginModeView_Day_3_en","features.login.impl.login_LoginModeView_Night_3_en",20369,], -["features.login.impl.login_LoginModeView_Day_4_en","features.login.impl.login_LoginModeView_Night_4_en",20369,], -["features.login.impl.login_LoginModeView_Day_5_en","features.login.impl.login_LoginModeView_Night_5_en",20369,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_0_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_0_en",20369,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_1_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_1_en",20369,], -["features.login.impl.screens.loginpassword_LoginPasswordView_Day_2_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_2_en",20369,], -["features.logout.impl_LogoutView_Day_0_en","features.logout.impl_LogoutView_Night_0_en",20369,], -["features.logout.impl_LogoutView_Day_10_en","features.logout.impl_LogoutView_Night_10_en",20369,], -["features.logout.impl_LogoutView_Day_11_en","features.logout.impl_LogoutView_Night_11_en",20369,], -["features.logout.impl_LogoutView_Day_1_en","features.logout.impl_LogoutView_Night_1_en",20369,], -["features.logout.impl_LogoutView_Day_2_en","features.logout.impl_LogoutView_Night_2_en",20369,], -["features.logout.impl_LogoutView_Day_3_en","features.logout.impl_LogoutView_Night_3_en",20369,], -["features.logout.impl_LogoutView_Day_4_en","features.logout.impl_LogoutView_Night_4_en",20369,], -["features.logout.impl_LogoutView_Day_5_en","features.logout.impl_LogoutView_Night_5_en",20369,], -["features.logout.impl_LogoutView_Day_6_en","features.logout.impl_LogoutView_Night_6_en",20369,], -["features.logout.impl_LogoutView_Day_7_en","features.logout.impl_LogoutView_Night_7_en",20369,], -["features.logout.impl_LogoutView_Day_8_en","features.logout.impl_LogoutView_Night_8_en",20369,], -["features.logout.impl_LogoutView_Day_9_en","features.logout.impl_LogoutView_Night_9_en",20369,], +["appnav.loggedin_LoggedInView_Day_1_en","appnav.loggedin_LoggedInView_Night_1_en",20445,], +["appnav.loggedin_LoggedInView_Day_2_en","appnav.loggedin_LoggedInView_Night_2_en",20445,], +["appnav.loggedin_LoggedInView_Day_3_en","appnav.loggedin_LoggedInView_Night_3_en",20445,], +["features.login.impl.login_LoginModeView_Day_0_en","features.login.impl.login_LoginModeView_Night_0_en",20445,], +["features.login.impl.login_LoginModeView_Day_1_en","features.login.impl.login_LoginModeView_Night_1_en",20445,], +["features.login.impl.login_LoginModeView_Day_2_en","features.login.impl.login_LoginModeView_Night_2_en",20445,], +["features.login.impl.login_LoginModeView_Day_3_en","features.login.impl.login_LoginModeView_Night_3_en",20445,], +["features.login.impl.login_LoginModeView_Day_4_en","features.login.impl.login_LoginModeView_Night_4_en",20445,], +["features.login.impl.login_LoginModeView_Day_5_en","features.login.impl.login_LoginModeView_Night_5_en",20445,], +["features.login.impl.login_LoginModeView_Day_6_en","features.login.impl.login_LoginModeView_Night_6_en",20445,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_0_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_0_en",20445,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_1_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_1_en",20445,], +["features.login.impl.screens.loginpassword_LoginPasswordView_Day_2_en","features.login.impl.screens.loginpassword_LoginPasswordView_Night_2_en",20445,], +["features.logout.impl_LogoutView_Day_0_en","features.logout.impl_LogoutView_Night_0_en",20445,], +["features.logout.impl_LogoutView_Day_10_en","features.logout.impl_LogoutView_Night_10_en",20445,], +["features.logout.impl_LogoutView_Day_11_en","features.logout.impl_LogoutView_Night_11_en",20445,], +["features.logout.impl_LogoutView_Day_1_en","features.logout.impl_LogoutView_Night_1_en",20445,], +["features.logout.impl_LogoutView_Day_2_en","features.logout.impl_LogoutView_Night_2_en",20445,], +["features.logout.impl_LogoutView_Day_3_en","features.logout.impl_LogoutView_Night_3_en",20445,], +["features.logout.impl_LogoutView_Day_4_en","features.logout.impl_LogoutView_Night_4_en",20445,], +["features.logout.impl_LogoutView_Day_5_en","features.logout.impl_LogoutView_Night_5_en",20445,], +["features.logout.impl_LogoutView_Day_6_en","features.logout.impl_LogoutView_Night_6_en",20445,], +["features.logout.impl_LogoutView_Day_7_en","features.logout.impl_LogoutView_Night_7_en",20445,], +["features.logout.impl_LogoutView_Day_8_en","features.logout.impl_LogoutView_Night_8_en",20445,], +["features.logout.impl_LogoutView_Day_9_en","features.logout.impl_LogoutView_Night_9_en",20445,], ["libraries.designsystem.components.button_MainActionButton_Buttons_en","",0,], -["libraries.textcomposer_MarkdownTextComposerEdit_Day_0_en","libraries.textcomposer_MarkdownTextComposerEdit_Night_0_en",20369,], +["libraries.textcomposer_MarkdownTextComposerEdit_Day_0_en","libraries.textcomposer_MarkdownTextComposerEdit_Night_0_en",20445,], ["libraries.textcomposer.components.markdown_MarkdownTextInput_Day_0_en","libraries.textcomposer.components.markdown_MarkdownTextInput_Night_0_en",0,], ["libraries.designsystem.atomic.atoms_MatrixBadgeAtomInfo_Day_0_en","libraries.designsystem.atomic.atoms_MatrixBadgeAtomInfo_Night_0_en",0,], ["libraries.designsystem.atomic.atoms_MatrixBadgeAtomNegative_Day_0_en","libraries.designsystem.atomic.atoms_MatrixBadgeAtomNegative_Night_0_en",0,], @@ -708,22 +622,22 @@ export const screenshots = [ ["libraries.matrix.ui.components_MatrixUserRow_Day_1_en","libraries.matrix.ui.components_MatrixUserRow_Night_1_en",0,], ["libraries.mediaviewer.impl.local.audio_MediaAudioView_Day_0_en","libraries.mediaviewer.impl.local.audio_MediaAudioView_Night_0_en",0,], ["libraries.mediaviewer.impl.local.audio_MediaAudioView_Day_1_en","libraries.mediaviewer.impl.local.audio_MediaAudioView_Night_1_en",0,], -["libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_en","libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Night_0_en",20369,], -["libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en","libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Night_0_en",20369,], +["libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Day_0_en","libraries.mediaviewer.impl.details_MediaDeleteConfirmationBottomSheet_Night_0_en",20445,], +["libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Day_0_en","libraries.mediaviewer.impl.details_MediaDetailsBottomSheet_Night_0_en",20445,], ["libraries.mediaviewer.impl.local.file_MediaFileView_Day_0_en","libraries.mediaviewer.impl.local.file_MediaFileView_Night_0_en",0,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en",20369,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_10_en",20369,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_11_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_11_en",20369,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_12_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_12_en",20369,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_1_en",20369,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_2_en",20369,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en",20369,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_4_en",20369,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_5_en",20369,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en",20369,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en",20369,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en",20369,], -["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en",20369,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_0_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_0_en",20445,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_10_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_10_en",20445,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_11_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_11_en",20445,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_12_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_12_en",20445,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_1_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_1_en",20445,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_2_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_2_en",20445,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_3_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_3_en",20445,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_4_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_4_en",20445,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_5_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_5_en",20445,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_6_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_6_en",20445,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_7_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_7_en",20445,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_8_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_8_en",20445,], +["libraries.mediaviewer.impl.gallery_MediaGalleryView_Day_9_en","libraries.mediaviewer.impl.gallery_MediaGalleryView_Night_9_en",20445,], ["libraries.mediaviewer.impl.local.image_MediaImageView_Day_0_en","libraries.mediaviewer.impl.local.image_MediaImageView_Night_0_en",0,], ["libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Day_0_en","libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Night_0_en",0,], ["libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Day_1_en","libraries.mediaviewer.impl.local.player_MediaPlayerControllerView_Night_1_en",0,], @@ -731,14 +645,14 @@ export const screenshots = [ ["libraries.mediaviewer.impl.local.video_MediaVideoView_Day_0_en","libraries.mediaviewer.impl.local.video_MediaVideoView_Night_0_en",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_0_en","",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_10_en","",0,], -["libraries.mediaviewer.impl.viewer_MediaViewerView_11_en","",20369,], -["libraries.mediaviewer.impl.viewer_MediaViewerView_12_en","",20369,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_11_en","",20445,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_12_en","",20445,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_13_en","",0,], -["libraries.mediaviewer.impl.viewer_MediaViewerView_14_en","",20369,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_14_en","",20445,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_15_en","",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_16_en","",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_1_en","",0,], -["libraries.mediaviewer.impl.viewer_MediaViewerView_2_en","",20369,], +["libraries.mediaviewer.impl.viewer_MediaViewerView_2_en","",20445,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_3_en","",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_4_en","",0,], ["libraries.mediaviewer.impl.viewer_MediaViewerView_5_en","",0,], @@ -752,7 +666,7 @@ export const screenshots = [ ["libraries.textcomposer.mentions_MentionSpanTheme_Day_0_en","libraries.textcomposer.mentions_MentionSpanTheme_Night_0_en",0,], ["libraries.designsystem.theme.components.previews_Menu_Menus_en","",0,], ["features.messages.impl.messagecomposer_MessageComposerViewVoice_Day_0_en","features.messages.impl.messagecomposer_MessageComposerViewVoice_Night_0_en",0,], -["features.messages.impl.messagecomposer_MessageComposerView_Day_0_en","features.messages.impl.messagecomposer_MessageComposerView_Night_0_en",20369,], +["features.messages.impl.messagecomposer_MessageComposerView_Day_0_en","features.messages.impl.messagecomposer_MessageComposerView_Night_0_en",20445,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_0_en","features.messages.impl.timeline.components_MessageEventBubble_Night_0_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_1_en","features.messages.impl.timeline.components_MessageEventBubble_Night_1_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_2_en","features.messages.impl.timeline.components_MessageEventBubble_Night_2_en",0,], @@ -761,7 +675,7 @@ export const screenshots = [ ["features.messages.impl.timeline.components_MessageEventBubble_Day_5_en","features.messages.impl.timeline.components_MessageEventBubble_Night_5_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_6_en","features.messages.impl.timeline.components_MessageEventBubble_Night_6_en",0,], ["features.messages.impl.timeline.components_MessageEventBubble_Day_7_en","features.messages.impl.timeline.components_MessageEventBubble_Night_7_en",0,], -["features.messages.impl.timeline.components_MessageShieldView_Day_0_en","features.messages.impl.timeline.components_MessageShieldView_Night_0_en",20369,], +["features.messages.impl.timeline.components_MessageShieldView_Day_0_en","features.messages.impl.timeline.components_MessageShieldView_Night_0_en",20445,], ["features.messages.impl.timeline.components_MessageStateEventContainer_Day_0_en","features.messages.impl.timeline.components_MessageStateEventContainer_Night_0_en",0,], ["features.messages.impl.timeline.components_MessagesReactionButtonAdd_Day_0_en","features.messages.impl.timeline.components_MessagesReactionButtonAdd_Night_0_en",0,], ["features.messages.impl.timeline.components_MessagesReactionButtonExtra_Day_0_en","features.messages.impl.timeline.components_MessagesReactionButtonExtra_Night_0_en",0,], @@ -769,138 +683,142 @@ export const screenshots = [ ["features.messages.impl.timeline.components_MessagesReactionButton_Day_1_en","features.messages.impl.timeline.components_MessagesReactionButton_Night_1_en",0,], ["features.messages.impl.timeline.components_MessagesReactionButton_Day_2_en","features.messages.impl.timeline.components_MessagesReactionButton_Night_2_en",0,], ["features.messages.impl.timeline.components_MessagesReactionButton_Day_3_en","features.messages.impl.timeline.components_MessagesReactionButton_Night_3_en",0,], -["features.messages.impl.topbars_MessagesViewTopBar_Day_0_en","features.messages.impl.topbars_MessagesViewTopBar_Night_0_en",20369,], -["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en",20369,], -["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en",20369,], -["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en",20369,], -["features.messages.impl_MessagesView_Day_0_en","features.messages.impl_MessagesView_Night_0_en",20369,], -["features.messages.impl_MessagesView_Day_10_en","features.messages.impl_MessagesView_Night_10_en",20369,], -["features.messages.impl_MessagesView_Day_1_en","features.messages.impl_MessagesView_Night_1_en",20369,], -["features.messages.impl_MessagesView_Day_2_en","features.messages.impl_MessagesView_Night_2_en",20369,], -["features.messages.impl_MessagesView_Day_3_en","features.messages.impl_MessagesView_Night_3_en",20369,], -["features.messages.impl_MessagesView_Day_4_en","features.messages.impl_MessagesView_Night_4_en",20369,], -["features.messages.impl_MessagesView_Day_5_en","features.messages.impl_MessagesView_Night_5_en",20369,], -["features.messages.impl_MessagesView_Day_6_en","features.messages.impl_MessagesView_Night_6_en",20369,], -["features.messages.impl_MessagesView_Day_7_en","features.messages.impl_MessagesView_Night_7_en",20369,], -["features.messages.impl_MessagesView_Day_8_en","features.messages.impl_MessagesView_Night_8_en",20369,], -["features.messages.impl_MessagesView_Day_9_en","features.messages.impl_MessagesView_Night_9_en",20369,], +["features.messages.impl.topbars_MessagesViewTopBar_Day_0_en","features.messages.impl.topbars_MessagesViewTopBar_Night_0_en",20445,], +["features.messages.impl.crypto.historyvisible_MessagesViewWithHistoryVisible_Day_0_en","features.messages.impl.crypto.historyvisible_MessagesViewWithHistoryVisible_Night_0_en",20445,], +["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_0_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_0_en",20445,], +["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_1_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_1_en",20445,], +["features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Day_2_en","features.messages.impl.crypto.identity_MessagesViewWithIdentityChange_Night_2_en",20445,], +["features.messages.impl_MessagesView_Day_0_en","features.messages.impl_MessagesView_Night_0_en",20445,], +["features.messages.impl_MessagesView_Day_10_en","features.messages.impl_MessagesView_Night_10_en",20445,], +["features.messages.impl_MessagesView_Day_11_en","features.messages.impl_MessagesView_Night_11_en",20445,], +["features.messages.impl_MessagesView_Day_12_en","features.messages.impl_MessagesView_Night_12_en",20445,], +["features.messages.impl_MessagesView_Day_1_en","features.messages.impl_MessagesView_Night_1_en",20445,], +["features.messages.impl_MessagesView_Day_2_en","features.messages.impl_MessagesView_Night_2_en",20445,], +["features.messages.impl_MessagesView_Day_3_en","features.messages.impl_MessagesView_Night_3_en",20445,], +["features.messages.impl_MessagesView_Day_4_en","features.messages.impl_MessagesView_Night_4_en",20445,], +["features.messages.impl_MessagesView_Day_5_en","features.messages.impl_MessagesView_Night_5_en",20445,], +["features.messages.impl_MessagesView_Day_6_en","features.messages.impl_MessagesView_Night_6_en",20445,], +["features.messages.impl_MessagesView_Day_7_en","features.messages.impl_MessagesView_Night_7_en",20445,], +["features.messages.impl_MessagesView_Day_8_en","features.messages.impl_MessagesView_Night_8_en",20445,], +["features.messages.impl_MessagesView_Day_9_en","features.messages.impl_MessagesView_Night_9_en",20445,], ["features.migration.impl_MigrationView_Day_0_en","features.migration.impl_MigrationView_Night_0_en",0,], -["features.migration.impl_MigrationView_Day_1_en","features.migration.impl_MigrationView_Night_1_en",20369,], +["features.migration.impl_MigrationView_Day_1_en","features.migration.impl_MigrationView_Night_1_en",20445,], ["libraries.designsystem.theme.components_ModalBottomSheetDark_Bottom_Sheets_en","",0,], ["libraries.designsystem.theme.components_ModalBottomSheetLight_Bottom_Sheets_en","",0,], ["appicon.element_MonochromeIcon_en","",0,], -["features.preferences.impl.root_MultiAccountSection_Day_0_en","features.preferences.impl.root_MultiAccountSection_Night_0_en",20369,], +["features.preferences.impl.root_MultiAccountSection_Day_0_en","features.preferences.impl.root_MultiAccountSection_Night_0_en",0,], ["libraries.designsystem.components.dialogs_MultipleSelectionDialogContent_Dialogs_en","",0,], ["libraries.designsystem.components.dialogs_MultipleSelectionDialog_Day_0_en","libraries.designsystem.components.dialogs_MultipleSelectionDialog_Night_0_en",0,], ["libraries.designsystem.components.list_MutipleSelectionListItemSelectedTrailingContent_Multiple_selection_List_item_-_selection_in_trailing_content_List_items_en","",0,], ["libraries.designsystem.components.list_MutipleSelectionListItemSelected_Multiple_selection_List_item_-_selection_in_supporting_text_List_items_en","",0,], ["libraries.designsystem.components.list_MutipleSelectionListItem_Multiple_selection_List_item_-_no_selection_List_items_en","",0,], ["libraries.designsystem.theme.components_NavigationBar_App_Bars_en","",0,], -["features.home.impl.components_NewNotificationSoundBanner_Day_0_en","features.home.impl.components_NewNotificationSoundBanner_Night_0_en",20369,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_0_en","features.preferences.impl.notifications_NotificationSettingsView_Night_0_en",20369,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_10_en","features.preferences.impl.notifications_NotificationSettingsView_Night_10_en",20369,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_11_en","features.preferences.impl.notifications_NotificationSettingsView_Night_11_en",20369,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_12_en","features.preferences.impl.notifications_NotificationSettingsView_Night_12_en",20369,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_13_en","features.preferences.impl.notifications_NotificationSettingsView_Night_13_en",20369,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_1_en","features.preferences.impl.notifications_NotificationSettingsView_Night_1_en",20369,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_2_en","features.preferences.impl.notifications_NotificationSettingsView_Night_2_en",20369,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_3_en","features.preferences.impl.notifications_NotificationSettingsView_Night_3_en",20369,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_4_en","features.preferences.impl.notifications_NotificationSettingsView_Night_4_en",20369,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_5_en","features.preferences.impl.notifications_NotificationSettingsView_Night_5_en",20369,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_6_en","features.preferences.impl.notifications_NotificationSettingsView_Night_6_en",20369,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_7_en","features.preferences.impl.notifications_NotificationSettingsView_Night_7_en",20369,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_8_en","features.preferences.impl.notifications_NotificationSettingsView_Night_8_en",20369,], -["features.preferences.impl.notifications_NotificationSettingsView_Day_9_en","features.preferences.impl.notifications_NotificationSettingsView_Night_9_en",20369,], -["features.ftue.impl.notifications_NotificationsOptInView_Day_0_en","features.ftue.impl.notifications_NotificationsOptInView_Night_0_en",20369,], +["features.home.impl.components_NewNotificationSoundBanner_Day_0_en","features.home.impl.components_NewNotificationSoundBanner_Night_0_en",20445,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_0_en","features.preferences.impl.notifications_NotificationSettingsView_Night_0_en",20445,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_10_en","features.preferences.impl.notifications_NotificationSettingsView_Night_10_en",20445,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_11_en","features.preferences.impl.notifications_NotificationSettingsView_Night_11_en",20445,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_12_en","features.preferences.impl.notifications_NotificationSettingsView_Night_12_en",20445,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_13_en","features.preferences.impl.notifications_NotificationSettingsView_Night_13_en",20445,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_1_en","features.preferences.impl.notifications_NotificationSettingsView_Night_1_en",20445,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_2_en","features.preferences.impl.notifications_NotificationSettingsView_Night_2_en",20445,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_3_en","features.preferences.impl.notifications_NotificationSettingsView_Night_3_en",20445,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_4_en","features.preferences.impl.notifications_NotificationSettingsView_Night_4_en",20445,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_5_en","features.preferences.impl.notifications_NotificationSettingsView_Night_5_en",20445,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_6_en","features.preferences.impl.notifications_NotificationSettingsView_Night_6_en",20445,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_7_en","features.preferences.impl.notifications_NotificationSettingsView_Night_7_en",20445,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_8_en","features.preferences.impl.notifications_NotificationSettingsView_Night_8_en",20445,], +["features.preferences.impl.notifications_NotificationSettingsView_Day_9_en","features.preferences.impl.notifications_NotificationSettingsView_Night_9_en",20445,], +["features.ftue.impl.notifications_NotificationsOptInView_Day_0_en","features.ftue.impl.notifications_NotificationsOptInView_Night_0_en",20445,], +["features.linknewdevice.impl.screens.number.component_NumberTextField_Day_0_en","features.linknewdevice.impl.screens.number.component_NumberTextField_Night_0_en",0,], ["libraries.designsystem.atomic.pages_OnBoardingPage_Day_0_en","libraries.designsystem.atomic.pages_OnBoardingPage_Night_0_en",0,], -["features.login.impl.screens.onboarding_OnBoardingView_Day_0_en","features.login.impl.screens.onboarding_OnBoardingView_Night_0_en",20369,], -["features.login.impl.screens.onboarding_OnBoardingView_Day_1_en","features.login.impl.screens.onboarding_OnBoardingView_Night_1_en",20369,], -["features.login.impl.screens.onboarding_OnBoardingView_Day_2_en","features.login.impl.screens.onboarding_OnBoardingView_Night_2_en",20369,], -["features.login.impl.screens.onboarding_OnBoardingView_Day_3_en","features.login.impl.screens.onboarding_OnBoardingView_Night_3_en",20369,], -["features.login.impl.screens.onboarding_OnBoardingView_Day_4_en","features.login.impl.screens.onboarding_OnBoardingView_Night_4_en",20369,], -["features.login.impl.screens.onboarding_OnBoardingView_Day_5_en","features.login.impl.screens.onboarding_OnBoardingView_Night_5_en",20369,], -["features.login.impl.screens.onboarding_OnBoardingView_Day_6_en","features.login.impl.screens.onboarding_OnBoardingView_Night_6_en",20369,], -["features.login.impl.screens.onboarding_OnBoardingView_Day_7_en","features.login.impl.screens.onboarding_OnBoardingView_Night_7_en",20369,], +["features.login.impl.screens.onboarding_OnBoardingView_Day_0_en","features.login.impl.screens.onboarding_OnBoardingView_Night_0_en",20445,], +["features.login.impl.screens.onboarding_OnBoardingView_Day_1_en","features.login.impl.screens.onboarding_OnBoardingView_Night_1_en",20445,], +["features.login.impl.screens.onboarding_OnBoardingView_Day_2_en","features.login.impl.screens.onboarding_OnBoardingView_Night_2_en",20445,], +["features.login.impl.screens.onboarding_OnBoardingView_Day_3_en","features.login.impl.screens.onboarding_OnBoardingView_Night_3_en",20445,], +["features.login.impl.screens.onboarding_OnBoardingView_Day_4_en","features.login.impl.screens.onboarding_OnBoardingView_Night_4_en",20445,], +["features.login.impl.screens.onboarding_OnBoardingView_Day_5_en","features.login.impl.screens.onboarding_OnBoardingView_Night_5_en",20445,], +["features.login.impl.screens.onboarding_OnBoardingView_Day_6_en","features.login.impl.screens.onboarding_OnBoardingView_Night_6_en",20445,], +["features.login.impl.screens.onboarding_OnBoardingView_Day_7_en","features.login.impl.screens.onboarding_OnBoardingView_Night_7_en",20445,], ["libraries.designsystem.background_OnboardingBackground_Day_0_en","libraries.designsystem.background_OnboardingBackground_Night_0_en",0,], -["libraries.matrix.ui.components_OrganizationHeader_Day_0_en","libraries.matrix.ui.components_OrganizationHeader_Night_0_en",20369,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_0_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_0_en",20369,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_10_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_10_en",20369,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_11_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_11_en",20369,], +["libraries.matrix.ui.components_OrganizationHeader_Day_0_en","libraries.matrix.ui.components_OrganizationHeader_Night_0_en",20445,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_0_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_0_en",20445,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_10_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_10_en",20445,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_11_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_11_en",20445,], ["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_12_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_12_en",0,], ["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_13_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_13_en",0,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_1_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_1_en",20369,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_2_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_2_en",20369,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_3_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_3_en",20369,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_4_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_4_en",20369,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_5_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_5_en",20369,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_6_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_6_en",20369,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_7_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_7_en",20369,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_8_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_8_en",20369,], -["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_9_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_9_en",20369,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_1_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_1_en",20445,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_2_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_2_en",20445,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_3_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_3_en",20445,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_4_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_4_en",20445,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_5_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_5_en",20445,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_6_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_6_en",20445,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_7_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_7_en",20445,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_8_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_8_en",20445,], +["features.verifysession.impl.outgoing_OutgoingVerificationView_Day_9_en","features.verifysession.impl.outgoing_OutgoingVerificationView_Night_9_en",20445,], ["libraries.designsystem.theme.components_OutlinedButtonLargeLowPadding_Buttons_en","",0,], ["libraries.designsystem.theme.components_OutlinedButtonLarge_Buttons_en","",0,], ["libraries.designsystem.theme.components_OutlinedButtonMediumLowPadding_Buttons_en","",0,], ["libraries.designsystem.theme.components_OutlinedButtonMedium_Buttons_en","",0,], ["libraries.designsystem.theme.components_OutlinedButtonSmall_Buttons_en","",0,], -["libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Day_0_en","libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Night_0_en",20369,], -["features.changeroommemberroles.impl_PendingMemberRowWithLongName_Day_0_en","features.changeroommemberroles.impl_PendingMemberRowWithLongName_Night_0_en",20369,], -["libraries.permissions.api_PermissionsView_Day_0_en","libraries.permissions.api_PermissionsView_Night_0_en",20369,], -["libraries.permissions.api_PermissionsView_Day_1_en","libraries.permissions.api_PermissionsView_Night_1_en",20369,], -["libraries.permissions.api_PermissionsView_Day_2_en","libraries.permissions.api_PermissionsView_Night_2_en",20369,], -["libraries.permissions.api_PermissionsView_Day_3_en","libraries.permissions.api_PermissionsView_Night_3_en",20369,], +["libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Day_0_en","libraries.mediaviewer.impl.local.pdf_PdfPagesErrorView_Night_0_en",20445,], +["features.rolesandpermissions.impl.roles_PendingMemberRowWithLongName_Day_0_en","features.rolesandpermissions.impl.roles_PendingMemberRowWithLongName_Night_0_en",0,], +["libraries.permissions.api_PermissionsView_Day_0_en","libraries.permissions.api_PermissionsView_Night_0_en",20445,], +["libraries.permissions.api_PermissionsView_Day_1_en","libraries.permissions.api_PermissionsView_Night_1_en",20445,], +["libraries.permissions.api_PermissionsView_Day_2_en","libraries.permissions.api_PermissionsView_Night_2_en",20445,], +["libraries.permissions.api_PermissionsView_Day_3_en","libraries.permissions.api_PermissionsView_Night_3_en",20445,], ["features.lockscreen.impl.components_PinEntryTextField_Day_0_en","features.lockscreen.impl.components_PinEntryTextField_Night_0_en",0,], ["libraries.designsystem.components_PinIcon_Day_0_en","libraries.designsystem.components_PinIcon_Night_0_en",0,], ["features.lockscreen.impl.unlock.keypad_PinKeypad_Day_0_en","features.lockscreen.impl.unlock.keypad_PinKeypad_Night_0_en",0,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_0_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_0_en",20369,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_1_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_1_en",20369,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_2_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_2_en",20369,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_3_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_3_en",20369,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_4_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_4_en",20369,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_5_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_5_en",20369,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_6_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_6_en",20369,], -["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_7_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_7_en",20369,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_0_en","features.lockscreen.impl.unlock_PinUnlockView_Night_0_en",20369,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_1_en","features.lockscreen.impl.unlock_PinUnlockView_Night_1_en",20369,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_2_en","features.lockscreen.impl.unlock_PinUnlockView_Night_2_en",20369,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_3_en","features.lockscreen.impl.unlock_PinUnlockView_Night_3_en",20369,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_4_en","features.lockscreen.impl.unlock_PinUnlockView_Night_4_en",20369,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_5_en","features.lockscreen.impl.unlock_PinUnlockView_Night_5_en",20369,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_6_en","features.lockscreen.impl.unlock_PinUnlockView_Night_6_en",20369,], -["features.lockscreen.impl.unlock_PinUnlockView_Day_7_en","features.lockscreen.impl.unlock_PinUnlockView_Night_7_en",20369,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_0_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_0_en",20445,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_1_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_1_en",20445,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_2_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_2_en",20445,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_3_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_3_en",20445,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_4_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_4_en",20445,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_5_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_5_en",20445,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_6_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_6_en",20445,], +["features.lockscreen.impl.unlock_PinUnlockViewInApp_Day_7_en","features.lockscreen.impl.unlock_PinUnlockViewInApp_Night_7_en",20445,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_0_en","features.lockscreen.impl.unlock_PinUnlockView_Night_0_en",20445,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_1_en","features.lockscreen.impl.unlock_PinUnlockView_Night_1_en",20445,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_2_en","features.lockscreen.impl.unlock_PinUnlockView_Night_2_en",20445,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_3_en","features.lockscreen.impl.unlock_PinUnlockView_Night_3_en",20445,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_4_en","features.lockscreen.impl.unlock_PinUnlockView_Night_4_en",20445,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_5_en","features.lockscreen.impl.unlock_PinUnlockView_Night_5_en",20445,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_6_en","features.lockscreen.impl.unlock_PinUnlockView_Night_6_en",20445,], +["features.lockscreen.impl.unlock_PinUnlockView_Day_7_en","features.lockscreen.impl.unlock_PinUnlockView_Night_7_en",20445,], ["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_0_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_0_en",0,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_10_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_10_en",20369,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_1_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_1_en",20369,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_2_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_2_en",20369,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_3_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_3_en",20369,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_4_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_4_en",20369,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_5_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_5_en",20369,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_6_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_6_en",20369,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_7_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_7_en",20369,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_8_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_8_en",20369,], -["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_9_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_9_en",20369,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_0_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_0_en",20369,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_1_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_1_en",20369,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_2_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_2_en",20369,], -["features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_3_en",20369,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_10_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_10_en",20445,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_1_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_1_en",20445,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_2_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_2_en",20445,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_3_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_3_en",20445,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_4_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_4_en",20445,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_5_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_5_en",20445,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_6_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_6_en",20445,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_7_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_7_en",20445,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_8_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_8_en",20445,], +["features.messages.impl.pinned.banner_PinnedMessagesBannerView_Day_9_en","features.messages.impl.pinned.banner_PinnedMessagesBannerView_Night_9_en",20445,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_0_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_0_en",20445,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_1_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_1_en",20445,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_2_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_2_en",20445,], +["features.messages.impl.pinned.list_PinnedMessagesListView_Day_3_en","features.messages.impl.pinned.list_PinnedMessagesListView_Night_3_en",20445,], ["libraries.designsystem.atomic.atoms_PlaceholderAtom_Day_0_en","libraries.designsystem.atomic.atoms_PlaceholderAtom_Night_0_en",0,], -["features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Night_0_en",20369,], -["features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Night_0_en",20369,], -["features.poll.api.pollcontent_PollAnswerViewEndedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedSelected_Night_0_en",20369,], -["features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Night_0_en",20369,], -["features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Night_0_en",20369,], +["features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedNotSelected_Night_0_en",20445,], +["features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewDisclosedSelected_Night_0_en",20445,], +["features.poll.api.pollcontent_PollAnswerViewEndedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedSelected_Night_0_en",20445,], +["features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerNotSelected_Night_0_en",20445,], +["features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewEndedWinnerSelected_Night_0_en",20445,], ["features.poll.api.pollcontent_PollAnswerViewUndisclosedNotSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewUndisclosedNotSelected_Night_0_en",0,], ["features.poll.api.pollcontent_PollAnswerViewUndisclosedSelected_Day_0_en","features.poll.api.pollcontent_PollAnswerViewUndisclosedSelected_Night_0_en",0,], -["features.poll.api.pollcontent_PollContentViewCreatorEditable_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEditable_Night_0_en",20369,], -["features.poll.api.pollcontent_PollContentViewCreatorEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEnded_Night_0_en",20369,], -["features.poll.api.pollcontent_PollContentViewCreator_Day_0_en","features.poll.api.pollcontent_PollContentViewCreator_Night_0_en",20369,], -["features.poll.api.pollcontent_PollContentViewDisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewDisclosed_Night_0_en",20369,], -["features.poll.api.pollcontent_PollContentViewEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewEnded_Night_0_en",20369,], -["features.poll.api.pollcontent_PollContentViewUndisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewUndisclosed_Night_0_en",20369,], -["features.poll.impl.history_PollHistoryView_Day_0_en","features.poll.impl.history_PollHistoryView_Night_0_en",20369,], -["features.poll.impl.history_PollHistoryView_Day_1_en","features.poll.impl.history_PollHistoryView_Night_1_en",20369,], -["features.poll.impl.history_PollHistoryView_Day_2_en","features.poll.impl.history_PollHistoryView_Night_2_en",20369,], -["features.poll.impl.history_PollHistoryView_Day_3_en","features.poll.impl.history_PollHistoryView_Night_3_en",20369,], -["features.poll.impl.history_PollHistoryView_Day_4_en","features.poll.impl.history_PollHistoryView_Night_4_en",20369,], +["features.poll.api.pollcontent_PollContentViewCreatorEditable_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEditable_Night_0_en",20445,], +["features.poll.api.pollcontent_PollContentViewCreatorEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewCreatorEnded_Night_0_en",20445,], +["features.poll.api.pollcontent_PollContentViewCreator_Day_0_en","features.poll.api.pollcontent_PollContentViewCreator_Night_0_en",20445,], +["features.poll.api.pollcontent_PollContentViewDisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewDisclosed_Night_0_en",20445,], +["features.poll.api.pollcontent_PollContentViewEnded_Day_0_en","features.poll.api.pollcontent_PollContentViewEnded_Night_0_en",20445,], +["features.poll.api.pollcontent_PollContentViewUndisclosed_Day_0_en","features.poll.api.pollcontent_PollContentViewUndisclosed_Night_0_en",20445,], +["features.poll.impl.history_PollHistoryView_Day_0_en","features.poll.impl.history_PollHistoryView_Night_0_en",20445,], +["features.poll.impl.history_PollHistoryView_Day_1_en","features.poll.impl.history_PollHistoryView_Night_1_en",20445,], +["features.poll.impl.history_PollHistoryView_Day_2_en","features.poll.impl.history_PollHistoryView_Night_2_en",20445,], +["features.poll.impl.history_PollHistoryView_Day_3_en","features.poll.impl.history_PollHistoryView_Night_3_en",20445,], +["features.poll.impl.history_PollHistoryView_Day_4_en","features.poll.impl.history_PollHistoryView_Night_4_en",20445,], ["features.poll.api.pollcontent_PollTitleView_Day_0_en","features.poll.api.pollcontent_PollTitleView_Night_0_en",0,], ["libraries.designsystem.components.preferences_PreferenceCategory_Preferences_en","",0,], ["libraries.designsystem.components.preferences_PreferenceCheckbox_Preferences_en","",0,], @@ -914,208 +832,207 @@ export const screenshots = [ ["libraries.designsystem.components.preferences_PreferenceRow_Preferences_en","",0,], ["libraries.designsystem.components.preferences_PreferenceSlide_Preferences_en","",0,], ["libraries.designsystem.components.preferences_PreferenceSwitch_Preferences_en","",0,], -["features.preferences.impl.root_PreferencesRootViewDark_0_en","",20369,], -["features.preferences.impl.root_PreferencesRootViewDark_1_en","",20369,], -["features.preferences.impl.root_PreferencesRootViewLight_0_en","",20369,], -["features.preferences.impl.root_PreferencesRootViewLight_1_en","",20369,], +["features.preferences.impl.root_PreferencesRootViewDark_0_en","",20445,], +["features.preferences.impl.root_PreferencesRootViewDark_1_en","",20445,], +["features.preferences.impl.root_PreferencesRootViewLight_0_en","",20445,], +["features.preferences.impl.root_PreferencesRootViewLight_1_en","",20445,], ["features.messages.impl.timeline.components.event_ProgressButton_Day_0_en","features.messages.impl.timeline.components.event_ProgressButton_Night_0_en",0,], -["libraries.designsystem.components_ProgressDialogContent_Dialogs_en","",20369,], -["libraries.designsystem.components_ProgressDialogWithContent_Day_0_en","libraries.designsystem.components_ProgressDialogWithContent_Night_0_en",20369,], +["libraries.designsystem.components_ProgressDialogContent_Dialogs_en","",20445,], +["libraries.designsystem.components_ProgressDialogWithContent_Day_0_en","libraries.designsystem.components_ProgressDialogWithContent_Night_0_en",20445,], ["libraries.designsystem.components_ProgressDialogWithTextAndContent_Day_0_en","libraries.designsystem.components_ProgressDialogWithTextAndContent_Night_0_en",0,], -["libraries.designsystem.components_ProgressDialog_Day_0_en","libraries.designsystem.components_ProgressDialog_Night_0_en",20369,], -["features.messages.impl.timeline.protection_ProtectedView_Day_0_en","features.messages.impl.timeline.protection_ProtectedView_Night_0_en",20369,], -["features.messages.impl.timeline.protection_ProtectedView_Day_1_en","features.messages.impl.timeline.protection_ProtectedView_Night_1_en",20369,], -["features.messages.impl.timeline.protection_ProtectedView_Day_2_en","features.messages.impl.timeline.protection_ProtectedView_Night_2_en",20369,], -["features.messages.impl.timeline.protection_ProtectedView_Day_3_en","features.messages.impl.timeline.protection_ProtectedView_Night_3_en",20369,], -["libraries.troubleshoot.impl.history_PushHistoryView_Day_0_en","libraries.troubleshoot.impl.history_PushHistoryView_Night_0_en",20369,], -["libraries.troubleshoot.impl.history_PushHistoryView_Day_1_en","libraries.troubleshoot.impl.history_PushHistoryView_Night_1_en",20369,], -["libraries.troubleshoot.impl.history_PushHistoryView_Day_2_en","libraries.troubleshoot.impl.history_PushHistoryView_Night_2_en",20369,], -["libraries.troubleshoot.impl.history_PushHistoryView_Day_3_en","libraries.troubleshoot.impl.history_PushHistoryView_Night_3_en",20369,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_0_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_0_en",20369,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_1_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_1_en",20369,], -["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_2_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_2_en",20369,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_0_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_0_en",20369,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_1_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_1_en",20369,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_2_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_2_en",20369,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_3_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_3_en",20369,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_4_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_4_en",20369,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_5_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_5_en",20369,], -["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_6_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_6_en",20369,], -["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_0_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_0_en",20369,], -["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_1_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_1_en",20369,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_0_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_0_en",20369,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_1_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_1_en",20369,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_2_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_2_en",20369,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_3_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_3_en",20369,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_4_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_4_en",20369,], -["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_5_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_5_en",20369,], +["libraries.designsystem.components_ProgressDialog_Day_0_en","libraries.designsystem.components_ProgressDialog_Night_0_en",20445,], +["features.messages.impl.timeline.protection_ProtectedView_Day_0_en","features.messages.impl.timeline.protection_ProtectedView_Night_0_en",20445,], +["features.messages.impl.timeline.protection_ProtectedView_Day_1_en","features.messages.impl.timeline.protection_ProtectedView_Night_1_en",20445,], +["features.messages.impl.timeline.protection_ProtectedView_Day_2_en","features.messages.impl.timeline.protection_ProtectedView_Night_2_en",20445,], +["features.messages.impl.timeline.protection_ProtectedView_Day_3_en","features.messages.impl.timeline.protection_ProtectedView_Night_3_en",20445,], +["libraries.troubleshoot.impl.history_PushHistoryView_Day_0_en","libraries.troubleshoot.impl.history_PushHistoryView_Night_0_en",20445,], +["libraries.troubleshoot.impl.history_PushHistoryView_Day_1_en","libraries.troubleshoot.impl.history_PushHistoryView_Night_1_en",20445,], +["libraries.troubleshoot.impl.history_PushHistoryView_Day_2_en","libraries.troubleshoot.impl.history_PushHistoryView_Night_2_en",20445,], +["libraries.troubleshoot.impl.history_PushHistoryView_Day_3_en","libraries.troubleshoot.impl.history_PushHistoryView_Night_3_en",20445,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_0_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_0_en",20445,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_1_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_1_en",20445,], +["features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Day_2_en","features.login.impl.screens.qrcode.confirmation_QrCodeConfirmationView_Night_2_en",20445,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_0_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_0_en",20445,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_1_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_1_en",20445,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_2_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_2_en",20445,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_3_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_3_en",20445,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_4_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_4_en",20445,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_5_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_5_en",20445,], +["features.login.impl.screens.qrcode.error_QrCodeErrorView_Day_6_en","features.login.impl.screens.qrcode.error_QrCodeErrorView_Night_6_en",20445,], +["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_0_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_0_en",20445,], +["features.login.impl.screens.qrcode.intro_QrCodeIntroView_Day_1_en","features.login.impl.screens.qrcode.intro_QrCodeIntroView_Night_1_en",20445,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_0_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_0_en",20445,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_1_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_1_en",20445,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_2_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_2_en",20445,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_3_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_3_en",20445,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_4_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_4_en",20445,], +["features.login.impl.screens.qrcode.scan_QrCodeScanView_Day_5_en","features.login.impl.screens.qrcode.scan_QrCodeScanView_Night_5_en",20445,], +["libraries.qrcode_QrCodeView_en","",0,], ["libraries.designsystem.theme.components_RadioButton_Toggles_en","",0,], -["features.rageshake.api.detection_RageshakeDialogContent_Day_0_en","features.rageshake.api.detection_RageshakeDialogContent_Night_0_en",20369,], -["features.rageshake.api.preferences_RageshakePreferencesView_Day_0_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_0_en",20369,], +["features.rageshake.api.detection_RageshakeDialogContent_Day_0_en","features.rageshake.api.detection_RageshakeDialogContent_Night_0_en",20445,], +["features.rageshake.api.preferences_RageshakePreferencesView_Day_0_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_0_en",20445,], ["features.rageshake.api.preferences_RageshakePreferencesView_Day_1_en","features.rageshake.api.preferences_RageshakePreferencesView_Night_1_en",0,], ["features.messages.impl.timeline.components.reactionsummary_ReactionSummaryViewContent_Day_0_en","features.messages.impl.timeline.components.reactionsummary_ReactionSummaryViewContent_Night_0_en",0,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_0_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_0_en",20369,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_1_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_1_en",20369,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_2_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_2_en",20369,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_3_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_3_en",20369,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_4_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_4_en",20369,], -["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_5_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_5_en",20369,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_0_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_0_en",20369,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_10_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_10_en",20369,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_11_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_11_en",20369,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_12_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_12_en",20369,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_13_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_13_en",20369,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_14_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_14_en",20369,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_1_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_1_en",20369,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_2_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_2_en",20369,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_3_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_3_en",20369,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_4_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_4_en",20369,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_5_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_5_en",20369,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_6_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_6_en",20369,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_7_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_7_en",20369,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_8_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_8_en",20369,], -["features.securebackup.impl.setup.views_RecoveryKeyView_Day_9_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_9_en",20369,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_0_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_0_en",20445,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_1_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_1_en",20445,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_2_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_2_en",20445,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_3_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_3_en",20445,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_4_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_4_en",20445,], +["features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Day_5_en","features.messages.impl.timeline.components.receipt.bottomsheet_ReadReceiptBottomSheet_Night_5_en",20445,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_0_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_0_en",20445,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_10_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_10_en",20445,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_11_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_11_en",20445,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_12_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_12_en",20445,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_13_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_13_en",20445,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_14_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_14_en",20445,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_1_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_1_en",20445,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_2_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_2_en",20445,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_3_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_3_en",20445,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_4_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_4_en",20445,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_5_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_5_en",20445,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_6_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_6_en",20445,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_7_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_7_en",20445,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_8_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_8_en",20445,], +["features.securebackup.impl.setup.views_RecoveryKeyView_Day_9_en","features.securebackup.impl.setup.views_RecoveryKeyView_Night_9_en",20445,], ["libraries.designsystem.atomic.atoms_RedIndicatorAtom_Day_0_en","libraries.designsystem.atomic.atoms_RedIndicatorAtom_Night_0_en",0,], ["features.messages.impl.timeline.components_ReplySwipeIndicator_Day_0_en","features.messages.impl.timeline.components_ReplySwipeIndicator_Night_0_en",0,], -["features.messages.impl.report_ReportMessageView_Day_0_en","features.messages.impl.report_ReportMessageView_Night_0_en",20369,], -["features.messages.impl.report_ReportMessageView_Day_1_en","features.messages.impl.report_ReportMessageView_Night_1_en",20369,], -["features.messages.impl.report_ReportMessageView_Day_2_en","features.messages.impl.report_ReportMessageView_Night_2_en",20369,], -["features.messages.impl.report_ReportMessageView_Day_3_en","features.messages.impl.report_ReportMessageView_Night_3_en",20369,], -["features.messages.impl.report_ReportMessageView_Day_4_en","features.messages.impl.report_ReportMessageView_Night_4_en",20369,], -["features.messages.impl.report_ReportMessageView_Day_5_en","features.messages.impl.report_ReportMessageView_Night_5_en",20369,], -["features.reportroom.impl_ReportRoomView_Day_0_en","features.reportroom.impl_ReportRoomView_Night_0_en",20369,], -["features.reportroom.impl_ReportRoomView_Day_1_en","features.reportroom.impl_ReportRoomView_Night_1_en",20369,], -["features.reportroom.impl_ReportRoomView_Day_2_en","features.reportroom.impl_ReportRoomView_Night_2_en",20369,], -["features.reportroom.impl_ReportRoomView_Day_3_en","features.reportroom.impl_ReportRoomView_Night_3_en",20369,], -["features.reportroom.impl_ReportRoomView_Day_4_en","features.reportroom.impl_ReportRoomView_Night_4_en",20369,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_0_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_0_en",20369,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_1_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_1_en",20369,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_2_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_2_en",20369,], -["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_3_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_3_en",20369,], -["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_0_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_0_en",20369,], -["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_1_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_1_en",20369,], +["features.messages.impl.report_ReportMessageView_Day_0_en","features.messages.impl.report_ReportMessageView_Night_0_en",20445,], +["features.messages.impl.report_ReportMessageView_Day_1_en","features.messages.impl.report_ReportMessageView_Night_1_en",20445,], +["features.messages.impl.report_ReportMessageView_Day_2_en","features.messages.impl.report_ReportMessageView_Night_2_en",20445,], +["features.messages.impl.report_ReportMessageView_Day_3_en","features.messages.impl.report_ReportMessageView_Night_3_en",20445,], +["features.messages.impl.report_ReportMessageView_Day_4_en","features.messages.impl.report_ReportMessageView_Night_4_en",20445,], +["features.messages.impl.report_ReportMessageView_Day_5_en","features.messages.impl.report_ReportMessageView_Night_5_en",20445,], +["features.reportroom.impl_ReportRoomView_Day_0_en","features.reportroom.impl_ReportRoomView_Night_0_en",20445,], +["features.reportroom.impl_ReportRoomView_Day_1_en","features.reportroom.impl_ReportRoomView_Night_1_en",20445,], +["features.reportroom.impl_ReportRoomView_Day_2_en","features.reportroom.impl_ReportRoomView_Night_2_en",20445,], +["features.reportroom.impl_ReportRoomView_Day_3_en","features.reportroom.impl_ReportRoomView_Night_3_en",20445,], +["features.reportroom.impl_ReportRoomView_Day_4_en","features.reportroom.impl_ReportRoomView_Night_4_en",20445,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_0_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_0_en",20445,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_1_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_1_en",20445,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_2_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_2_en",20445,], +["features.securebackup.impl.reset.password_ResetIdentityPasswordView_Day_3_en","features.securebackup.impl.reset.password_ResetIdentityPasswordView_Night_3_en",20445,], +["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_0_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_0_en",20445,], +["features.securebackup.impl.reset.root_ResetIdentityRootView_Day_1_en","features.securebackup.impl.reset.root_ResetIdentityRootView_Night_1_en",20445,], ["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_0_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_0_en",0,], -["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_1_en",20369,], -["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_2_en",20369,], -["libraries.designsystem.components.dialogs_RetryDialogContent_Dialogs_en","",20369,], -["libraries.designsystem.components.dialogs_RetryDialog_Day_0_en","libraries.designsystem.components.dialogs_RetryDialog_Night_0_en",20369,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_0_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_0_en",20369,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_1_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_1_en",20369,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_2_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_2_en",20369,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_3_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_3_en",20369,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_4_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_4_en",20369,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_5_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_5_en",20369,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_6_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_6_en",20369,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_7_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_7_en",20369,], -["features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Day_8_en","features.roomdetails.impl.rolesandpermissions_RolesAndPermissionsView_Night_8_en",20369,], +["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_1_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_1_en",20445,], +["features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Day_2_en","features.messages.impl.crypto.sendfailure.resolve_ResolveVerifiedUserSendFailureView_Night_2_en",20445,], +["libraries.designsystem.components.dialogs_RetryDialogContent_Dialogs_en","",20445,], +["libraries.designsystem.components.dialogs_RetryDialog_Day_0_en","libraries.designsystem.components.dialogs_RetryDialog_Night_0_en",20445,], +["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_0_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_0_en",20445,], +["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_1_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_1_en",20445,], +["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_2_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_2_en",20445,], +["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_3_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_3_en",20445,], +["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_4_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_4_en",20445,], +["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_5_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_5_en",20445,], +["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_6_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_6_en",20445,], +["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_7_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_7_en",20445,], +["features.rolesandpermissions.impl.root_RolesAndPermissionsView_Day_8_en","features.rolesandpermissions.impl.root_RolesAndPermissionsView_Night_8_en",20445,], ["libraries.matrix.ui.room.address_RoomAddressField_Day_0_en","libraries.matrix.ui.room.address_RoomAddressField_Night_0_en",0,], ["features.roomaliasresolver.impl_RoomAliasResolverView_Day_0_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_0_en",0,], -["features.roomaliasresolver.impl_RoomAliasResolverView_Day_1_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_1_en",20369,], -["features.roomaliasresolver.impl_RoomAliasResolverView_Day_2_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_2_en",20369,], -["features.roomdetails.impl_RoomDetailsDark_0_en","",20369,], -["features.roomdetails.impl_RoomDetailsDark_10_en","",20369,], -["features.roomdetails.impl_RoomDetailsDark_11_en","",20369,], -["features.roomdetails.impl_RoomDetailsDark_12_en","",20369,], -["features.roomdetails.impl_RoomDetailsDark_13_en","",20369,], -["features.roomdetails.impl_RoomDetailsDark_14_en","",20369,], -["features.roomdetails.impl_RoomDetailsDark_15_en","",20369,], -["features.roomdetails.impl_RoomDetailsDark_16_en","",20369,], -["features.roomdetails.impl_RoomDetailsDark_17_en","",20369,], -["features.roomdetails.impl_RoomDetailsDark_18_en","",20369,], -["features.roomdetails.impl_RoomDetailsDark_19_en","",20369,], -["features.roomdetails.impl_RoomDetailsDark_1_en","",20369,], -["features.roomdetails.impl_RoomDetailsDark_2_en","",20369,], -["features.roomdetails.impl_RoomDetailsDark_3_en","",20369,], -["features.roomdetails.impl_RoomDetailsDark_4_en","",20369,], -["features.roomdetails.impl_RoomDetailsDark_5_en","",20369,], -["features.roomdetails.impl_RoomDetailsDark_6_en","",20369,], -["features.roomdetails.impl_RoomDetailsDark_7_en","",20369,], -["features.roomdetails.impl_RoomDetailsDark_8_en","",20369,], -["features.roomdetails.impl_RoomDetailsDark_9_en","",20369,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_0_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_0_en",20369,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_1_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_1_en",20369,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_2_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_2_en",20369,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_3_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_3_en",20369,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_4_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_4_en",20369,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_5_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_5_en",20369,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_6_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_6_en",20369,], -["features.roomdetails.impl.edit_RoomDetailsEditView_Day_7_en","features.roomdetails.impl.edit_RoomDetailsEditView_Night_7_en",20369,], -["features.roomdetails.impl_RoomDetails_0_en","",20369,], -["features.roomdetails.impl_RoomDetails_10_en","",20369,], -["features.roomdetails.impl_RoomDetails_11_en","",20369,], -["features.roomdetails.impl_RoomDetails_12_en","",20369,], -["features.roomdetails.impl_RoomDetails_13_en","",20369,], -["features.roomdetails.impl_RoomDetails_14_en","",20369,], -["features.roomdetails.impl_RoomDetails_15_en","",20369,], -["features.roomdetails.impl_RoomDetails_16_en","",20369,], -["features.roomdetails.impl_RoomDetails_17_en","",20369,], -["features.roomdetails.impl_RoomDetails_18_en","",20369,], -["features.roomdetails.impl_RoomDetails_19_en","",20369,], -["features.roomdetails.impl_RoomDetails_1_en","",20369,], -["features.roomdetails.impl_RoomDetails_2_en","",20369,], -["features.roomdetails.impl_RoomDetails_3_en","",20369,], -["features.roomdetails.impl_RoomDetails_4_en","",20369,], -["features.roomdetails.impl_RoomDetails_5_en","",20369,], -["features.roomdetails.impl_RoomDetails_6_en","",20369,], -["features.roomdetails.impl_RoomDetails_7_en","",20369,], -["features.roomdetails.impl_RoomDetails_8_en","",20369,], -["features.roomdetails.impl_RoomDetails_9_en","",20369,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_0_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_0_en",20369,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_1_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_1_en",20369,], -["features.roomdirectory.impl.root_RoomDirectoryView_Day_2_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_2_en",20369,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_0_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_0_en",20369,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_1_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_1_en",20369,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_2_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_2_en",20369,], -["features.roomdetails.impl.invite_RoomInviteMembersView_Day_3_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_3_en",20369,], -["features.home.impl.components_RoomListContentView_Day_0_en","features.home.impl.components_RoomListContentView_Night_0_en",20369,], -["features.home.impl.components_RoomListContentView_Day_1_en","features.home.impl.components_RoomListContentView_Night_1_en",20369,], +["features.roomaliasresolver.impl_RoomAliasResolverView_Day_1_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_1_en",20445,], +["features.roomaliasresolver.impl_RoomAliasResolverView_Day_2_en","features.roomaliasresolver.impl_RoomAliasResolverView_Night_2_en",20445,], +["features.roomdetails.impl_RoomDetailsDark_0_en","",20445,], +["features.roomdetails.impl_RoomDetailsDark_10_en","",20445,], +["features.roomdetails.impl_RoomDetailsDark_11_en","",20445,], +["features.roomdetails.impl_RoomDetailsDark_12_en","",20445,], +["features.roomdetails.impl_RoomDetailsDark_13_en","",20445,], +["features.roomdetails.impl_RoomDetailsDark_14_en","",20445,], +["features.roomdetails.impl_RoomDetailsDark_15_en","",20445,], +["features.roomdetails.impl_RoomDetailsDark_16_en","",20445,], +["features.roomdetails.impl_RoomDetailsDark_17_en","",20445,], +["features.roomdetails.impl_RoomDetailsDark_18_en","",20445,], +["features.roomdetails.impl_RoomDetailsDark_19_en","",20445,], +["features.roomdetails.impl_RoomDetailsDark_1_en","",20445,], +["features.roomdetails.impl_RoomDetailsDark_2_en","",20445,], +["features.roomdetails.impl_RoomDetailsDark_3_en","",20445,], +["features.roomdetails.impl_RoomDetailsDark_4_en","",20445,], +["features.roomdetails.impl_RoomDetailsDark_5_en","",20445,], +["features.roomdetails.impl_RoomDetailsDark_6_en","",20445,], +["features.roomdetails.impl_RoomDetailsDark_7_en","",20445,], +["features.roomdetails.impl_RoomDetailsDark_8_en","",20445,], +["features.roomdetails.impl_RoomDetailsDark_9_en","",20445,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_0_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_0_en",20445,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_1_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_1_en",20445,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_2_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_2_en",20445,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_3_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_3_en",20445,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_4_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_4_en",20445,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_5_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_5_en",20445,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_6_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_6_en",20445,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_7_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_7_en",20445,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_8_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_8_en",20445,], +["features.roomdetailsedit.impl_RoomDetailsEditView_Day_9_en","features.roomdetailsedit.impl_RoomDetailsEditView_Night_9_en",20445,], +["features.roomdetails.impl_RoomDetails_0_en","",20445,], +["features.roomdetails.impl_RoomDetails_10_en","",20445,], +["features.roomdetails.impl_RoomDetails_11_en","",20445,], +["features.roomdetails.impl_RoomDetails_12_en","",20445,], +["features.roomdetails.impl_RoomDetails_13_en","",20445,], +["features.roomdetails.impl_RoomDetails_14_en","",20445,], +["features.roomdetails.impl_RoomDetails_15_en","",20445,], +["features.roomdetails.impl_RoomDetails_16_en","",20445,], +["features.roomdetails.impl_RoomDetails_17_en","",20445,], +["features.roomdetails.impl_RoomDetails_18_en","",20445,], +["features.roomdetails.impl_RoomDetails_19_en","",20445,], +["features.roomdetails.impl_RoomDetails_1_en","",20445,], +["features.roomdetails.impl_RoomDetails_2_en","",20445,], +["features.roomdetails.impl_RoomDetails_3_en","",20445,], +["features.roomdetails.impl_RoomDetails_4_en","",20445,], +["features.roomdetails.impl_RoomDetails_5_en","",20445,], +["features.roomdetails.impl_RoomDetails_6_en","",20445,], +["features.roomdetails.impl_RoomDetails_7_en","",20445,], +["features.roomdetails.impl_RoomDetails_8_en","",20445,], +["features.roomdetails.impl_RoomDetails_9_en","",20445,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_0_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_0_en",20445,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_1_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_1_en",20445,], +["features.roomdirectory.impl.root_RoomDirectoryView_Day_2_en","features.roomdirectory.impl.root_RoomDirectoryView_Night_2_en",20445,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_0_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_0_en",20445,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_1_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_1_en",20445,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_2_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_2_en",20445,], +["features.roomdetails.impl.invite_RoomInviteMembersView_Day_3_en","features.roomdetails.impl.invite_RoomInviteMembersView_Night_3_en",20445,], +["features.home.impl.components_RoomListContentView_Day_0_en","features.home.impl.components_RoomListContentView_Night_0_en",20445,], +["features.home.impl.components_RoomListContentView_Day_1_en","features.home.impl.components_RoomListContentView_Night_1_en",20445,], ["features.home.impl.components_RoomListContentView_Day_2_en","features.home.impl.components_RoomListContentView_Night_2_en",0,], -["features.home.impl.components_RoomListContentView_Day_3_en","features.home.impl.components_RoomListContentView_Night_3_en",20369,], -["features.home.impl.components_RoomListContentView_Day_4_en","features.home.impl.components_RoomListContentView_Night_4_en",20369,], -["features.home.impl.components_RoomListContentView_Day_5_en","features.home.impl.components_RoomListContentView_Night_5_en",20369,], -["features.home.impl.roomlist_RoomListDeclineInviteMenuContent_Day_0_en","features.home.impl.roomlist_RoomListDeclineInviteMenuContent_Night_0_en",20369,], -["features.home.impl.filters_RoomListFiltersView_Day_0_en","features.home.impl.filters_RoomListFiltersView_Night_0_en",20369,], -["features.home.impl.filters_RoomListFiltersView_Day_1_en","features.home.impl.filters_RoomListFiltersView_Night_1_en",20369,], -["features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_0_en","features.home.impl.roomlist_RoomListModalBottomSheetContent_Night_0_en",20369,], -["features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_1_en","features.home.impl.roomlist_RoomListModalBottomSheetContent_Night_1_en",20369,], -["features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_2_en","features.home.impl.roomlist_RoomListModalBottomSheetContent_Night_2_en",20369,], +["features.home.impl.components_RoomListContentView_Day_3_en","features.home.impl.components_RoomListContentView_Night_3_en",20445,], +["features.home.impl.components_RoomListContentView_Day_4_en","features.home.impl.components_RoomListContentView_Night_4_en",20445,], +["features.home.impl.components_RoomListContentView_Day_5_en","features.home.impl.components_RoomListContentView_Night_5_en",20445,], +["features.home.impl.roomlist_RoomListDeclineInviteMenuContent_Day_0_en","features.home.impl.roomlist_RoomListDeclineInviteMenuContent_Night_0_en",20445,], +["features.home.impl.filters_RoomListFiltersView_Day_0_en","features.home.impl.filters_RoomListFiltersView_Night_0_en",20445,], +["features.home.impl.filters_RoomListFiltersView_Day_1_en","features.home.impl.filters_RoomListFiltersView_Night_1_en",20445,], +["features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_0_en","features.home.impl.roomlist_RoomListModalBottomSheetContent_Night_0_en",20445,], +["features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_1_en","features.home.impl.roomlist_RoomListModalBottomSheetContent_Night_1_en",20445,], +["features.home.impl.roomlist_RoomListModalBottomSheetContent_Day_2_en","features.home.impl.roomlist_RoomListModalBottomSheetContent_Night_2_en",20445,], ["features.home.impl.search_RoomListSearchContent_Day_0_en","features.home.impl.search_RoomListSearchContent_Night_0_en",0,], -["features.home.impl.search_RoomListSearchContent_Day_1_en","features.home.impl.search_RoomListSearchContent_Night_1_en",20369,], -["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_0_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_0_en",20369,], -["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_1_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_1_en",20369,], -["features.roomdetails.impl.members_RoomMemberListViewBanned_Day_2_en","features.roomdetails.impl.members_RoomMemberListViewBanned_Night_2_en",20369,], -["features.roomdetails.impl.members_RoomMemberListView_Day_0_en","features.roomdetails.impl.members_RoomMemberListView_Night_0_en",20369,], -["features.roomdetails.impl.members_RoomMemberListView_Day_1_en","features.roomdetails.impl.members_RoomMemberListView_Night_1_en",20369,], -["features.roomdetails.impl.members_RoomMemberListView_Day_2_en","features.roomdetails.impl.members_RoomMemberListView_Night_2_en",20369,], -["features.roomdetails.impl.members_RoomMemberListView_Day_3_en","features.roomdetails.impl.members_RoomMemberListView_Night_3_en",20369,], -["features.roomdetails.impl.members_RoomMemberListView_Day_4_en","features.roomdetails.impl.members_RoomMemberListView_Night_4_en",20369,], -["features.roomdetails.impl.members_RoomMemberListView_Day_5_en","features.roomdetails.impl.members_RoomMemberListView_Night_5_en",20369,], -["features.roomdetails.impl.members_RoomMemberListView_Day_6_en","features.roomdetails.impl.members_RoomMemberListView_Night_6_en",0,], -["features.roomdetails.impl.members_RoomMemberListView_Day_7_en","features.roomdetails.impl.members_RoomMemberListView_Night_7_en",20369,], -["features.roomdetails.impl.members_RoomMemberListView_Day_8_en","features.roomdetails.impl.members_RoomMemberListView_Night_8_en",20369,], -["features.roomdetails.impl.members_RoomMemberListView_Day_9_en","features.roomdetails.impl.members_RoomMemberListView_Night_9_en",20369,], -["features.roommembermoderation.impl_RoomMemberModerationView_Day_0_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_0_en",20369,], -["features.roommembermoderation.impl_RoomMemberModerationView_Day_1_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_1_en",20369,], -["features.roommembermoderation.impl_RoomMemberModerationView_Day_2_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_2_en",20369,], -["features.roommembermoderation.impl_RoomMemberModerationView_Day_3_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_3_en",20369,], -["features.roommembermoderation.impl_RoomMemberModerationView_Day_4_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_4_en",20369,], -["features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en",20369,], -["features.roommembermoderation.impl_RoomMemberModerationView_Day_6_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_6_en",20369,], -["features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en",20369,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Night_0_en",20369,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_0_en",20369,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_1_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_1_en",20369,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_2_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_2_en",20369,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_3_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_3_en",20369,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_4_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_4_en",20369,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_5_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_5_en",20369,], -["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_6_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_6_en",20369,], -["libraries.roomselect.impl_RoomSelectView_Day_0_en","libraries.roomselect.impl_RoomSelectView_Night_0_en",20369,], -["libraries.roomselect.impl_RoomSelectView_Day_1_en","libraries.roomselect.impl_RoomSelectView_Night_1_en",20369,], -["libraries.roomselect.impl_RoomSelectView_Day_2_en","libraries.roomselect.impl_RoomSelectView_Night_2_en",20369,], -["libraries.roomselect.impl_RoomSelectView_Day_3_en","libraries.roomselect.impl_RoomSelectView_Night_3_en",20369,], -["libraries.roomselect.impl_RoomSelectView_Day_4_en","libraries.roomselect.impl_RoomSelectView_Night_4_en",20369,], -["libraries.roomselect.impl_RoomSelectView_Day_5_en","libraries.roomselect.impl_RoomSelectView_Night_5_en",20369,], +["features.home.impl.search_RoomListSearchContent_Day_1_en","features.home.impl.search_RoomListSearchContent_Night_1_en",20445,], +["features.roomdetails.impl.members_RoomMemberListView_Day_0_en","features.roomdetails.impl.members_RoomMemberListView_Night_0_en",20445,], +["features.roomdetails.impl.members_RoomMemberListView_Day_1_en","features.roomdetails.impl.members_RoomMemberListView_Night_1_en",20445,], +["features.roomdetails.impl.members_RoomMemberListView_Day_2_en","features.roomdetails.impl.members_RoomMemberListView_Night_2_en",20445,], +["features.roomdetails.impl.members_RoomMemberListView_Day_3_en","features.roomdetails.impl.members_RoomMemberListView_Night_3_en",20445,], +["features.roomdetails.impl.members_RoomMemberListView_Day_4_en","features.roomdetails.impl.members_RoomMemberListView_Night_4_en",20445,], +["features.roomdetails.impl.members_RoomMemberListView_Day_5_en","features.roomdetails.impl.members_RoomMemberListView_Night_5_en",20445,], +["features.roomdetails.impl.members_RoomMemberListView_Day_6_en","features.roomdetails.impl.members_RoomMemberListView_Night_6_en",20445,], +["features.roommembermoderation.impl_RoomMemberModerationView_Day_0_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_0_en",20445,], +["features.roommembermoderation.impl_RoomMemberModerationView_Day_1_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_1_en",20445,], +["features.roommembermoderation.impl_RoomMemberModerationView_Day_2_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_2_en",20445,], +["features.roommembermoderation.impl_RoomMemberModerationView_Day_3_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_3_en",20445,], +["features.roommembermoderation.impl_RoomMemberModerationView_Day_4_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_4_en",20445,], +["features.roommembermoderation.impl_RoomMemberModerationView_Day_5_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_5_en",20445,], +["features.roommembermoderation.impl_RoomMemberModerationView_Day_6_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_6_en",20445,], +["features.roommembermoderation.impl_RoomMemberModerationView_Day_7_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_7_en",20445,], +["features.roommembermoderation.impl_RoomMemberModerationView_Day_8_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_8_en",20445,], +["features.roommembermoderation.impl_RoomMemberModerationView_Day_9_en","features.roommembermoderation.impl_RoomMemberModerationView_Night_9_en",0,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsOption_Night_0_en",20445,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_0_en",20445,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_1_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_1_en",20445,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_2_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_2_en",20445,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_3_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_3_en",20445,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_4_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_4_en",20445,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_5_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_5_en",20445,], +["features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Day_6_en","features.roomdetails.impl.notificationsettings_RoomNotificationSettingsView_Night_6_en",20445,], +["libraries.roomselect.impl_RoomSelectView_Day_0_en","libraries.roomselect.impl_RoomSelectView_Night_0_en",20445,], +["libraries.roomselect.impl_RoomSelectView_Day_1_en","libraries.roomselect.impl_RoomSelectView_Night_1_en",20445,], +["libraries.roomselect.impl_RoomSelectView_Day_2_en","libraries.roomselect.impl_RoomSelectView_Night_2_en",20445,], +["libraries.roomselect.impl_RoomSelectView_Day_3_en","libraries.roomselect.impl_RoomSelectView_Night_3_en",20445,], +["libraries.roomselect.impl_RoomSelectView_Day_4_en","libraries.roomselect.impl_RoomSelectView_Night_4_en",20445,], +["libraries.roomselect.impl_RoomSelectView_Day_5_en","libraries.roomselect.impl_RoomSelectView_Night_5_en",20445,], ["features.home.impl.components_RoomSummaryPlaceholderRow_Day_0_en","features.home.impl.components_RoomSummaryPlaceholderRow_Night_0_en",0,], ["features.home.impl.components_RoomSummaryRow_Day_0_en","features.home.impl.components_RoomSummaryRow_Night_0_en",0,], ["features.home.impl.components_RoomSummaryRow_Day_10_en","features.home.impl.components_RoomSummaryRow_Night_10_en",0,], @@ -1138,14 +1055,16 @@ export const screenshots = [ ["features.home.impl.components_RoomSummaryRow_Day_26_en","features.home.impl.components_RoomSummaryRow_Night_26_en",0,], ["features.home.impl.components_RoomSummaryRow_Day_27_en","features.home.impl.components_RoomSummaryRow_Night_27_en",0,], ["features.home.impl.components_RoomSummaryRow_Day_28_en","features.home.impl.components_RoomSummaryRow_Night_28_en",0,], -["features.home.impl.components_RoomSummaryRow_Day_29_en","features.home.impl.components_RoomSummaryRow_Night_29_en",20369,], -["features.home.impl.components_RoomSummaryRow_Day_2_en","features.home.impl.components_RoomSummaryRow_Night_2_en",20369,], -["features.home.impl.components_RoomSummaryRow_Day_30_en","features.home.impl.components_RoomSummaryRow_Night_30_en",20369,], -["features.home.impl.components_RoomSummaryRow_Day_31_en","features.home.impl.components_RoomSummaryRow_Night_31_en",20369,], -["features.home.impl.components_RoomSummaryRow_Day_32_en","features.home.impl.components_RoomSummaryRow_Night_32_en",20369,], -["features.home.impl.components_RoomSummaryRow_Day_33_en","features.home.impl.components_RoomSummaryRow_Night_33_en",20369,], -["features.home.impl.components_RoomSummaryRow_Day_34_en","features.home.impl.components_RoomSummaryRow_Night_34_en",20369,], -["features.home.impl.components_RoomSummaryRow_Day_35_en","features.home.impl.components_RoomSummaryRow_Night_35_en",20369,], +["features.home.impl.components_RoomSummaryRow_Day_29_en","features.home.impl.components_RoomSummaryRow_Night_29_en",20445,], +["features.home.impl.components_RoomSummaryRow_Day_2_en","features.home.impl.components_RoomSummaryRow_Night_2_en",20445,], +["features.home.impl.components_RoomSummaryRow_Day_30_en","features.home.impl.components_RoomSummaryRow_Night_30_en",20445,], +["features.home.impl.components_RoomSummaryRow_Day_31_en","features.home.impl.components_RoomSummaryRow_Night_31_en",20445,], +["features.home.impl.components_RoomSummaryRow_Day_32_en","features.home.impl.components_RoomSummaryRow_Night_32_en",20445,], +["features.home.impl.components_RoomSummaryRow_Day_33_en","features.home.impl.components_RoomSummaryRow_Night_33_en",20445,], +["features.home.impl.components_RoomSummaryRow_Day_34_en","features.home.impl.components_RoomSummaryRow_Night_34_en",20445,], +["features.home.impl.components_RoomSummaryRow_Day_35_en","features.home.impl.components_RoomSummaryRow_Night_35_en",20445,], +["features.home.impl.components_RoomSummaryRow_Day_36_en","features.home.impl.components_RoomSummaryRow_Night_36_en",0,], +["features.home.impl.components_RoomSummaryRow_Day_37_en","features.home.impl.components_RoomSummaryRow_Night_37_en",0,], ["features.home.impl.components_RoomSummaryRow_Day_3_en","features.home.impl.components_RoomSummaryRow_Night_3_en",0,], ["features.home.impl.components_RoomSummaryRow_Day_4_en","features.home.impl.components_RoomSummaryRow_Night_4_en",0,], ["features.home.impl.components_RoomSummaryRow_Day_5_en","features.home.impl.components_RoomSummaryRow_Night_5_en",0,], @@ -1153,80 +1072,109 @@ export const screenshots = [ ["features.home.impl.components_RoomSummaryRow_Day_7_en","features.home.impl.components_RoomSummaryRow_Night_7_en",0,], ["features.home.impl.components_RoomSummaryRow_Day_8_en","features.home.impl.components_RoomSummaryRow_Night_8_en",0,], ["features.home.impl.components_RoomSummaryRow_Day_9_en","features.home.impl.components_RoomSummaryRow_Night_9_en",0,], -["appnav.root_RootView_Day_0_en","appnav.root_RootView_Night_0_en",20369,], -["appnav.root_RootView_Day_1_en","appnav.root_RootView_Night_1_en",20369,], -["appnav.root_RootView_Day_2_en","appnav.root_RootView_Night_2_en",20369,], +["appnav.root_RootView_Day_0_en","appnav.root_RootView_Night_0_en",20445,], +["appnav.root_RootView_Day_1_en","appnav.root_RootView_Night_1_en",20445,], +["appnav.root_RootView_Day_2_en","appnav.root_RootView_Night_2_en",20445,], ["appicon.enterprise_RoundIcon_en","",0,], ["appicon.element_RoundIcon_en","",0,], ["libraries.designsystem.atomic.atoms_RoundedIconAtom_Day_0_en","libraries.designsystem.atomic.atoms_RoundedIconAtom_Night_0_en",0,], -["features.verifysession.impl.emoji_SasEmojis_Day_0_en","features.verifysession.impl.emoji_SasEmojis_Night_0_en",20369,], -["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_0_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_0_en",20369,], -["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_1_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_1_en",20369,], +["features.verifysession.impl.emoji_SasEmojis_Day_0_en","features.verifysession.impl.emoji_SasEmojis_Night_0_en",20445,], +["libraries.designsystem.components.dialogs_SaveChangesDialog_Day_0_en","libraries.designsystem.components.dialogs_SaveChangesDialog_Night_0_en",20445,], +["features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_0_en","features.linknewdevice.impl.screens.scan_ScanQrCodeView_Night_0_en",20445,], +["features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_1_en","features.linknewdevice.impl.screens.scan_ScanQrCodeView_Night_1_en",20445,], +["features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_2_en","features.linknewdevice.impl.screens.scan_ScanQrCodeView_Night_2_en",20445,], +["features.linknewdevice.impl.screens.scan_ScanQrCodeView_Day_3_en","features.linknewdevice.impl.screens.scan_ScanQrCodeView_Night_3_en",20445,], +["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_0_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_0_en",20445,], +["features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Day_1_en","features.login.impl.screens.searchaccountprovider_SearchAccountProviderView_Night_1_en",20445,], ["libraries.designsystem.theme.components_SearchBarActiveNoneQuery_Search_views_en","",0,], ["libraries.designsystem.theme.components_SearchBarActiveWithContent_Search_views_en","",0,], -["libraries.designsystem.theme.components_SearchBarActiveWithNoResults_Search_views_en","",20369,], +["libraries.designsystem.theme.components_SearchBarActiveWithNoResults_Search_views_en","",20445,], ["libraries.designsystem.theme.components_SearchBarActiveWithQueryNoBackButton_Search_views_en","",0,], ["libraries.designsystem.theme.components_SearchBarActiveWithQuery_Search_views_en","",0,], ["libraries.designsystem.theme.components_SearchBarInactive_Search_views_en","",0,], -["features.startchat.impl.components_SearchMultipleUsersResultItem_en","",20369,], -["features.startchat.impl.components_SearchSingleUserResultItem_en","",20369,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_0_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_0_en",20369,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_1_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_1_en",20369,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_2_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_2_en",20369,], -["features.securebackup.impl.disable_SecureBackupDisableView_Day_3_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_3_en",20369,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_0_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_0_en",20369,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_1_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_1_en",20369,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_2_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_2_en",20369,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_3_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_3_en",20369,], -["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_4_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_4_en",20369,], -["features.securebackup.impl.root_SecureBackupRootView_Day_0_en","features.securebackup.impl.root_SecureBackupRootView_Night_0_en",20369,], -["features.securebackup.impl.root_SecureBackupRootView_Day_10_en","features.securebackup.impl.root_SecureBackupRootView_Night_10_en",20369,], -["features.securebackup.impl.root_SecureBackupRootView_Day_11_en","features.securebackup.impl.root_SecureBackupRootView_Night_11_en",20369,], -["features.securebackup.impl.root_SecureBackupRootView_Day_12_en","features.securebackup.impl.root_SecureBackupRootView_Night_12_en",20369,], -["features.securebackup.impl.root_SecureBackupRootView_Day_13_en","features.securebackup.impl.root_SecureBackupRootView_Night_13_en",20369,], -["features.securebackup.impl.root_SecureBackupRootView_Day_14_en","features.securebackup.impl.root_SecureBackupRootView_Night_14_en",20369,], -["features.securebackup.impl.root_SecureBackupRootView_Day_15_en","features.securebackup.impl.root_SecureBackupRootView_Night_15_en",20369,], -["features.securebackup.impl.root_SecureBackupRootView_Day_16_en","features.securebackup.impl.root_SecureBackupRootView_Night_16_en",20369,], -["features.securebackup.impl.root_SecureBackupRootView_Day_17_en","features.securebackup.impl.root_SecureBackupRootView_Night_17_en",20369,], -["features.securebackup.impl.root_SecureBackupRootView_Day_1_en","features.securebackup.impl.root_SecureBackupRootView_Night_1_en",20369,], -["features.securebackup.impl.root_SecureBackupRootView_Day_2_en","features.securebackup.impl.root_SecureBackupRootView_Night_2_en",20369,], -["features.securebackup.impl.root_SecureBackupRootView_Day_3_en","features.securebackup.impl.root_SecureBackupRootView_Night_3_en",20369,], -["features.securebackup.impl.root_SecureBackupRootView_Day_4_en","features.securebackup.impl.root_SecureBackupRootView_Night_4_en",20369,], -["features.securebackup.impl.root_SecureBackupRootView_Day_5_en","features.securebackup.impl.root_SecureBackupRootView_Night_5_en",20369,], -["features.securebackup.impl.root_SecureBackupRootView_Day_6_en","features.securebackup.impl.root_SecureBackupRootView_Night_6_en",20369,], -["features.securebackup.impl.root_SecureBackupRootView_Day_7_en","features.securebackup.impl.root_SecureBackupRootView_Night_7_en",20369,], -["features.securebackup.impl.root_SecureBackupRootView_Day_8_en","features.securebackup.impl.root_SecureBackupRootView_Night_8_en",20369,], -["features.securebackup.impl.root_SecureBackupRootView_Day_9_en","features.securebackup.impl.root_SecureBackupRootView_Night_9_en",20369,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_0_en",20369,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_1_en",20369,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_2_en",20369,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_3_en",20369,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_4_en",20369,], -["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_5_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_5_en",20369,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_0_en",20369,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_1_en",20369,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_2_en",20369,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_3_en",20369,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_4_en",20369,], -["features.securebackup.impl.setup_SecureBackupSetupView_Day_5_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_5_en",20369,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_0_en","",20369,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_1_en","",20369,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_2_en","",20369,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_3_en","",20369,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_4_en","",20369,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_5_en","",20369,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_6_en","",20369,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_7_en","",20369,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewDark_8_en","",20369,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_0_en","",20369,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_1_en","",20369,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_2_en","",20369,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_3_en","",20369,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_4_en","",20369,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_5_en","",20369,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_6_en","",20369,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_7_en","",20369,], -["features.roomdetails.impl.securityandprivacy_SecurityAndPrivacyViewLight_8_en","",20369,], +["libraries.designsystem.theme.components_SearchFieldsDark_Search_views_en","",0,], +["libraries.designsystem.theme.components_SearchFieldsLight_Search_views_en","",0,], +["features.startchat.impl.components_SearchMultipleUsersResultItem_en","",20445,], +["features.startchat.impl.components_SearchSingleUserResultItem_en","",20445,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_0_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_0_en",20445,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_1_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_1_en",20445,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_2_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_2_en",20445,], +["features.securebackup.impl.disable_SecureBackupDisableView_Day_3_en","features.securebackup.impl.disable_SecureBackupDisableView_Night_3_en",20445,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_0_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_0_en",20445,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_1_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_1_en",20445,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_2_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_2_en",20445,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_3_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_3_en",20445,], +["features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Day_4_en","features.securebackup.impl.enter_SecureBackupEnterRecoveryKeyView_Night_4_en",20445,], +["features.securebackup.impl.root_SecureBackupRootView_Day_0_en","features.securebackup.impl.root_SecureBackupRootView_Night_0_en",20445,], +["features.securebackup.impl.root_SecureBackupRootView_Day_10_en","features.securebackup.impl.root_SecureBackupRootView_Night_10_en",20445,], +["features.securebackup.impl.root_SecureBackupRootView_Day_11_en","features.securebackup.impl.root_SecureBackupRootView_Night_11_en",20445,], +["features.securebackup.impl.root_SecureBackupRootView_Day_12_en","features.securebackup.impl.root_SecureBackupRootView_Night_12_en",20445,], +["features.securebackup.impl.root_SecureBackupRootView_Day_13_en","features.securebackup.impl.root_SecureBackupRootView_Night_13_en",20445,], +["features.securebackup.impl.root_SecureBackupRootView_Day_14_en","features.securebackup.impl.root_SecureBackupRootView_Night_14_en",20445,], +["features.securebackup.impl.root_SecureBackupRootView_Day_15_en","features.securebackup.impl.root_SecureBackupRootView_Night_15_en",20445,], +["features.securebackup.impl.root_SecureBackupRootView_Day_16_en","features.securebackup.impl.root_SecureBackupRootView_Night_16_en",20445,], +["features.securebackup.impl.root_SecureBackupRootView_Day_17_en","features.securebackup.impl.root_SecureBackupRootView_Night_17_en",20445,], +["features.securebackup.impl.root_SecureBackupRootView_Day_1_en","features.securebackup.impl.root_SecureBackupRootView_Night_1_en",20445,], +["features.securebackup.impl.root_SecureBackupRootView_Day_2_en","features.securebackup.impl.root_SecureBackupRootView_Night_2_en",20445,], +["features.securebackup.impl.root_SecureBackupRootView_Day_3_en","features.securebackup.impl.root_SecureBackupRootView_Night_3_en",20445,], +["features.securebackup.impl.root_SecureBackupRootView_Day_4_en","features.securebackup.impl.root_SecureBackupRootView_Night_4_en",20445,], +["features.securebackup.impl.root_SecureBackupRootView_Day_5_en","features.securebackup.impl.root_SecureBackupRootView_Night_5_en",20445,], +["features.securebackup.impl.root_SecureBackupRootView_Day_6_en","features.securebackup.impl.root_SecureBackupRootView_Night_6_en",20445,], +["features.securebackup.impl.root_SecureBackupRootView_Day_7_en","features.securebackup.impl.root_SecureBackupRootView_Night_7_en",20445,], +["features.securebackup.impl.root_SecureBackupRootView_Day_8_en","features.securebackup.impl.root_SecureBackupRootView_Night_8_en",20445,], +["features.securebackup.impl.root_SecureBackupRootView_Day_9_en","features.securebackup.impl.root_SecureBackupRootView_Night_9_en",20445,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_0_en",20445,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_1_en",20445,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_2_en",20445,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_3_en",20445,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_4_en",20445,], +["features.securebackup.impl.setup_SecureBackupSetupViewChange_Day_5_en","features.securebackup.impl.setup_SecureBackupSetupViewChange_Night_5_en",20445,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_0_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_0_en",20445,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_1_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_1_en",20445,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_2_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_2_en",20445,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_3_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_3_en",20445,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_4_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_4_en",20445,], +["features.securebackup.impl.setup_SecureBackupSetupView_Day_5_en","features.securebackup.impl.setup_SecureBackupSetupView_Night_5_en",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_0_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_10_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_11_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_12_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_13_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_14_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_15_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_16_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_17_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_18_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_19_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_1_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_2_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_3_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_4_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_5_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_6_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_7_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_8_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_9_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_0_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_10_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_11_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_12_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_13_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_14_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_15_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_16_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_17_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_18_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_19_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_1_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_2_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_3_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_4_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_5_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_6_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_7_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_8_en","",20445,], +["features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_9_en","",20445,], ["libraries.designsystem.atomic.atoms_SelectedIndicatorAtom_Day_0_en","libraries.designsystem.atomic.atoms_SelectedIndicatorAtom_Night_0_en",0,], ["libraries.matrix.ui.components_SelectedRoomRtl_Day_0_en","libraries.matrix.ui.components_SelectedRoomRtl_Night_0_en",0,], ["libraries.matrix.ui.components_SelectedRoomRtl_Day_1_en","libraries.matrix.ui.components_SelectedRoomRtl_Night_1_en",0,], @@ -1240,11 +1188,11 @@ export const screenshots = [ ["libraries.matrix.ui.components_SelectedUser_Day_1_en","libraries.matrix.ui.components_SelectedUser_Night_1_en",0,], ["libraries.matrix.ui.components_SelectedUsersRowList_Day_0_en","libraries.matrix.ui.components_SelectedUsersRowList_Night_0_en",0,], ["libraries.textcomposer.components_SendButton_Day_0_en","libraries.textcomposer.components_SendButton_Night_0_en",0,], -["features.location.impl.send_SendLocationView_Day_0_en","features.location.impl.send_SendLocationView_Night_0_en",20369,], -["features.location.impl.send_SendLocationView_Day_1_en","features.location.impl.send_SendLocationView_Night_1_en",20369,], -["features.location.impl.send_SendLocationView_Day_2_en","features.location.impl.send_SendLocationView_Night_2_en",20369,], -["features.location.impl.send_SendLocationView_Day_3_en","features.location.impl.send_SendLocationView_Night_3_en",20369,], -["features.location.impl.send_SendLocationView_Day_4_en","features.location.impl.send_SendLocationView_Night_4_en",20369,], +["features.location.impl.send_SendLocationView_Day_0_en","features.location.impl.send_SendLocationView_Night_0_en",20445,], +["features.location.impl.send_SendLocationView_Day_1_en","features.location.impl.send_SendLocationView_Night_1_en",20445,], +["features.location.impl.send_SendLocationView_Day_2_en","features.location.impl.send_SendLocationView_Night_2_en",20445,], +["features.location.impl.send_SendLocationView_Day_3_en","features.location.impl.send_SendLocationView_Night_3_en",20445,], +["features.location.impl.send_SendLocationView_Day_4_en","features.location.impl.send_SendLocationView_Night_4_en",20445,], ["libraries.matrix.ui.messages.sender_SenderName_Day_0_en","libraries.matrix.ui.messages.sender_SenderName_Night_0_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_1_en","libraries.matrix.ui.messages.sender_SenderName_Night_1_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_2_en","libraries.matrix.ui.messages.sender_SenderName_Night_2_en",0,], @@ -1254,27 +1202,29 @@ export const screenshots = [ ["libraries.matrix.ui.messages.sender_SenderName_Day_6_en","libraries.matrix.ui.messages.sender_SenderName_Night_6_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_7_en","libraries.matrix.ui.messages.sender_SenderName_Night_7_en",0,], ["libraries.matrix.ui.messages.sender_SenderName_Day_8_en","libraries.matrix.ui.messages.sender_SenderName_Night_8_en",0,], -["features.verifysession.impl.incoming.ui_SessionDetailsView_Day_0_en","features.verifysession.impl.incoming.ui_SessionDetailsView_Night_0_en",20369,], -["features.home.impl.components_SetUpRecoveryKeyBanner_Day_0_en","features.home.impl.components_SetUpRecoveryKeyBanner_Night_0_en",20369,], -["features.lockscreen.impl.setup.biometric_SetupBiometricView_Day_0_en","features.lockscreen.impl.setup.biometric_SetupBiometricView_Night_0_en",20369,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_0_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_0_en",20369,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_1_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_1_en",20369,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_2_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_2_en",20369,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_3_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_3_en",20369,], -["features.lockscreen.impl.setup.pin_SetupPinView_Day_4_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_4_en",20369,], +["features.verifysession.impl.incoming.ui_SessionDetailsView_Day_0_en","features.verifysession.impl.incoming.ui_SessionDetailsView_Night_0_en",20445,], +["features.home.impl.components_SetUpRecoveryKeyBanner_Day_0_en","features.home.impl.components_SetUpRecoveryKeyBanner_Night_0_en",20445,], +["features.lockscreen.impl.setup.biometric_SetupBiometricView_Day_0_en","features.lockscreen.impl.setup.biometric_SetupBiometricView_Night_0_en",20445,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_0_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_0_en",20445,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_1_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_1_en",20445,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_2_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_2_en",20445,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_3_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_3_en",20445,], +["features.lockscreen.impl.setup.pin_SetupPinView_Day_4_en","features.lockscreen.impl.setup.pin_SetupPinView_Night_4_en",20445,], ["features.share.impl_ShareView_Day_0_en","features.share.impl_ShareView_Night_0_en",0,], ["features.share.impl_ShareView_Day_1_en","features.share.impl_ShareView_Night_1_en",0,], ["features.share.impl_ShareView_Day_2_en","features.share.impl_ShareView_Night_2_en",0,], -["features.share.impl_ShareView_Day_3_en","features.share.impl_ShareView_Night_3_en",20369,], -["features.location.impl.show_ShowLocationView_Day_0_en","features.location.impl.show_ShowLocationView_Night_0_en",20369,], -["features.location.impl.show_ShowLocationView_Day_1_en","features.location.impl.show_ShowLocationView_Night_1_en",20369,], -["features.location.impl.show_ShowLocationView_Day_2_en","features.location.impl.show_ShowLocationView_Night_2_en",20369,], -["features.location.impl.show_ShowLocationView_Day_3_en","features.location.impl.show_ShowLocationView_Night_3_en",20369,], -["features.location.impl.show_ShowLocationView_Day_4_en","features.location.impl.show_ShowLocationView_Night_4_en",20369,], -["features.location.impl.show_ShowLocationView_Day_5_en","features.location.impl.show_ShowLocationView_Night_5_en",20369,], -["features.location.impl.show_ShowLocationView_Day_6_en","features.location.impl.show_ShowLocationView_Night_6_en",20369,], -["features.location.impl.show_ShowLocationView_Day_7_en","features.location.impl.show_ShowLocationView_Night_7_en",20369,], -["features.signedout.impl_SignedOutView_Day_0_en","features.signedout.impl_SignedOutView_Night_0_en",20369,], +["features.share.impl_ShareView_Day_3_en","features.share.impl_ShareView_Night_3_en",20445,], +["features.location.impl.show_ShowLocationView_Day_0_en","features.location.impl.show_ShowLocationView_Night_0_en",20445,], +["features.location.impl.show_ShowLocationView_Day_1_en","features.location.impl.show_ShowLocationView_Night_1_en",20445,], +["features.location.impl.show_ShowLocationView_Day_2_en","features.location.impl.show_ShowLocationView_Night_2_en",20445,], +["features.location.impl.show_ShowLocationView_Day_3_en","features.location.impl.show_ShowLocationView_Night_3_en",20445,], +["features.location.impl.show_ShowLocationView_Day_4_en","features.location.impl.show_ShowLocationView_Night_4_en",20445,], +["features.location.impl.show_ShowLocationView_Day_5_en","features.location.impl.show_ShowLocationView_Night_5_en",20445,], +["features.location.impl.show_ShowLocationView_Day_6_en","features.location.impl.show_ShowLocationView_Night_6_en",20445,], +["features.location.impl.show_ShowLocationView_Day_7_en","features.location.impl.show_ShowLocationView_Night_7_en",20445,], +["features.linknewdevice.impl.screens.qrcode_ShowQrCodeView_Day_0_en","features.linknewdevice.impl.screens.qrcode_ShowQrCodeView_Night_0_en",20445,], +["features.signedout.impl_SignedOutView_Day_0_en","features.signedout.impl_SignedOutView_Night_0_en",20445,], +["libraries.designsystem.components_SimpleModalBottomSheet_Day_0_en","libraries.designsystem.components_SimpleModalBottomSheet_Night_0_en",0,], ["libraries.designsystem.components.dialogs_SingleSelectionDialogContent_Dialogs_en","",0,], ["libraries.designsystem.components.dialogs_SingleSelectionDialog_Day_0_en","libraries.designsystem.components.dialogs_SingleSelectionDialog_Night_0_en",0,], ["libraries.designsystem.components.list_SingleSelectionListItemCustomFormattert_Single_selection_List_item_-_custom_formatter_List_items_en","",0,], @@ -1283,97 +1233,102 @@ export const screenshots = [ ["libraries.designsystem.components.list_SingleSelectionListItemUnselectedWithSupportingText_Single_selection_List_item_-_no_selection,_supporting_text_List_items_en","",0,], ["libraries.designsystem.components.list_SingleSelectionListItem_Single_selection_List_item_-_no_selection_List_items_en","",0,], ["libraries.designsystem.theme.components_Sliders_Sliders_en","",0,], -["features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Day_0_en","features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Night_0_en",20369,], +["features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Day_0_en","features.login.impl.dialogs_SlidingSyncNotSupportedDialog_Night_0_en",20445,], ["libraries.designsystem.theme.components_SnackbarWithActionAndCloseButton_Snackbar_with_action_and_close_button_Snackbars_en","",0,], ["libraries.designsystem.theme.components_SnackbarWithActionOnNewLineAndCloseButton_Snackbar_with_action_and_close_button_on_new_line_Snackbars_en","",0,], ["libraries.designsystem.theme.components_SnackbarWithActionOnNewLine_Snackbar_with_action_on_new_line_Snackbars_en","",0,], ["libraries.designsystem.theme.components_SnackbarWithAction_Snackbar_with_action_Snackbars_en","",0,], ["libraries.designsystem.theme.components_Snackbar_Snackbar_Snackbars_en","",0,], -["features.announcement.impl.spaces_SpaceAnnouncementView_Day_0_en","features.announcement.impl.spaces_SpaceAnnouncementView_Night_0_en",20369,], +["features.announcement.impl.spaces_SpaceAnnouncementView_Day_0_en","features.announcement.impl.spaces_SpaceAnnouncementView_Night_0_en",20445,], ["libraries.designsystem.components.avatar.internal_SpaceAvatar_Avatars_en","",0,], -["libraries.matrix.ui.components_SpaceHeaderRootView_Day_0_en","libraries.matrix.ui.components_SpaceHeaderRootView_Night_0_en",20369,], -["libraries.matrix.ui.components_SpaceHeaderView_Day_0_en","libraries.matrix.ui.components_SpaceHeaderView_Night_0_en",20369,], -["libraries.matrix.ui.components_SpaceInfoRow_Day_0_en","libraries.matrix.ui.components_SpaceInfoRow_Night_0_en",20369,], +["libraries.matrix.ui.components_SpaceHeaderRootView_Day_0_en","libraries.matrix.ui.components_SpaceHeaderRootView_Night_0_en",20445,], +["libraries.matrix.ui.components_SpaceHeaderView_Day_0_en","libraries.matrix.ui.components_SpaceHeaderView_Night_0_en",20445,], +["libraries.matrix.ui.components_SpaceInfoRow_Day_0_en","libraries.matrix.ui.components_SpaceInfoRow_Night_0_en",20445,], ["libraries.matrix.ui.components_SpaceMembersViewNoHeroes_Day_0_en","libraries.matrix.ui.components_SpaceMembersViewNoHeroes_Night_0_en",0,], ["libraries.matrix.ui.components_SpaceMembersView_Day_0_en","libraries.matrix.ui.components_SpaceMembersView_Night_0_en",0,], -["libraries.matrix.ui.components_SpaceRoomItemView_Day_0_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_0_en",20369,], -["libraries.matrix.ui.components_SpaceRoomItemView_Day_1_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_1_en",20369,], -["libraries.matrix.ui.components_SpaceRoomItemView_Day_2_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_2_en",20369,], -["libraries.matrix.ui.components_SpaceRoomItemView_Day_3_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_3_en",20369,], -["libraries.matrix.ui.components_SpaceRoomItemView_Day_4_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_4_en",20369,], -["libraries.matrix.ui.components_SpaceRoomItemView_Day_5_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_5_en",20369,], -["libraries.matrix.ui.components_SpaceRoomItemView_Day_6_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_6_en",20369,], -["libraries.matrix.ui.components_SpaceRoomItemView_Day_7_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_7_en",20369,], -["features.space.impl.root_SpaceView_Day_0_en","features.space.impl.root_SpaceView_Night_0_en",20369,], -["features.space.impl.root_SpaceView_Day_1_en","features.space.impl.root_SpaceView_Night_1_en",20369,], -["features.space.impl.root_SpaceView_Day_2_en","features.space.impl.root_SpaceView_Night_2_en",20369,], -["features.space.impl.root_SpaceView_Day_3_en","features.space.impl.root_SpaceView_Night_3_en",20369,], -["features.space.impl.root_SpaceView_Day_4_en","features.space.impl.root_SpaceView_Night_4_en",20369,], -["features.space.impl.root_SpaceView_Day_5_en","features.space.impl.root_SpaceView_Night_5_en",20369,], +["libraries.matrix.ui.components_SpaceRoomItemView_Day_0_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_0_en",20445,], +["libraries.matrix.ui.components_SpaceRoomItemView_Day_1_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_1_en",20445,], +["libraries.matrix.ui.components_SpaceRoomItemView_Day_2_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_2_en",20445,], +["libraries.matrix.ui.components_SpaceRoomItemView_Day_3_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_3_en",20445,], +["libraries.matrix.ui.components_SpaceRoomItemView_Day_4_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_4_en",20445,], +["libraries.matrix.ui.components_SpaceRoomItemView_Day_5_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_5_en",20445,], +["libraries.matrix.ui.components_SpaceRoomItemView_Day_6_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_6_en",20445,], +["libraries.matrix.ui.components_SpaceRoomItemView_Day_7_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_7_en",20445,], +["libraries.matrix.ui.components_SpaceRoomItemView_Day_8_en","libraries.matrix.ui.components_SpaceRoomItemView_Night_8_en",20445,], +["features.space.impl.settings_SpaceSettingsView_Day_0_en","features.space.impl.settings_SpaceSettingsView_Night_0_en",20445,], +["features.space.impl.settings_SpaceSettingsView_Day_1_en","features.space.impl.settings_SpaceSettingsView_Night_1_en",20445,], +["features.space.impl.settings_SpaceSettingsView_Day_2_en","features.space.impl.settings_SpaceSettingsView_Night_2_en",20445,], +["features.space.impl.settings_SpaceSettingsView_Day_3_en","features.space.impl.settings_SpaceSettingsView_Night_3_en",20445,], +["features.space.impl.root_SpaceView_Day_0_en","features.space.impl.root_SpaceView_Night_0_en",20445,], +["features.space.impl.root_SpaceView_Day_1_en","features.space.impl.root_SpaceView_Night_1_en",20445,], +["features.space.impl.root_SpaceView_Day_2_en","features.space.impl.root_SpaceView_Night_2_en",20445,], +["features.space.impl.root_SpaceView_Day_3_en","features.space.impl.root_SpaceView_Night_3_en",20445,], +["features.space.impl.root_SpaceView_Day_4_en","features.space.impl.root_SpaceView_Night_4_en",20445,], +["features.space.impl.root_SpaceView_Day_5_en","features.space.impl.root_SpaceView_Night_5_en",20445,], ["libraries.designsystem.modifiers_SquareSizeModifierInsideSquare_en","",0,], ["libraries.designsystem.modifiers_SquareSizeModifierLargeHeight_en","",0,], ["libraries.designsystem.modifiers_SquareSizeModifierLargeWidth_en","",0,], -["features.startchat.impl.root_StartChatView_Day_0_en","features.startchat.impl.root_StartChatView_Night_0_en",20369,], -["features.startchat.impl.root_StartChatView_Day_1_en","features.startchat.impl.root_StartChatView_Night_1_en",20369,], -["features.startchat.impl.root_StartChatView_Day_2_en","features.startchat.impl.root_StartChatView_Night_2_en",20369,], -["features.startchat.impl.root_StartChatView_Day_3_en","features.startchat.impl.root_StartChatView_Night_3_en",20369,], -["features.startchat.impl.root_StartChatView_Day_4_en","features.startchat.impl.root_StartChatView_Night_4_en",20369,], -["features.startchat.impl.root_StartChatView_Day_5_en","features.startchat.impl.root_StartChatView_Night_5_en",20369,], -["features.location.api.internal_StaticMapPlaceholder_Day_0_en","features.location.api.internal_StaticMapPlaceholder_Night_0_en",20369,], +["features.startchat.impl.root_StartChatView_Day_0_en","features.startchat.impl.root_StartChatView_Night_0_en",20445,], +["features.startchat.impl.root_StartChatView_Day_1_en","features.startchat.impl.root_StartChatView_Night_1_en",20445,], +["features.startchat.impl.root_StartChatView_Day_2_en","features.startchat.impl.root_StartChatView_Night_2_en",20445,], +["features.startchat.impl.root_StartChatView_Day_3_en","features.startchat.impl.root_StartChatView_Night_3_en",20445,], +["features.startchat.impl.root_StartChatView_Day_4_en","features.startchat.impl.root_StartChatView_Night_4_en",20445,], +["features.startchat.impl.root_StartChatView_Day_5_en","features.startchat.impl.root_StartChatView_Night_5_en",20445,], +["features.location.api.internal_StaticMapPlaceholder_Day_0_en","features.location.api.internal_StaticMapPlaceholder_Night_0_en",20445,], ["features.location.api_StaticMapView_Day_0_en","features.location.api_StaticMapView_Night_0_en",0,], -["features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Day_0_en","features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Night_0_en",20369,], +["features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Day_0_en","features.messages.impl.messagecomposer.suggestions_SuggestionsPickerView_Night_0_en",20445,], ["libraries.designsystem.atomic.pages_SunsetPage_Day_0_en","libraries.designsystem.atomic.pages_SunsetPage_Night_0_en",0,], ["libraries.designsystem.components.button_SuperButton_Day_0_en","libraries.designsystem.components.button_SuperButton_Night_0_en",0,], ["libraries.designsystem.theme.components_Surface_en","",0,], ["libraries.designsystem.theme.components_Switch_Toggles_en","",0,], -["appnav.loggedin_SyncStateView_Day_0_en","appnav.loggedin_SyncStateView_Night_0_en",20369,], +["appnav.loggedin_SyncStateView_Day_0_en","appnav.loggedin_SyncStateView_Night_0_en",20445,], ["libraries.designsystem.components.avatar.internal_TextAvatar_Avatars_en","",0,], ["libraries.designsystem.theme.components_TextButtonLargeLowPadding_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonLarge_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonMediumLowPadding_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonMedium_Buttons_en","",0,], ["libraries.designsystem.theme.components_TextButtonSmall_Buttons_en","",0,], -["libraries.textcomposer_TextComposerAddCaption_Day_0_en","libraries.textcomposer_TextComposerAddCaption_Night_0_en",20369,], -["libraries.textcomposer_TextComposerCaption_Day_0_en","libraries.textcomposer_TextComposerCaption_Night_0_en",20369,], -["libraries.textcomposer_TextComposerEditCaption_Day_0_en","libraries.textcomposer_TextComposerEditCaption_Night_0_en",20369,], -["libraries.textcomposer_TextComposerEditNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerEditNotEncrypted_Night_0_en",20369,], -["libraries.textcomposer_TextComposerEdit_Day_0_en","libraries.textcomposer_TextComposerEdit_Night_0_en",20369,], -["libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerFormattingNotEncrypted_Night_0_en",20369,], -["libraries.textcomposer_TextComposerFormatting_Day_0_en","libraries.textcomposer_TextComposerFormatting_Night_0_en",20369,], -["libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Night_0_en",20369,], -["libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLink_Night_0_en",20369,], -["libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogEditLink_Night_0_en",20369,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_0_en",20369,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_10_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_10_en",20369,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_11_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_11_en",20369,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_1_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_1_en",20369,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_2_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_2_en",20369,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_3_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_3_en",20369,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_4_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_4_en",20369,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_5_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_5_en",20369,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_6_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_6_en",20369,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_7_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_7_en",20369,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_8_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_8_en",20369,], -["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_9_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_9_en",20369,], -["libraries.textcomposer_TextComposerReply_Day_0_en","libraries.textcomposer_TextComposerReply_Night_0_en",20369,], -["libraries.textcomposer_TextComposerReply_Day_10_en","libraries.textcomposer_TextComposerReply_Night_10_en",20369,], -["libraries.textcomposer_TextComposerReply_Day_11_en","libraries.textcomposer_TextComposerReply_Night_11_en",20369,], -["libraries.textcomposer_TextComposerReply_Day_1_en","libraries.textcomposer_TextComposerReply_Night_1_en",20369,], -["libraries.textcomposer_TextComposerReply_Day_2_en","libraries.textcomposer_TextComposerReply_Night_2_en",20369,], -["libraries.textcomposer_TextComposerReply_Day_3_en","libraries.textcomposer_TextComposerReply_Night_3_en",20369,], -["libraries.textcomposer_TextComposerReply_Day_4_en","libraries.textcomposer_TextComposerReply_Night_4_en",20369,], -["libraries.textcomposer_TextComposerReply_Day_5_en","libraries.textcomposer_TextComposerReply_Night_5_en",20369,], -["libraries.textcomposer_TextComposerReply_Day_6_en","libraries.textcomposer_TextComposerReply_Night_6_en",20369,], -["libraries.textcomposer_TextComposerReply_Day_7_en","libraries.textcomposer_TextComposerReply_Night_7_en",20369,], -["libraries.textcomposer_TextComposerReply_Day_8_en","libraries.textcomposer_TextComposerReply_Night_8_en",20369,], -["libraries.textcomposer_TextComposerReply_Day_9_en","libraries.textcomposer_TextComposerReply_Night_9_en",20369,], -["libraries.textcomposer_TextComposerSimpleNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerSimpleNotEncrypted_Night_0_en",20369,], -["libraries.textcomposer_TextComposerSimple_Day_0_en","libraries.textcomposer_TextComposerSimple_Night_0_en",20369,], -["libraries.textcomposer_TextComposerVoiceNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerVoiceNotEncrypted_Night_0_en",20369,], +["libraries.textcomposer_TextComposerAddCaption_Day_0_en","libraries.textcomposer_TextComposerAddCaption_Night_0_en",20445,], +["libraries.textcomposer_TextComposerCaption_Day_0_en","libraries.textcomposer_TextComposerCaption_Night_0_en",20445,], +["libraries.textcomposer_TextComposerEditCaption_Day_0_en","libraries.textcomposer_TextComposerEditCaption_Night_0_en",20445,], +["libraries.textcomposer_TextComposerEditNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerEditNotEncrypted_Night_0_en",20445,], +["libraries.textcomposer_TextComposerEdit_Day_0_en","libraries.textcomposer_TextComposerEdit_Night_0_en",20445,], +["libraries.textcomposer_TextComposerFormattingNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerFormattingNotEncrypted_Night_0_en",20445,], +["libraries.textcomposer_TextComposerFormatting_Day_0_en","libraries.textcomposer_TextComposerFormatting_Night_0_en",20445,], +["libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLinkWithoutText_Night_0_en",20445,], +["libraries.textcomposer_TextComposerLinkDialogCreateLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogCreateLink_Night_0_en",20445,], +["libraries.textcomposer_TextComposerLinkDialogEditLink_Day_0_en","libraries.textcomposer_TextComposerLinkDialogEditLink_Night_0_en",20445,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_0_en",20445,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_10_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_10_en",20445,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_11_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_11_en",20445,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_1_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_1_en",20445,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_2_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_2_en",20445,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_3_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_3_en",20445,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_4_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_4_en",20445,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_5_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_5_en",20445,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_6_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_6_en",20445,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_7_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_7_en",20445,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_8_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_8_en",20445,], +["libraries.textcomposer_TextComposerReplyNotEncrypted_Day_9_en","libraries.textcomposer_TextComposerReplyNotEncrypted_Night_9_en",20445,], +["libraries.textcomposer_TextComposerReply_Day_0_en","libraries.textcomposer_TextComposerReply_Night_0_en",20445,], +["libraries.textcomposer_TextComposerReply_Day_10_en","libraries.textcomposer_TextComposerReply_Night_10_en",20445,], +["libraries.textcomposer_TextComposerReply_Day_11_en","libraries.textcomposer_TextComposerReply_Night_11_en",20445,], +["libraries.textcomposer_TextComposerReply_Day_1_en","libraries.textcomposer_TextComposerReply_Night_1_en",20445,], +["libraries.textcomposer_TextComposerReply_Day_2_en","libraries.textcomposer_TextComposerReply_Night_2_en",20445,], +["libraries.textcomposer_TextComposerReply_Day_3_en","libraries.textcomposer_TextComposerReply_Night_3_en",20445,], +["libraries.textcomposer_TextComposerReply_Day_4_en","libraries.textcomposer_TextComposerReply_Night_4_en",20445,], +["libraries.textcomposer_TextComposerReply_Day_5_en","libraries.textcomposer_TextComposerReply_Night_5_en",20445,], +["libraries.textcomposer_TextComposerReply_Day_6_en","libraries.textcomposer_TextComposerReply_Night_6_en",20445,], +["libraries.textcomposer_TextComposerReply_Day_7_en","libraries.textcomposer_TextComposerReply_Night_7_en",20445,], +["libraries.textcomposer_TextComposerReply_Day_8_en","libraries.textcomposer_TextComposerReply_Night_8_en",20445,], +["libraries.textcomposer_TextComposerReply_Day_9_en","libraries.textcomposer_TextComposerReply_Night_9_en",20445,], +["libraries.textcomposer_TextComposerSimpleNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerSimpleNotEncrypted_Night_0_en",20445,], +["libraries.textcomposer_TextComposerSimple_Day_0_en","libraries.textcomposer_TextComposerSimple_Night_0_en",20445,], +["libraries.textcomposer_TextComposerVoiceNotEncrypted_Day_0_en","libraries.textcomposer_TextComposerVoiceNotEncrypted_Night_0_en",20445,], ["libraries.textcomposer_TextComposerVoice_Day_0_en","libraries.textcomposer_TextComposerVoice_Night_0_en",0,], ["libraries.designsystem.theme.components_TextDark_Text_en","",0,], -["libraries.designsystem.components.dialogs_TextFieldDialogWithError_Day_0_en","libraries.designsystem.components.dialogs_TextFieldDialogWithError_Night_0_en",20369,], -["libraries.designsystem.components.dialogs_TextFieldDialog_Day_0_en","libraries.designsystem.components.dialogs_TextFieldDialog_Night_0_en",20369,], +["libraries.designsystem.components.dialogs_TextFieldDialogWithError_Day_0_en","libraries.designsystem.components.dialogs_TextFieldDialogWithError_Night_0_en",20445,], +["libraries.designsystem.components.dialogs_TextFieldDialog_Day_0_en","libraries.designsystem.components.dialogs_TextFieldDialog_Night_0_en",20445,], ["libraries.designsystem.components.list_TextFieldListItemEmpty_Text_field_List_item_-_empty_List_items_en","",0,], ["libraries.designsystem.components.list_TextFieldListItemTextFieldValue_Text_field_List_item_-_textfieldvalue_List_items_en","",0,], ["libraries.designsystem.components.list_TextFieldListItem_Text_field_List_item_-_text_List_items_en","",0,], @@ -1385,16 +1340,16 @@ export const screenshots = [ ["libraries.mediaviewer.impl.local.txt_TextFileContentView_Day_3_en","libraries.mediaviewer.impl.local.txt_TextFileContentView_Night_3_en",0,], ["libraries.textcomposer.components_TextFormatting_Day_0_en","libraries.textcomposer.components_TextFormatting_Night_0_en",0,], ["libraries.designsystem.theme.components_TextLight_Text_en","",0,], -["features.messages.impl.timeline.components_ThreadSummaryView_Day_0_en","features.messages.impl.timeline.components_ThreadSummaryView_Night_0_en",20369,], -["features.messages.impl.topbars_ThreadTopBar_Day_0_en","features.messages.impl.topbars_ThreadTopBar_Night_0_en",20369,], -["libraries.designsystem.theme.components.previews_TimePickerHorizontal_DateTime_pickers_en","",20369,], -["libraries.designsystem.theme.components.previews_TimePickerVerticalDark_DateTime_pickers_en","",20369,], -["libraries.designsystem.theme.components.previews_TimePickerVerticalLight_DateTime_pickers_en","",20369,], +["features.messages.impl.timeline.components_ThreadSummaryView_Day_0_en","features.messages.impl.timeline.components_ThreadSummaryView_Night_0_en",20445,], +["features.messages.impl.topbars_ThreadTopBar_Day_0_en","features.messages.impl.topbars_ThreadTopBar_Night_0_en",20445,], +["libraries.designsystem.theme.components.previews_TimePickerHorizontal_DateTime_pickers_en","",20445,], +["libraries.designsystem.theme.components.previews_TimePickerVerticalDark_DateTime_pickers_en","",20445,], +["libraries.designsystem.theme.components.previews_TimePickerVerticalLight_DateTime_pickers_en","",20445,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_0_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_1_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_2_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_2_en",0,], -["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_3_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_3_en",20369,], -["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_4_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_4_en",20369,], +["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_3_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_3_en",20445,], +["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_4_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_4_en",20445,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_5_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_5_en",0,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_6_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_6_en",0,], ["features.messages.impl.timeline.components_TimelineEventTimestampView_Day_7_en","features.messages.impl.timeline.components_TimelineEventTimestampView_Night_7_en",0,], @@ -1404,18 +1359,18 @@ export const screenshots = [ ["features.messages.impl.timeline.components.event_TimelineItemAudioView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemAudioView_Night_2_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemAudioView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemAudioView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemAudioView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemAudioView_Night_4_en",0,], -["features.messages.impl.timeline.components_TimelineItemCallNotifyView_Day_0_en","features.messages.impl.timeline.components_TimelineItemCallNotifyView_Night_0_en",20369,], +["features.messages.impl.timeline.components_TimelineItemCallNotifyView_Day_0_en","features.messages.impl.timeline.components_TimelineItemCallNotifyView_Night_0_en",20445,], ["features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Night_0_en",0,], ["features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Day_1_en","features.messages.impl.timeline.components.virtual_TimelineItemDaySeparatorView_Night_1_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_0_en",20369,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_1_en",20369,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_2_en",20369,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_3_en",20369,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_4_en",20369,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_5_en",20369,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_6_en",20369,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_7_en",20369,], -["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_8_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_8_en",20369,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_0_en",20445,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_1_en",20445,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_2_en",20445,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_3_en",20445,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_4_en",20445,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_5_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_5_en",20445,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_6_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_6_en",20445,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_7_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_7_en",20445,], +["features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Day_8_en","features.messages.impl.timeline.components.event_TimelineItemEncryptedView_Night_8_en",20445,], ["features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowDisambiguated_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowForDirectRoom_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowForDirectRoom_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowLongSenderName_en","",0,], @@ -1423,18 +1378,18 @@ export const screenshots = [ ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_2_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_2_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_3_en",20369,], -["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_4_en",20369,], +["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_3_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_3_en",20445,], +["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_4_en",20445,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_5_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_5_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_6_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_6_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_7_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_7_en",20369,], -["features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowUtd_Night_0_en",20369,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Night_0_en",20369,], +["features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Day_7_en","features.messages.impl.timeline.components_TimelineItemEventRowTimestamp_Night_7_en",20445,], +["features.messages.impl.timeline.components_TimelineItemEventRowUtd_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowUtd_Night_0_en",20445,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithManyReactions_Night_0_en",20445,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Day_2_en","features.messages.impl.timeline.components_TimelineItemEventRowWithRR_Night_2_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en",20369,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en",20369,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_0_en",20445,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyInformative_Night_1_en",20445,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_0_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReplyOther_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_0_en",0,], @@ -1443,41 +1398,41 @@ export const screenshots = [ ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_1_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_1_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_2_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_2_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_3_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_3_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en",20369,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_4_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_4_en",20445,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_5_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_5_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_6_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_6_en",0,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_7_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_7_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en",20369,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_8_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_8_en",20445,], ["features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Day_9_en","features.messages.impl.timeline.components_TimelineItemEventRowWithReply_Night_9_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventRowWithThreadSummary_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithThreadSummary_Night_0_en",20369,], +["features.messages.impl.timeline.components_TimelineItemEventRowWithThreadSummary_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRowWithThreadSummary_Night_0_en",20445,], ["features.messages.impl.timeline.components_TimelineItemEventRow_Day_0_en","features.messages.impl.timeline.components_TimelineItemEventRow_Night_0_en",0,], -["features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_en","",20369,], +["features.messages.impl.timeline.components_TimelineItemEventTimestampBelow_en","",20445,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_1_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_2_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemFileView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemFileView_Night_4_en",0,], -["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Night_0_en",20369,], -["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Night_0_en",20369,], -["features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Night_0_en",20369,], +["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentCollapse_Night_0_en",20445,], +["features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Day_0_en","features.messages.impl.timeline.components_TimelineItemGroupedEventsRowContentExpanded_Night_0_en",20445,], +["features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemImageViewHideMediaContent_Night_0_en",20445,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_1_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_2_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemImageView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemImageView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemInformativeView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemInformativeView_Night_0_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Night_0_en",20369,], +["features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemLegacyCallInviteView_Night_0_en",20445,], ["features.messages.impl.timeline.components.event_TimelineItemLocationView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemLocationView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemLocationView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemLocationView_Night_1_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_0_en",20369,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_1_en",20369,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_2_en",20369,], -["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_3_en",20369,], -["features.messages.impl.timeline.components_TimelineItemReactionsLayout_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsLayout_Night_0_en",20369,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_0_en",20445,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_1_en",20445,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_2_en",20445,], +["features.messages.impl.timeline.components.event_TimelineItemPollView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemPollView_Night_3_en",20445,], +["features.messages.impl.timeline.components_TimelineItemReactionsLayout_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsLayout_Night_0_en",20445,], ["features.messages.impl.timeline.components_TimelineItemReactionsViewFew_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewFew_Night_0_en",0,], -["features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Night_0_en",20369,], -["features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Night_0_en",20369,], +["features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewIncoming_Night_0_en",20445,], +["features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsViewOutgoing_Night_0_en",20445,], ["features.messages.impl.timeline.components_TimelineItemReactionsView_Day_0_en","features.messages.impl.timeline.components_TimelineItemReactionsView_Night_0_en",0,], -["features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Night_0_en",20369,], +["features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemReadMarkerView_Night_0_en",20445,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_0_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_0_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_1_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_1_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_2_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_2_en",0,], @@ -1486,8 +1441,8 @@ export const screenshots = [ ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_5_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_5_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_6_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_6_en",0,], ["features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Day_7_en","features.messages.impl.timeline.components.receipt_TimelineItemReadReceiptView_Night_7_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemRedactedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemRedactedView_Night_0_en",20369,], -["features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Night_0_en",20369,], +["features.messages.impl.timeline.components.event_TimelineItemRedactedView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemRedactedView_Night_0_en",20445,], +["features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineItemRoomBeginningView_Night_0_en",20445,], ["features.messages.impl.timeline.components_TimelineItemStateEventRow_Day_0_en","features.messages.impl.timeline.components_TimelineItemStateEventRow_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemStateView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemStateView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemStickerView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemStickerView_Night_0_en",0,], @@ -1502,8 +1457,8 @@ export const screenshots = [ ["features.messages.impl.timeline.components.event_TimelineItemTextView_Day_3_en","features.messages.impl.timeline.components.event_TimelineItemTextView_Night_3_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemTextView_Day_4_en","features.messages.impl.timeline.components.event_TimelineItemTextView_Night_4_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemTextView_Day_5_en","features.messages.impl.timeline.components.event_TimelineItemTextView_Night_5_en",0,], -["features.messages.impl.timeline.components.event_TimelineItemUnknownView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemUnknownView_Night_0_en",20369,], -["features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Night_0_en",20369,], +["features.messages.impl.timeline.components.event_TimelineItemUnknownView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemUnknownView_Night_0_en",20445,], +["features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemVideoViewHideMediaContent_Night_0_en",20445,], ["features.messages.impl.timeline.components.event_TimelineItemVideoView_Day_0_en","features.messages.impl.timeline.components.event_TimelineItemVideoView_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemVideoView_Day_1_en","features.messages.impl.timeline.components.event_TimelineItemVideoView_Night_1_en",0,], ["features.messages.impl.timeline.components.event_TimelineItemVideoView_Day_2_en","features.messages.impl.timeline.components.event_TimelineItemVideoView_Night_2_en",0,], @@ -1526,85 +1481,85 @@ export const screenshots = [ ["features.messages.impl.timeline.components.event_TimelineItemVoiceView_Day_9_en","features.messages.impl.timeline.components.event_TimelineItemVoiceView_Night_9_en",0,], ["features.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_Day_0_en","features.messages.impl.timeline.components.virtual_TimelineLoadingMoreIndicator_Night_0_en",0,], ["features.messages.impl.timeline.components.event_TimelineVideoWithCaptionRow_Day_0_en","features.messages.impl.timeline.components.event_TimelineVideoWithCaptionRow_Night_0_en",0,], -["features.messages.impl.timeline_TimelineViewMessageShield_Day_0_en","features.messages.impl.timeline_TimelineViewMessageShield_Night_0_en",20369,], -["features.messages.impl.timeline_TimelineView_Day_0_en","features.messages.impl.timeline_TimelineView_Night_0_en",20369,], +["features.messages.impl.timeline_TimelineViewMessageShield_Day_0_en","features.messages.impl.timeline_TimelineViewMessageShield_Night_0_en",20445,], +["features.messages.impl.timeline_TimelineView_Day_0_en","features.messages.impl.timeline_TimelineView_Night_0_en",20445,], ["features.messages.impl.timeline_TimelineView_Day_10_en","features.messages.impl.timeline_TimelineView_Night_10_en",0,], -["features.messages.impl.timeline_TimelineView_Day_11_en","features.messages.impl.timeline_TimelineView_Night_11_en",20369,], -["features.messages.impl.timeline_TimelineView_Day_12_en","features.messages.impl.timeline_TimelineView_Night_12_en",20369,], -["features.messages.impl.timeline_TimelineView_Day_13_en","features.messages.impl.timeline_TimelineView_Night_13_en",20369,], -["features.messages.impl.timeline_TimelineView_Day_14_en","features.messages.impl.timeline_TimelineView_Night_14_en",20369,], -["features.messages.impl.timeline_TimelineView_Day_15_en","features.messages.impl.timeline_TimelineView_Night_15_en",20369,], -["features.messages.impl.timeline_TimelineView_Day_16_en","features.messages.impl.timeline_TimelineView_Night_16_en",20369,], -["features.messages.impl.timeline_TimelineView_Day_17_en","features.messages.impl.timeline_TimelineView_Night_17_en",20369,], -["features.messages.impl.timeline_TimelineView_Day_1_en","features.messages.impl.timeline_TimelineView_Night_1_en",20369,], +["features.messages.impl.timeline_TimelineView_Day_11_en","features.messages.impl.timeline_TimelineView_Night_11_en",20445,], +["features.messages.impl.timeline_TimelineView_Day_12_en","features.messages.impl.timeline_TimelineView_Night_12_en",20445,], +["features.messages.impl.timeline_TimelineView_Day_13_en","features.messages.impl.timeline_TimelineView_Night_13_en",20445,], +["features.messages.impl.timeline_TimelineView_Day_14_en","features.messages.impl.timeline_TimelineView_Night_14_en",20445,], +["features.messages.impl.timeline_TimelineView_Day_15_en","features.messages.impl.timeline_TimelineView_Night_15_en",20445,], +["features.messages.impl.timeline_TimelineView_Day_16_en","features.messages.impl.timeline_TimelineView_Night_16_en",20445,], +["features.messages.impl.timeline_TimelineView_Day_17_en","features.messages.impl.timeline_TimelineView_Night_17_en",20445,], +["features.messages.impl.timeline_TimelineView_Day_1_en","features.messages.impl.timeline_TimelineView_Night_1_en",20445,], ["features.messages.impl.timeline_TimelineView_Day_2_en","features.messages.impl.timeline_TimelineView_Night_2_en",0,], ["features.messages.impl.timeline_TimelineView_Day_3_en","features.messages.impl.timeline_TimelineView_Night_3_en",0,], -["features.messages.impl.timeline_TimelineView_Day_4_en","features.messages.impl.timeline_TimelineView_Night_4_en",20369,], +["features.messages.impl.timeline_TimelineView_Day_4_en","features.messages.impl.timeline_TimelineView_Night_4_en",20445,], ["features.messages.impl.timeline_TimelineView_Day_5_en","features.messages.impl.timeline_TimelineView_Night_5_en",0,], -["features.messages.impl.timeline_TimelineView_Day_6_en","features.messages.impl.timeline_TimelineView_Night_6_en",20369,], +["features.messages.impl.timeline_TimelineView_Day_6_en","features.messages.impl.timeline_TimelineView_Night_6_en",20445,], ["features.messages.impl.timeline_TimelineView_Day_7_en","features.messages.impl.timeline_TimelineView_Night_7_en",0,], -["features.messages.impl.timeline_TimelineView_Day_8_en","features.messages.impl.timeline_TimelineView_Night_8_en",20369,], +["features.messages.impl.timeline_TimelineView_Day_8_en","features.messages.impl.timeline_TimelineView_Night_8_en",20445,], ["features.messages.impl.timeline_TimelineView_Day_9_en","features.messages.impl.timeline_TimelineView_Night_9_en",0,], ["libraries.designsystem.components.avatar.internal_TombstonedRoomAvatar_Avatars_en","",0,], ["libraries.designsystem.theme.components_TopAppBarStr_App_Bars_en","",0,], ["libraries.designsystem.theme.components_TopAppBar_App_Bars_en","",0,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_0_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_0_en",20369,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_1_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_1_en",20369,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_2_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_2_en",20369,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_3_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_3_en",20369,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_4_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_4_en",20369,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_5_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_5_en",20369,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_6_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_6_en",20369,], -["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_7_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_7_en",20369,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_0_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_0_en",20445,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_1_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_1_en",20445,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_2_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_2_en",20445,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_3_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_3_en",20445,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_4_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_4_en",20445,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_5_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_5_en",20445,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_6_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_6_en",20445,], +["libraries.troubleshoot.impl_TroubleshootNotificationsView_Day_7_en","libraries.troubleshoot.impl_TroubleshootNotificationsView_Night_7_en",20445,], ["features.messages.impl.typing_TypingNotificationView_Day_0_en","features.messages.impl.typing_TypingNotificationView_Night_0_en",0,], -["features.messages.impl.typing_TypingNotificationView_Day_1_en","features.messages.impl.typing_TypingNotificationView_Night_1_en",20369,], -["features.messages.impl.typing_TypingNotificationView_Day_2_en","features.messages.impl.typing_TypingNotificationView_Night_2_en",20369,], -["features.messages.impl.typing_TypingNotificationView_Day_3_en","features.messages.impl.typing_TypingNotificationView_Night_3_en",20369,], -["features.messages.impl.typing_TypingNotificationView_Day_4_en","features.messages.impl.typing_TypingNotificationView_Night_4_en",20369,], -["features.messages.impl.typing_TypingNotificationView_Day_5_en","features.messages.impl.typing_TypingNotificationView_Night_5_en",20369,], -["features.messages.impl.typing_TypingNotificationView_Day_6_en","features.messages.impl.typing_TypingNotificationView_Night_6_en",20369,], +["features.messages.impl.typing_TypingNotificationView_Day_1_en","features.messages.impl.typing_TypingNotificationView_Night_1_en",20445,], +["features.messages.impl.typing_TypingNotificationView_Day_2_en","features.messages.impl.typing_TypingNotificationView_Night_2_en",20445,], +["features.messages.impl.typing_TypingNotificationView_Day_3_en","features.messages.impl.typing_TypingNotificationView_Night_3_en",20445,], +["features.messages.impl.typing_TypingNotificationView_Day_4_en","features.messages.impl.typing_TypingNotificationView_Night_4_en",20445,], +["features.messages.impl.typing_TypingNotificationView_Day_5_en","features.messages.impl.typing_TypingNotificationView_Night_5_en",20445,], +["features.messages.impl.typing_TypingNotificationView_Day_6_en","features.messages.impl.typing_TypingNotificationView_Night_6_en",20445,], ["features.messages.impl.typing_TypingNotificationView_Day_7_en","features.messages.impl.typing_TypingNotificationView_Night_7_en",0,], ["features.messages.impl.typing_TypingNotificationView_Day_8_en","features.messages.impl.typing_TypingNotificationView_Night_8_en",0,], ["libraries.designsystem.atomic.atoms_UnreadIndicatorAtom_Day_0_en","libraries.designsystem.atomic.atoms_UnreadIndicatorAtom_Night_0_en",0,], -["libraries.matrix.ui.components_UnresolvedUserRow_en","",20369,], +["libraries.matrix.ui.components_UnresolvedUserRow_en","",20445,], ["libraries.matrix.ui.components_UnsavedAvatar_Day_0_en","libraries.matrix.ui.components_UnsavedAvatar_Night_0_en",0,], ["libraries.designsystem.components.avatar.internal_UserAvatarColors_Day_0_en","libraries.designsystem.components.avatar.internal_UserAvatarColors_Night_0_en",0,], -["features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Night_0_en",20369,], -["features.startchat.impl.components_UserListView_Day_0_en","features.startchat.impl.components_UserListView_Night_0_en",20369,], -["features.startchat.impl.components_UserListView_Day_1_en","features.startchat.impl.components_UserListView_Night_1_en",20369,], -["features.startchat.impl.components_UserListView_Day_2_en","features.startchat.impl.components_UserListView_Night_2_en",20369,], +["features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Day_0_en","features.roomdetails.impl.notificationsettings_UserDefinedRoomNotificationSettingsView_Night_0_en",20445,], +["features.startchat.impl.components_UserListView_Day_0_en","features.startchat.impl.components_UserListView_Night_0_en",20445,], +["features.startchat.impl.components_UserListView_Day_1_en","features.startchat.impl.components_UserListView_Night_1_en",20445,], +["features.startchat.impl.components_UserListView_Day_2_en","features.startchat.impl.components_UserListView_Night_2_en",20445,], ["features.startchat.impl.components_UserListView_Day_3_en","features.startchat.impl.components_UserListView_Night_3_en",0,], ["features.startchat.impl.components_UserListView_Day_4_en","features.startchat.impl.components_UserListView_Night_4_en",0,], ["features.startchat.impl.components_UserListView_Day_5_en","features.startchat.impl.components_UserListView_Night_5_en",0,], ["features.startchat.impl.components_UserListView_Day_6_en","features.startchat.impl.components_UserListView_Night_6_en",0,], -["features.startchat.impl.components_UserListView_Day_7_en","features.startchat.impl.components_UserListView_Night_7_en",20369,], +["features.startchat.impl.components_UserListView_Day_7_en","features.startchat.impl.components_UserListView_Night_7_en",20445,], ["features.startchat.impl.components_UserListView_Day_8_en","features.startchat.impl.components_UserListView_Night_8_en",0,], -["features.startchat.impl.components_UserListView_Day_9_en","features.startchat.impl.components_UserListView_Night_9_en",20369,], +["features.startchat.impl.components_UserListView_Day_9_en","features.startchat.impl.components_UserListView_Night_9_en",20445,], ["features.preferences.impl.user_UserPreferences_Day_0_en","features.preferences.impl.user_UserPreferences_Night_0_en",0,], ["features.preferences.impl.user_UserPreferences_Day_1_en","features.preferences.impl.user_UserPreferences_Night_1_en",0,], ["features.preferences.impl.user_UserPreferences_Day_2_en","features.preferences.impl.user_UserPreferences_Night_2_en",0,], -["features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Day_0_en","features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Night_0_en",20369,], -["features.userprofile.shared_UserProfileHeaderSection_Day_0_en","features.userprofile.shared_UserProfileHeaderSection_Night_0_en",20369,], -["features.userprofile.shared_UserProfileView_Day_0_en","features.userprofile.shared_UserProfileView_Night_0_en",20369,], -["features.userprofile.shared_UserProfileView_Day_1_en","features.userprofile.shared_UserProfileView_Night_1_en",20369,], -["features.userprofile.shared_UserProfileView_Day_2_en","features.userprofile.shared_UserProfileView_Night_2_en",20369,], -["features.userprofile.shared_UserProfileView_Day_3_en","features.userprofile.shared_UserProfileView_Night_3_en",20369,], -["features.userprofile.shared_UserProfileView_Day_4_en","features.userprofile.shared_UserProfileView_Night_4_en",20369,], -["features.userprofile.shared_UserProfileView_Day_5_en","features.userprofile.shared_UserProfileView_Night_5_en",20369,], -["features.userprofile.shared_UserProfileView_Day_6_en","features.userprofile.shared_UserProfileView_Night_6_en",20369,], -["features.userprofile.shared_UserProfileView_Day_7_en","features.userprofile.shared_UserProfileView_Night_7_en",20369,], -["features.userprofile.shared_UserProfileView_Day_8_en","features.userprofile.shared_UserProfileView_Night_8_en",20369,], -["features.userprofile.shared_UserProfileView_Day_9_en","features.userprofile.shared_UserProfileView_Night_9_en",20369,], +["features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Day_0_en","features.userprofile.shared_UserProfileHeaderSectionWithVerificationViolation_Night_0_en",20445,], +["features.userprofile.shared_UserProfileHeaderSection_Day_0_en","features.userprofile.shared_UserProfileHeaderSection_Night_0_en",20445,], +["features.userprofile.shared_UserProfileView_Day_0_en","features.userprofile.shared_UserProfileView_Night_0_en",20445,], +["features.userprofile.shared_UserProfileView_Day_1_en","features.userprofile.shared_UserProfileView_Night_1_en",20445,], +["features.userprofile.shared_UserProfileView_Day_2_en","features.userprofile.shared_UserProfileView_Night_2_en",20445,], +["features.userprofile.shared_UserProfileView_Day_3_en","features.userprofile.shared_UserProfileView_Night_3_en",20445,], +["features.userprofile.shared_UserProfileView_Day_4_en","features.userprofile.shared_UserProfileView_Night_4_en",20445,], +["features.userprofile.shared_UserProfileView_Day_5_en","features.userprofile.shared_UserProfileView_Night_5_en",20445,], +["features.userprofile.shared_UserProfileView_Day_6_en","features.userprofile.shared_UserProfileView_Night_6_en",20445,], +["features.userprofile.shared_UserProfileView_Day_7_en","features.userprofile.shared_UserProfileView_Night_7_en",20445,], +["features.userprofile.shared_UserProfileView_Day_8_en","features.userprofile.shared_UserProfileView_Night_8_en",20445,], +["features.userprofile.shared_UserProfileView_Day_9_en","features.userprofile.shared_UserProfileView_Night_9_en",20445,], ["features.verifysession.impl.ui_VerificationUserProfileContent_Day_0_en","features.verifysession.impl.ui_VerificationUserProfileContent_Night_0_en",0,], ["libraries.designsystem.ruler_VerticalRuler_Day_0_en","libraries.designsystem.ruler_VerticalRuler_Night_0_en",0,], ["libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_0_en","libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_0_en",0,], ["libraries.mediaviewer.impl.gallery.ui_VideoItemView_Day_1_en","libraries.mediaviewer.impl.gallery.ui_VideoItemView_Night_1_en",0,], -["features.preferences.impl.advanced_VideoQualitySelectorDialog_Day_0_en","features.preferences.impl.advanced_VideoQualitySelectorDialog_Night_0_en",20369,], -["features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Day_0_en","features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Night_0_en",20369,], +["features.preferences.impl.advanced_VideoQualitySelectorDialog_Day_0_en","features.preferences.impl.advanced_VideoQualitySelectorDialog_Night_0_en",20445,], +["features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Day_0_en","features.messages.impl.attachments.preview_VideoQualitySelectorDialog_Night_0_en",20445,], ["features.viewfolder.impl.file_ViewFileView_Day_0_en","features.viewfolder.impl.file_ViewFileView_Night_0_en",0,], ["features.viewfolder.impl.file_ViewFileView_Day_1_en","features.viewfolder.impl.file_ViewFileView_Night_1_en",0,], ["features.viewfolder.impl.file_ViewFileView_Day_2_en","features.viewfolder.impl.file_ViewFileView_Night_2_en",0,], -["features.viewfolder.impl.file_ViewFileView_Day_3_en","features.viewfolder.impl.file_ViewFileView_Night_3_en",20369,], +["features.viewfolder.impl.file_ViewFileView_Day_3_en","features.viewfolder.impl.file_ViewFileView_Night_3_en",20445,], ["features.viewfolder.impl.file_ViewFileView_Day_4_en","features.viewfolder.impl.file_ViewFileView_Night_4_en",0,], ["features.viewfolder.impl.file_ViewFileView_Day_5_en","features.viewfolder.impl.file_ViewFileView_Night_5_en",0,], ["features.viewfolder.impl.folder_ViewFolderView_Day_0_en","features.viewfolder.impl.folder_ViewFolderView_Night_0_en",0,], @@ -1619,9 +1574,9 @@ export const screenshots = [ ["libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_2_en","libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_2_en",0,], ["libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Day_3_en","libraries.mediaviewer.impl.gallery.ui_VoiceItemView_Night_3_en",0,], ["libraries.textcomposer.components_VoiceMessageDeleteButton_Day_0_en","libraries.textcomposer.components_VoiceMessageDeleteButton_Night_0_en",0,], +["libraries.textcomposer.components_VoiceMessagePreview_Day_0_en","libraries.textcomposer.components_VoiceMessagePreview_Night_0_en",0,], ["libraries.textcomposer.components_VoiceMessageRecorderButton_Day_0_en","libraries.textcomposer.components_VoiceMessageRecorderButton_Night_0_en",0,], ["libraries.textcomposer.components_VoiceMessageRecording_Day_0_en","libraries.textcomposer.components_VoiceMessageRecording_Night_0_en",0,], -["libraries.textcomposer.components_VoiceMessage_Day_0_en","libraries.textcomposer.components_VoiceMessage_Night_0_en",0,], ["libraries.designsystem.components.media_WaveformPlaybackView_Day_0_en","libraries.designsystem.components.media_WaveformPlaybackView_Night_0_en",0,], ["libraries.designsystem.ruler_WithRulers_Day_0_en","libraries.designsystem.ruler_WithRulers_Night_0_en",0,], ]; diff --git a/screenshots/html/screenshots.css b/screenshots/html/screenshots.css index 72e17f2e46..5d91c44352 100644 --- a/screenshots/html/screenshots.css +++ b/screenshots/html/screenshots.css @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ html { diff --git a/screenshots/html/script.js b/screenshots/html/script.js index 95ce39b183..e581aa812b 100644 --- a/screenshots/html/script.js +++ b/screenshots/html/script.js @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ import { screenshots } from './data.js'; diff --git a/screenshots/index.html b/screenshots/index.html index 8bd61a6570..1a4d55528c 100644 --- a/screenshots/index.html +++ b/screenshots/index.html @@ -1,7 +1,8 @@ diff --git a/services/analytics/api/build.gradle.kts b/services/analytics/api/build.gradle.kts index 676d27d339..1e1f0710f1 100644 --- a/services/analytics/api/build.gradle.kts +++ b/services/analytics/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsLongRunningTransaction.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsLongRunningTransaction.kt new file mode 100644 index 0000000000..b318807a45 --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsLongRunningTransaction.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analytics.api + +sealed class AnalyticsLongRunningTransaction( + val name: String, + val operation: String?, +) { + data object ColdStartUntilCachedRoomList : AnalyticsLongRunningTransaction("Cold start until cached room list is displayed", null) + data object FirstRoomsDisplayed : AnalyticsLongRunningTransaction("First rooms displayed after login or restoration", null) + data object ResumeAppUntilNewRoomsReceived : AnalyticsLongRunningTransaction("App was resumed and new room list items arrived", null) + data object NotificationTapOpensTimeline : AnalyticsLongRunningTransaction("A notification was tapped and it opened a timeline", null) + data object OpenRoom : AnalyticsLongRunningTransaction("Open a room and see loaded items in the timeline", null) + data object LoadJoinedRoomFlow : AnalyticsLongRunningTransaction("Load joined room UI", "ui.load") + data object LoadMessagesUi : AnalyticsLongRunningTransaction("Load messages UI", "ui.load") + data object DisplayFirstTimelineItems : AnalyticsLongRunningTransaction("Get and display first timeline items", null) +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsSdkManager.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsSdkManager.kt new file mode 100644 index 0000000000..bf5e04509c --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsSdkManager.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analytics.api + +/** + * Manager to handle SDK analytics (e.g., Sentry). + */ +interface AnalyticsSdkManager { + /** + * Enable or disable SDK analytics. + */ + fun enableSdkAnalytics(enabled: Boolean) + + /** + * Start a new span with the given [name], using [parentTraceId] to optionally attach it to a parent transaction. + */ + fun startSpan(name: String, parentTraceId: String? = null): AnalyticsSdkSpan + + /** + * Create a 'bridge' span optionally linking it to a parent trace via [parentTraceId]. + */ + fun bridge(parentTraceId: String? = null): AnalyticsSdkSpan +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsSdkSpan.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsSdkSpan.kt new file mode 100644 index 0000000000..92f79da7f9 --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsSdkSpan.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analytics.api + +/** + * Represents an analytics span in the Rust SDK. + */ +interface AnalyticsSdkSpan { + /** Enters the span and starts collecting metrics. */ + fun enter() + + /** Exit the span and stop collecting the metrics. A request should be sent shortly after. */ + fun exit() +} 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 589abf28fe..365300d679 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 @@ -1,13 +1,15 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.services.analytics.api import io.element.android.services.analyticsproviders.api.AnalyticsProvider +import io.element.android.services.analyticsproviders.api.AnalyticsTransaction import io.element.android.services.analyticsproviders.api.trackers.AnalyticsTracker import io.element.android.services.analyticsproviders.api.trackers.ErrorTracker import kotlinx.coroutines.flow.Flow @@ -47,4 +49,76 @@ interface AnalyticsService : AnalyticsTracker, ErrorTracker { * Update analyticsId from the AccountData. */ suspend fun setAnalyticsId(analyticsId: String) + + /** + * Starts a transaction to measure the performance of an operation. + */ + fun startTransaction(name: String, operation: String? = null): AnalyticsTransaction + + /** + * Starts an [AnalyticsLongRunningTransaction], that can be shared with other components. + */ + fun startLongRunningTransaction( + longRunningTransaction: AnalyticsLongRunningTransaction, + parentTransaction: AnalyticsTransaction? = null + ): AnalyticsTransaction + + /** + * Gets an ongoing [AnalyticsLongRunningTransaction], if it exists. + */ + fun getLongRunningTransaction(longRunningTransaction: AnalyticsLongRunningTransaction): AnalyticsTransaction? + + /** + * Removes an ongoing [AnalyticsLongRunningTransaction] so it's no longer shared. + */ + fun removeLongRunningTransaction(longRunningTransaction: AnalyticsLongRunningTransaction): AnalyticsTransaction? + + /** Enter a span inside the Rust SDK tracing system. If a [parentTraceId] is provided, the SDK trace will be added as a child of that trace. */ + fun enterSdkSpan(name: String?, parentTraceId: String?): AnalyticsSdkSpan +} + +inline fun AnalyticsService.recordTransaction( + name: String, + operation: String, + parentTransaction: AnalyticsTransaction? = null, + block: (AnalyticsTransaction) -> T +): T { + val transaction = parentTransaction?.startChild(name, operation) + ?: startTransaction(name, operation) + try { + val result = block(transaction) + return result + } finally { + transaction.finish() + } +} + +/** + * Cancels a long running transaction. It behaves the same as [AnalyticsService.removeLongRunningTransaction], + * but it doesn't return the transaction so we can't finish it later. + */ +fun AnalyticsService.cancelLongRunningTransaction( + longRunningTransaction: AnalyticsLongRunningTransaction +) = removeLongRunningTransaction(longRunningTransaction) + +/** + * Finishes a long running transaction if it exists. Optionally performs an [action] with the transaction before finishing it. + */ +fun AnalyticsService.finishLongRunningTransaction( + longRunningTransaction: AnalyticsLongRunningTransaction, + action: (AnalyticsTransaction) -> Unit = {}, +) { + removeLongRunningTransaction(longRunningTransaction)?.let { + action(it) + it.finish() + } +} + +inline fun AnalyticsService.inBridgeSdkSpan(parentTraceId: String?, block: (AnalyticsSdkSpan) -> T): T { + val span = enterSdkSpan(name = null, parentTraceId = parentTraceId) + return try { + block(span) + } finally { + span.exit() + } } diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/NoopAnalyticsSdkSpan.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/NoopAnalyticsSdkSpan.kt new file mode 100644 index 0000000000..d4db9359e2 --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/NoopAnalyticsSdkSpan.kt @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analytics.api + +object NoopAnalyticsSdkSpan : AnalyticsSdkSpan { + override fun enter() {} + override fun exit() {} +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/NoopAnalyticsTransaction.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/NoopAnalyticsTransaction.kt new file mode 100644 index 0000000000..914fac1b12 --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/NoopAnalyticsTransaction.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analytics.api + +import io.element.android.services.analyticsproviders.api.AnalyticsTransaction + +object NoopAnalyticsTransaction : AnalyticsTransaction { + override fun startChild(operation: String, description: String?): AnalyticsTransaction = NoopAnalyticsTransaction + override fun putExtraData(key: String, value: String) {} + override fun putIndexableData(key: String, value: String) {} + override fun isFinished(): Boolean = true + override fun traceId(): String? = null + override fun attachError(throwable: Throwable) {} + override fun finish() {} +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/ScreenTracker.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/ScreenTracker.kt index a0987eff17..29513f66ff 100644 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/ScreenTracker.kt +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/ScreenTracker.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/watchers/AnalyticsColdStartWatcher.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/watchers/AnalyticsColdStartWatcher.kt new file mode 100644 index 0000000000..67d3c5498b --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/watchers/AnalyticsColdStartWatcher.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analytics.api.watchers + +/** + * Adds a performance check transaction measuring the time between a cold start (or, after we read the user consent after a cold start) + * until the cached room list is displayed. This check only takes place in a cold app start after the user is authenticated. + */ +interface AnalyticsColdStartWatcher { + fun start() + fun whenLoggingIn() + fun onRoomListVisible() +} diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/watchers/AnalyticsRoomListStateWatcher.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/watchers/AnalyticsRoomListStateWatcher.kt new file mode 100644 index 0000000000..2f6d4723a3 --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/watchers/AnalyticsRoomListStateWatcher.kt @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analytics.api.watchers + +/** + * This component is used to check how long it takes for the room list to be up to date after opening the app while it's on a 'warm' state: + * the app was previously running and we just returned to it. + */ +interface AnalyticsRoomListStateWatcher { + fun start() + fun stop() +} diff --git a/services/analytics/compose/build.gradle.kts b/services/analytics/compose/build.gradle.kts index 4809bbb94b..fd01149dcb 100644 --- a/services/analytics/compose/build.gradle.kts +++ b/services/analytics/compose/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/services/analytics/compose/src/main/kotlin/io/element/android/services/analytics/compose/LocalAnalyticsService.kt b/services/analytics/compose/src/main/kotlin/io/element/android/services/analytics/compose/LocalAnalyticsService.kt index b5c59bac46..0a735869a3 100644 --- a/services/analytics/compose/src/main/kotlin/io/element/android/services/analytics/compose/LocalAnalyticsService.kt +++ b/services/analytics/compose/src/main/kotlin/io/element/android/services/analytics/compose/LocalAnalyticsService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/analytics/impl/build.gradle.kts b/services/analytics/impl/build.gradle.kts index 83eebead5d..6ba2963853 100644 --- a/services/analytics/impl/build.gradle.kts +++ b/services/analytics/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -24,14 +25,20 @@ dependencies { implementation(projects.libraries.core) implementation(projects.libraries.architecture) implementation(projects.libraries.designsystem) + implementation(projects.libraries.matrix.api) implementation(projects.libraries.preferences.api) implementation(projects.libraries.sessionStorage.api) + implementation(projects.services.appnavstate.api) api(projects.services.analyticsproviders.api) api(projects.services.analytics.api) implementation(libs.androidx.datastore.preferences) testCommonDependencies(libs) + testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.sessionStorage.test) + testImplementation(projects.services.analytics.test) testImplementation(projects.services.analyticsproviders.test) + testImplementation(projects.services.appnavstate.test) + testImplementation(projects.services.toolbox.test) } 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 04f0f6867a..5db1eb1b31 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.services.analytics.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import dev.zacsweers.metro.binding import im.vector.app.features.analytics.itf.VectorAnalyticsEvent @@ -17,32 +17,37 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsScreen import im.vector.app.features.analytics.plan.SuperProperties import im.vector.app.features.analytics.plan.UserProperties import io.element.android.libraries.di.annotations.AppCoroutineScope -import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.sessionstorage.api.observer.SessionListener import io.element.android.libraries.sessionstorage.api.observer.SessionObserver +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction +import io.element.android.services.analytics.api.AnalyticsSdkManager +import io.element.android.services.analytics.api.AnalyticsSdkSpan import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.api.NoopAnalyticsSdkSpan +import io.element.android.services.analytics.api.NoopAnalyticsTransaction import io.element.android.services.analytics.impl.log.analyticsTag import io.element.android.services.analytics.impl.store.AnalyticsStore import io.element.android.services.analyticsproviders.api.AnalyticsProvider +import io.element.android.services.analyticsproviders.api.AnalyticsTransaction import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import timber.log.Timber +import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicBoolean @SingleIn(AppScope::class) @ContributesBinding(AppScope::class, binding = binding()) -@Inject class DefaultAnalyticsService( private val analyticsProviders: Set<@JvmSuppressWildcards AnalyticsProvider>, private val analyticsStore: AnalyticsStore, -// private val lateInitUserPropertiesFactory: LateInitUserPropertiesFactory, - @AppCoroutineScope - private val coroutineScope: CoroutineScope, + @AppCoroutineScope private val coroutineScope: CoroutineScope, private val sessionObserver: SessionObserver, - private val sessionStore: SessionStore, + private val analyticsSdkManager: AnalyticsSdkManager, ) : AnalyticsService, SessionListener { + private val pendingLongRunningTransactions = ConcurrentHashMap() + // Cache for the store values private val userConsent = AtomicBoolean(false) @@ -65,6 +70,7 @@ class DefaultAnalyticsService( override suspend fun setUserConsent(userConsent: Boolean) { Timber.tag(analyticsTag.value).d("setUserConsent($userConsent)") analyticsStore.setUserConsent(userConsent) + analyticsSdkManager.enableSdkAnalytics(enabled = userConsent) } override suspend fun setDidAskUserConsent() { @@ -77,14 +83,11 @@ class DefaultAnalyticsService( analyticsStore.setAnalyticsId(analyticsId) } - override suspend fun onSessionCreated(userId: String) { - // Nothing to do - } - - override suspend fun onSessionDeleted(userId: String) { + override suspend fun onSessionDeleted(userId: String, wasLastSession: Boolean) { // Delete the store when the last session is deleted - if (sessionStore.getAllSessions().isEmpty()) { + if (wasLastSession) { analyticsStore.reset() + analyticsSdkManager.enableSdkAnalytics(false) } } @@ -145,4 +148,55 @@ class DefaultAnalyticsService( analyticsProviders.onEach { it.trackError(throwable) } } } + + override fun addExtraData(key: String, value: String) { + if (userConsent.get()) { + analyticsProviders.onEach { it.addExtraData(key, value) } + } + } + + override fun addIndexableData(key: String, value: String) { + if (userConsent.get()) { + analyticsProviders.onEach { it.addIndexableData(key, value) } + } + } + + override fun startTransaction(name: String, operation: String?): AnalyticsTransaction { + return if (userConsent.get()) { + analyticsProviders.firstNotNullOfOrNull { it.startTransaction(name, operation) } + } else { + null + } ?: NoopAnalyticsTransaction + } + + override fun startLongRunningTransaction( + longRunningTransaction: AnalyticsLongRunningTransaction, + parentTransaction: AnalyticsTransaction?, + ): AnalyticsTransaction { + val transaction = parentTransaction?.startChild(longRunningTransaction.name, longRunningTransaction.operation) + ?: startTransaction(longRunningTransaction.name, longRunningTransaction.operation) + + pendingLongRunningTransactions[longRunningTransaction] = transaction + return transaction + } + + override fun getLongRunningTransaction(longRunningTransaction: AnalyticsLongRunningTransaction): AnalyticsTransaction? { + return pendingLongRunningTransactions[longRunningTransaction] + } + + override fun removeLongRunningTransaction(longRunningTransaction: AnalyticsLongRunningTransaction): AnalyticsTransaction? { + return pendingLongRunningTransactions.remove(longRunningTransaction) + } + + override fun enterSdkSpan(name: String?, parentTraceId: String?): AnalyticsSdkSpan { + return if (userConsent.get()) { + if (name != null) { + analyticsSdkManager.startSpan(name, parentTraceId) + } else { + analyticsSdkManager.bridge(parentTraceId) + }.apply { enter() } + } else { + NoopAnalyticsSdkSpan + } + } } diff --git a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultScreenTracker.kt b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultScreenTracker.kt index 78ff13e554..1072d842ca 100644 --- a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultScreenTracker.kt +++ b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultScreenTracker.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,7 +16,6 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.Lifecycle import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.libraries.designsystem.utils.OnLifecycleEvent import io.element.android.services.analytics.api.AnalyticsService @@ -23,10 +23,9 @@ import io.element.android.services.analytics.api.ScreenTracker import io.element.android.services.toolbox.api.systemclock.SystemClock @ContributesBinding(AppScope::class) -@Inject class DefaultScreenTracker( private val analyticsService: AnalyticsService, - private val systemClock: SystemClock + private val systemClock: SystemClock, ) : ScreenTracker { @Composable override fun TrackScreen( diff --git a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/log/AnalyticsLoggerTag.kt b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/log/AnalyticsLoggerTag.kt index 53314735ba..245c77c9ae 100644 --- a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/log/AnalyticsLoggerTag.kt +++ b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/log/AnalyticsLoggerTag.kt @@ -1,7 +1,8 @@ /* - * Copyright 2021-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2021-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/store/AnalyticsStore.kt b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/store/AnalyticsStore.kt index b6e1773be6..edac8b7d7a 100644 --- a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/store/AnalyticsStore.kt +++ b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/store/AnalyticsStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2021-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2021-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,7 +13,6 @@ import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory import kotlinx.coroutines.flow.Flow @@ -36,7 +36,6 @@ interface AnalyticsStore { } @ContributesBinding(AppScope::class) -@Inject class DefaultAnalyticsStore( preferenceDataStoreFactory: PreferenceDataStoreFactory, ) : AnalyticsStore { diff --git a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsColdStartWatcher.kt b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsColdStartWatcher.kt new file mode 100644 index 0000000000..c53e28918b --- /dev/null +++ b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsColdStartWatcher.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analytics.impl.watchers + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.SingleIn +import io.element.android.libraries.di.annotations.AppCoroutineScope +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.api.cancelLongRunningTransaction +import io.element.android.services.analytics.api.finishLongRunningTransaction +import io.element.android.services.analytics.api.watchers.AnalyticsColdStartWatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import timber.log.Timber +import java.util.concurrent.atomic.AtomicBoolean + +@SingleIn(AppScope::class) +@ContributesBinding(AppScope::class) +class DefaultAnalyticsColdStartWatcher( + private val analyticsService: AnalyticsService, + @AppCoroutineScope private val appCoroutineScope: CoroutineScope, +) : AnalyticsColdStartWatcher { + private val isColdStart = AtomicBoolean(true) + + override fun start() { + analyticsService.userConsentFlow + .onEach { hasConsent -> + if (hasConsent) { + if (isColdStart.get()) { + Timber.d("Starting cold start check") + analyticsService.startLongRunningTransaction(AnalyticsLongRunningTransaction.ColdStartUntilCachedRoomList) + } else { + error("The app is no longer in a cold start state") + } + } + } + .catch { Timber.w(it.message) } + .launchIn(appCoroutineScope) + } + + override fun whenLoggingIn() { + if (isColdStart.getAndSet(false)) { + analyticsService.cancelLongRunningTransaction(AnalyticsLongRunningTransaction.ColdStartUntilCachedRoomList) + Timber.d("Canceled cold start check: user is logging in") + } + } + + override fun onRoomListVisible() { + if (isColdStart.getAndSet(false)) { + Timber.d("Room list is visible, finishing cold start check") + analyticsService.finishLongRunningTransaction(AnalyticsLongRunningTransaction.ColdStartUntilCachedRoomList) + } + } +} diff --git a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsRoomListStateWatcher.kt b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsRoomListStateWatcher.kt new file mode 100644 index 0000000000..97e438fcee --- /dev/null +++ b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsRoomListStateWatcher.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analytics.impl.watchers + +import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.coroutine.childScope +import io.element.android.libraries.core.coroutine.withPreviousValue +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.di.annotations.SessionCoroutineScope +import io.element.android.libraries.matrix.api.roomlist.RoomListService +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.api.finishLongRunningTransaction +import io.element.android.services.analytics.api.watchers.AnalyticsRoomListStateWatcher +import io.element.android.services.appnavstate.api.AppNavigationStateService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import timber.log.Timber +import java.util.concurrent.atomic.AtomicBoolean + +@ContributesBinding(SessionScope::class) +class DefaultAnalyticsRoomListStateWatcher( + private val appNavigationStateService: AppNavigationStateService, + private val roomListService: RoomListService, + private val analyticsService: AnalyticsService, + @SessionCoroutineScope sessionCoroutineScope: CoroutineScope, + dispatchers: CoroutineDispatchers, +) : AnalyticsRoomListStateWatcher { + private val coroutineScope: CoroutineScope = sessionCoroutineScope.childScope(dispatchers.computation, "AnalyticsRoomListStateWatcher") + private val isStarted = AtomicBoolean(false) + private val isWarmState = AtomicBoolean(false) + + override fun start() { + if (isStarted.getAndSet(true)) { + Timber.w("Can't start RoomListStateWatcher, it's already running.") + return + } + + appNavigationStateService.appNavigationState + .map { it.isInForeground } + .distinctUntilChanged() + .withPreviousValue() + .onEach { (wasInForeground, isInForeground) -> + if (isInForeground && roomListService.state.value != RoomListService.State.Running) { + analyticsService.startLongRunningTransaction(AnalyticsLongRunningTransaction.ResumeAppUntilNewRoomsReceived) + } + + if (wasInForeground == false && isInForeground) { + isWarmState.set(true) + } + } + .launchIn(coroutineScope) + + roomListService.state + .onEach { state -> + if (state == RoomListService.State.Running && isWarmState.get()) { + analyticsService.finishLongRunningTransaction(AnalyticsLongRunningTransaction.ResumeAppUntilNewRoomsReceived) + } + } + .launchIn(coroutineScope) + } + + override fun stop() { + if (isStarted.getAndSet(false)) { + coroutineScope.coroutineContext.cancelChildren() + } + } +} diff --git a/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsServiceTest.kt b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsServiceTest.kt index 1e5a54fb26..7de3d0ac53 100644 --- a/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsServiceTest.kt +++ b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsServiceTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,9 +17,8 @@ import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.analytics.plan.PollEnd import im.vector.app.features.analytics.plan.SuperProperties import im.vector.app.features.analytics.plan.UserProperties -import io.element.android.libraries.sessionstorage.api.SessionStore +import io.element.android.libraries.matrix.test.analytics.FakeAnalyticsSdkManager import io.element.android.libraries.sessionstorage.api.observer.SessionObserver -import io.element.android.libraries.sessionstorage.test.InMemorySessionStore import io.element.android.libraries.sessionstorage.test.observer.NoOpSessionObserver import io.element.android.services.analytics.impl.store.AnalyticsStore import io.element.android.services.analytics.impl.store.FakeAnalyticsStore @@ -33,6 +33,7 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test @@ -127,17 +128,20 @@ class DefaultAnalyticsServiceTest { } @Test - fun `setUserConsent is sent to the store`() = runTest { + fun `setUserConsent is sent to the store and the SDK`() = runTest { + val sdkAnalyticsEnabledLambda = lambdaRecorder {} val store = FakeAnalyticsStore() val sut = createDefaultAnalyticsService( coroutineScope = backgroundScope, analyticsStore = store, + sdkAnalyticsManager = FakeAnalyticsSdkManager(sdkAnalyticsEnabledLambda), ) assertThat(store.userConsentFlow.first()).isFalse() assertThat(sut.userConsentFlow.first()).isFalse() sut.setUserConsent(true) assertThat(store.userConsentFlow.first()).isTrue() assertThat(sut.userConsentFlow.first()).isTrue() + sdkAnalyticsEnabledLambda.assertions().isCalledOnce().with(value(true)) } @Test @@ -170,6 +174,23 @@ class DefaultAnalyticsServiceTest { @Test fun `when the last session is deleted, the store is reset`() = runTest { + val resetLambda = lambdaRecorder {} + val sdkAnalyticsEnabledLambda = lambdaRecorder {} + val store = FakeAnalyticsStore( + resetLambda = resetLambda, + ) + val sut = createDefaultAnalyticsService( + coroutineScope = backgroundScope, + analyticsStore = store, + sdkAnalyticsManager = FakeAnalyticsSdkManager(sdkAnalyticsEnabledLambda), + ) + sut.onSessionDeleted("userId", true) + resetLambda.assertions().isCalledOnce() + sdkAnalyticsEnabledLambda.assertions().isCalledOnce().with(value(false)) + } + + @Test + fun `when a session is deleted, the store is not reset if it was not the last session`() = runTest { val resetLambda = lambdaRecorder { } val store = FakeAnalyticsStore( resetLambda = resetLambda, @@ -178,8 +199,8 @@ class DefaultAnalyticsServiceTest { coroutineScope = backgroundScope, analyticsStore = store, ) - sut.onSessionDeleted("userId") - resetLambda.assertions().isCalledOnce() + sut.onSessionDeleted("userId", false) + resetLambda.assertions().isNeverCalled() } @Test @@ -221,7 +242,6 @@ class DefaultAnalyticsServiceTest { fun `when consent is provided, updateUserProperties is sent to the provider`() = runTest { val updateUserPropertiesLambda = lambdaRecorder { _ -> } val sut = createDefaultAnalyticsService( - coroutineScope = backgroundScope, analyticsProviders = setOf( FakeAnalyticsProvider( initLambda = { }, @@ -238,7 +258,6 @@ class DefaultAnalyticsServiceTest { fun `when super properties are updated, updateSuperProperties is sent to the provider`() = runTest { val updateSuperPropertiesLambda = lambdaRecorder { _ -> } val sut = createDefaultAnalyticsService( - coroutineScope = backgroundScope, analyticsProviders = setOf( FakeAnalyticsProvider( initLambda = { }, @@ -251,8 +270,15 @@ class DefaultAnalyticsServiceTest { updateSuperPropertiesLambda.assertions().isCalledOnce().with(value(aSuperProperty)) } - private suspend fun createDefaultAnalyticsService( - coroutineScope: CoroutineScope, + @Test + fun `startSdkSpan returns a span from the AnalyticsSdkManager`() = runTest { + val sut = createDefaultAnalyticsService() + val span = sut.enterSdkSpan("spanName", "parentTraceId") + assertThat(span).isNotNull() + } + + private suspend fun TestScope.createDefaultAnalyticsService( + coroutineScope: CoroutineScope = backgroundScope, analyticsProviders: Set<@JvmSuppressWildcards AnalyticsProvider> = setOf( FakeAnalyticsProvider( stopLambda = { }, @@ -260,13 +286,13 @@ class DefaultAnalyticsServiceTest { ), analyticsStore: AnalyticsStore = FakeAnalyticsStore(), sessionObserver: SessionObserver = NoOpSessionObserver(), - sessionStore: SessionStore = InMemorySessionStore(), + sdkAnalyticsManager: FakeAnalyticsSdkManager = FakeAnalyticsSdkManager(enableSdkAnalyticsLambda = {}), ) = DefaultAnalyticsService( analyticsProviders = analyticsProviders, analyticsStore = analyticsStore, coroutineScope = coroutineScope, sessionObserver = sessionObserver, - sessionStore = sessionStore, + analyticsSdkManager = sdkAnalyticsManager, ).also { // Wait for the service to be ready delay(1) diff --git a/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultScreenTrackerTest.kt b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultScreenTrackerTest.kt new file mode 100644 index 0000000000..13e026eb31 --- /dev/null +++ b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultScreenTrackerTest.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analytics.impl + +import androidx.lifecycle.Lifecycle +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import im.vector.app.features.analytics.plan.MobileScreen +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.services.toolbox.api.systemclock.SystemClock +import io.element.android.services.toolbox.test.systemclock.FakeSystemClock +import io.element.android.tests.testutils.FakeLifecycleOwner +import io.element.android.tests.testutils.withFakeLifecycleOwner +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class DefaultScreenTrackerTest { + @Test + fun `TrackScreen is working as expected`() = runTest { + val analyticsService = FakeAnalyticsService() + val systemClock = FakeSystemClock(150) + val lifecycleOwner = FakeLifecycleOwner() + val sut = createDefaultScreenTracker( + analyticsService = analyticsService, + systemClock = systemClock, + ) + moleculeFlow(RecompositionMode.Immediate) { + withFakeLifecycleOwner(lifecycleOwner) { + sut.TrackScreen(MobileScreen.ScreenName.RoomMembers) + } + }.test { + // Screen resumes + lifecycleOwner.givenState(Lifecycle.State.RESUMED) + assertThat(awaitItem()).isEqualTo(Unit) + systemClock.epochMillisResult = 450 + lifecycleOwner.givenState(Lifecycle.State.DESTROYED) + } + assertThat(analyticsService.screenEvents).containsExactly( + MobileScreen( + screenName = MobileScreen.ScreenName.RoomMembers, + durationMs = 300, + ) + ) + } +} + +private fun createDefaultScreenTracker( + analyticsService: AnalyticsService = FakeAnalyticsService(), + systemClock: SystemClock = FakeSystemClock(), +) = DefaultScreenTracker( + analyticsService = analyticsService, + systemClock = systemClock, +) diff --git a/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/store/FakeAnalyticsStore.kt b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/store/FakeAnalyticsStore.kt index 8cfa3649ec..7d9dbfb557 100644 --- a/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/store/FakeAnalyticsStore.kt +++ b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/store/FakeAnalyticsStore.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsColdStartWatcherTest.kt b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsColdStartWatcherTest.kt new file mode 100644 index 0000000000..325316b45c --- /dev/null +++ b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsColdStartWatcherTest.kt @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analytics.impl.watchers + +import com.google.common.truth.Truth.assertThat +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.ColdStartUntilCachedRoomList +import io.element.android.services.analytics.test.FakeAnalyticsService +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class DefaultAnalyticsColdStartWatcherTest { + @Test + fun `watch - until room list is visible`() = runTest { + val analyticsService = FakeAnalyticsService() + val watcher = createAnalyticsColdStartWatcher(analyticsService) + + // Start watching + watcher.start() + + // The user has given analytics consent, we can start tracking the cold start + analyticsService.setUserConsent(true) + runCurrent() + + // The transaction is running + assertThat(analyticsService.getLongRunningTransaction(ColdStartUntilCachedRoomList)).isNotNull() + + // As soon as the room list is visible + watcher.onRoomListVisible() + runCurrent() + + // The transaction is now finished + assertThat(analyticsService.getLongRunningTransaction(ColdStartUntilCachedRoomList)).isNull() + } + + @Test + fun `watch - user is logging in, transaction is cancelled since it's not a cold start`() = runTest { + val analyticsService = FakeAnalyticsService() + val watcher = createAnalyticsColdStartWatcher(analyticsService) + + // Start watching + watcher.start() + + // The user has given analytics consent, we can start tracking the cold start + analyticsService.setUserConsent(true) + runCurrent() + + // The transaction is running + assertThat(analyticsService.getLongRunningTransaction(ColdStartUntilCachedRoomList)).isNotNull() + + // If the user starts a login flow + watcher.whenLoggingIn() + runCurrent() + + // The transaction is gone + assertThat(analyticsService.getLongRunningTransaction(ColdStartUntilCachedRoomList)).isNull() + } + + @Test + fun `watch - user was logging in, transaction is never started since it's not a cold start`() = runTest { + val analyticsService = FakeAnalyticsService() + val watcher = createAnalyticsColdStartWatcher(analyticsService) + + // Start watching + watcher.start() + + // If the user starts a login flow + watcher.whenLoggingIn() + + // The user has given analytics consent, we can start tracking the cold start + analyticsService.setUserConsent(true) + runCurrent() + + // The transaction never starts + assertThat(analyticsService.getLongRunningTransaction(ColdStartUntilCachedRoomList)).isNull() + } + + @Test + fun `watch - never gets consent so it does nothing`() = runTest { + val analyticsService = FakeAnalyticsService() + val watcher = createAnalyticsColdStartWatcher(analyticsService) + + // Start watching + watcher.start() + + // The user never gets the analytics consent, so we do nothing + runCurrent() + + // The transaction is not running in that case + assertThat(analyticsService.getLongRunningTransaction(ColdStartUntilCachedRoomList)).isNull() + } + + private fun TestScope.createAnalyticsColdStartWatcher( + analyticsService: FakeAnalyticsService = FakeAnalyticsService(), + ) = DefaultAnalyticsColdStartWatcher( + analyticsService = analyticsService, + appCoroutineScope = backgroundScope, + ) +} diff --git a/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsRoomListStateWatcherTest.kt b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsRoomListStateWatcherTest.kt new file mode 100644 index 0000000000..3b1f562c67 --- /dev/null +++ b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/watchers/DefaultAnalyticsRoomListStateWatcherTest.kt @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analytics.impl.watchers + +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.roomlist.RoomListService +import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction.ResumeAppUntilNewRoomsReceived +import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.services.appnavstate.api.AppNavigationState +import io.element.android.services.appnavstate.api.NavigationState +import io.element.android.services.appnavstate.test.FakeAppNavigationStateService +import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +class DefaultAnalyticsRoomListStateWatcherTest { + @Test + fun `Opening the app in a warm state tracks the time until the room list is synced`() = runTest { + val navigationStateService = FakeAppNavigationStateService() + val roomListService = FakeRoomListService().apply { + postState(RoomListService.State.Idle) + } + val analyticsService = FakeAnalyticsService() + val watcher = createAnalyticsRoomListStateWatcher( + appNavigationStateService = navigationStateService, + roomListService = roomListService, + analyticsService = analyticsService, + ) + + watcher.start() + + // Give some time to load the initial state + runCurrent() + + // Make sure it's warm by changing its internal state + navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = false)) + runCurrent() + navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true)) + runCurrent() + + // The transaction should be present now + assertThat(analyticsService.getLongRunningTransaction(ResumeAppUntilNewRoomsReceived)).isNotNull() + + // And now the room list service running + roomListService.postState(RoomListService.State.Running) + runCurrent() + + // And the transaction should now be gone + assertThat(analyticsService.getLongRunningTransaction(ResumeAppUntilNewRoomsReceived)).isNull() + + watcher.stop() + } + + @Test + fun `Opening the app in a cold state does nothing`() = runTest { + val navigationStateService = FakeAppNavigationStateService().apply { + appNavigationState.emit(AppNavigationState(NavigationState.Root, false)) + } + val roomListService = FakeRoomListService().apply { + postState(RoomListService.State.Idle) + } + val analyticsService = FakeAnalyticsService() + val watcher = createAnalyticsRoomListStateWatcher( + appNavigationStateService = navigationStateService, + roomListService = roomListService, + analyticsService = analyticsService, + ) + + watcher.start() + + // Give some time to load the initial state + runCurrent() + + // The room list service running + roomListService.postState(RoomListService.State.Running) + runCurrent() + + // The transaction was never present + assertThat(analyticsService.getLongRunningTransaction(ResumeAppUntilNewRoomsReceived)).isNull() + + watcher.stop() + } + + @Test + fun `The transaction won't be finished until the room list is synchronised`() = runTest { + val navigationStateService = FakeAppNavigationStateService() + val roomListService = FakeRoomListService().apply { + postState(RoomListService.State.Idle) + } + val analyticsService = FakeAnalyticsService() + val watcher = createAnalyticsRoomListStateWatcher( + appNavigationStateService = navigationStateService, + roomListService = roomListService, + analyticsService = analyticsService, + ) + + watcher.start() + + // Give some time to load the initial state + runCurrent() + + // Make sure it's warm by changing its internal state + navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = false)) + runCurrent() + navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true)) + runCurrent() + + // The transaction should be present now + assertThat(analyticsService.getLongRunningTransaction(ResumeAppUntilNewRoomsReceived)).isNotNull() + + runCurrent() + + // But without the room list syncing, it never finishes + assertThat(analyticsService.getLongRunningTransaction(ResumeAppUntilNewRoomsReceived)).isNotNull() + + watcher.stop() + } + + @Test + fun `Opening the app when the room list state was already Running does nothing`() = runTest { + val navigationStateService = FakeAppNavigationStateService() + val roomListService = FakeRoomListService().apply { + postState(RoomListService.State.Running) + } + val analyticsService = FakeAnalyticsService() + val watcher = createAnalyticsRoomListStateWatcher( + appNavigationStateService = navigationStateService, + roomListService = roomListService, + analyticsService = analyticsService, + ) + + watcher.start() + + // Give some time to load the initial state + runCurrent() + + // Make sure it's warm by changing its internal state + navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = false)) + runCurrent() + navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true)) + runCurrent() + + // The transaction was never added + assertThat(analyticsService.getLongRunningTransaction(ResumeAppUntilNewRoomsReceived)).isNull() + + watcher.stop() + } + + private fun TestScope.createAnalyticsRoomListStateWatcher( + appNavigationStateService: FakeAppNavigationStateService = FakeAppNavigationStateService(), + roomListService: FakeRoomListService = FakeRoomListService(), + analyticsService: FakeAnalyticsService = FakeAnalyticsService(), + ) = DefaultAnalyticsRoomListStateWatcher( + appNavigationStateService = appNavigationStateService, + roomListService = roomListService, + analyticsService = analyticsService, + sessionCoroutineScope = backgroundScope, + dispatchers = testCoroutineDispatchers(), + ) +} diff --git a/services/analytics/noop/build.gradle.kts b/services/analytics/noop/build.gradle.kts index cd6d16d029..e1d3de2e65 100644 --- a/services/analytics/noop/build.gradle.kts +++ b/services/analytics/noop/build.gradle.kts @@ -1,9 +1,11 @@ import extension.setupDependencyInjection +import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { @@ -20,4 +22,5 @@ dependencies { implementation(projects.libraries.architecture) implementation(projects.libraries.di) api(projects.services.analytics.api) + testCommonDependencies(libs) } diff --git a/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsService.kt b/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsService.kt index db03ca5553..004a472de3 100644 --- a/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsService.kt +++ b/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,20 +10,23 @@ package io.element.android.services.analytics.noop import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import im.vector.app.features.analytics.itf.VectorAnalyticsEvent import im.vector.app.features.analytics.itf.VectorAnalyticsScreen import im.vector.app.features.analytics.plan.SuperProperties import im.vector.app.features.analytics.plan.UserProperties +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction +import io.element.android.services.analytics.api.AnalyticsSdkSpan import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.api.NoopAnalyticsSdkSpan +import io.element.android.services.analytics.api.NoopAnalyticsTransaction import io.element.android.services.analyticsproviders.api.AnalyticsProvider +import io.element.android.services.analyticsproviders.api.AnalyticsTransaction import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf @SingleIn(AppScope::class) @ContributesBinding(AppScope::class) -@Inject class NoopAnalyticsService : AnalyticsService { override fun getAvailableAnalyticsProviders(): Set = emptySet() override val userConsentFlow: Flow = flowOf(false) @@ -36,4 +40,13 @@ class NoopAnalyticsService : AnalyticsService { override fun updateUserProperties(userProperties: UserProperties) = Unit override fun trackError(throwable: Throwable) = Unit override fun updateSuperProperties(updatedProperties: SuperProperties) = Unit + override fun startTransaction(name: String, operation: String?): AnalyticsTransaction = NoopAnalyticsTransaction + override fun startLongRunningTransaction( + longRunningTransaction: AnalyticsLongRunningTransaction, + parentTransaction: AnalyticsTransaction?, + ): AnalyticsTransaction = NoopAnalyticsTransaction + override fun getLongRunningTransaction(longRunningTransaction: AnalyticsLongRunningTransaction): AnalyticsTransaction? = null + override fun removeLongRunningTransaction(longRunningTransaction: AnalyticsLongRunningTransaction) = NoopAnalyticsTransaction + + override fun enterSdkSpan(name: String?, parentTraceId: String?): AnalyticsSdkSpan = NoopAnalyticsSdkSpan } diff --git a/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopScreenTracker.kt b/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopScreenTracker.kt index fb193e115d..dced133ad1 100644 --- a/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopScreenTracker.kt +++ b/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopScreenTracker.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,12 +11,10 @@ package io.element.android.services.analytics.noop import androidx.compose.runtime.Composable import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import im.vector.app.features.analytics.plan.MobileScreen import io.element.android.services.analytics.api.ScreenTracker @ContributesBinding(AppScope::class) -@Inject class NoopScreenTracker : ScreenTracker { @Composable override fun TrackScreen(screen: MobileScreen.ScreenName) = Unit diff --git a/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/di/NoopAnalyticsModule.kt b/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/di/NoopAnalyticsModule.kt new file mode 100644 index 0000000000..3c4b00c7a0 --- /dev/null +++ b/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/di/NoopAnalyticsModule.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analytics.noop.di + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.BindingContainer +import dev.zacsweers.metro.ContributesTo +import dev.zacsweers.metro.Provides +import io.element.android.libraries.di.identifiers.SentrySdkDsn + +@BindingContainer +@ContributesTo(AppScope::class) +object NoopAnalyticsModule { + @Provides + fun provideSentrySdkDsn(): SentrySdkDsn? = null +} diff --git a/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/watchers/NoopAnalyticsColdStartWatcher.kt b/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/watchers/NoopAnalyticsColdStartWatcher.kt new file mode 100644 index 0000000000..c24c98ebfe --- /dev/null +++ b/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/watchers/NoopAnalyticsColdStartWatcher.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analytics.noop.watchers + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import io.element.android.services.analytics.api.watchers.AnalyticsColdStartWatcher + +@ContributesBinding(AppScope::class) +class NoopAnalyticsColdStartWatcher : AnalyticsColdStartWatcher { + override fun start() {} + override fun whenLoggingIn() {} + override fun onRoomListVisible() {} +} diff --git a/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/watchers/NoopAnalyticsRoomListStateWatcher.kt b/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/watchers/NoopAnalyticsRoomListStateWatcher.kt new file mode 100644 index 0000000000..711947fb2c --- /dev/null +++ b/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/watchers/NoopAnalyticsRoomListStateWatcher.kt @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analytics.noop.watchers + +import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.di.SessionScope +import io.element.android.services.analytics.api.watchers.AnalyticsRoomListStateWatcher + +@ContributesBinding(SessionScope::class) +class NoopAnalyticsRoomListStateWatcher : AnalyticsRoomListStateWatcher { + override fun start() {} + override fun stop() {} +} diff --git a/services/analytics/noop/src/test/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsServiceTest.kt b/services/analytics/noop/src/test/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsServiceTest.kt new file mode 100644 index 0000000000..5e7ae06c0f --- /dev/null +++ b/services/analytics/noop/src/test/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsServiceTest.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analytics.noop + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import im.vector.app.features.analytics.plan.CallStarted +import im.vector.app.features.analytics.plan.MobileScreen +import im.vector.app.features.analytics.plan.SuperProperties +import im.vector.app.features.analytics.plan.UserProperties +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class NoopAnalyticsServiceTest { + @Test + fun `getAvailableAnalyticsProviders returns emptySet`() { + val sut = NoopAnalyticsService() + assertThat(sut.getAvailableAnalyticsProviders()).isEmpty() + } + + @Test + fun `didAskUserConsentFlow emits only true`() = runTest { + val sut = NoopAnalyticsService() + sut.didAskUserConsentFlow.test { + assertThat(awaitItem()).isTrue() + awaitComplete() + } + } + + @Test + fun `analyticsIdFlow emits only empty string`() = runTest { + val sut = NoopAnalyticsService() + sut.analyticsIdFlow.test { + assertThat(awaitItem()).isEmpty() + sut.setAnalyticsId("anId") + awaitComplete() + } + } + + @Test + fun `userConsentFlow emits only false`() = runTest { + val sut = NoopAnalyticsService() + sut.userConsentFlow.test { + assertThat(awaitItem()).isFalse() + awaitComplete() + } + } + + @Test + fun `test no op methods`() = runTest { + val sut = NoopAnalyticsService() + sut.setUserConsent(false) + sut.setUserConsent(true) + sut.setDidAskUserConsent() + sut.setAnalyticsId("anId") + sut.capture(CallStarted(true, 1, true)) + sut.screen(MobileScreen(screenName = MobileScreen.ScreenName.RoomMembers)) + sut.updateUserProperties(UserProperties()) + sut.trackError(Exception("an_error")) + sut.updateSuperProperties(SuperProperties()) + } +} diff --git a/services/analytics/noop/src/test/kotlin/io/element/android/services/analytics/noop/NoopScreenTrackerTest.kt b/services/analytics/noop/src/test/kotlin/io/element/android/services/analytics/noop/NoopScreenTrackerTest.kt new file mode 100644 index 0000000000..e97ae39f5c --- /dev/null +++ b/services/analytics/noop/src/test/kotlin/io/element/android/services/analytics/noop/NoopScreenTrackerTest.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analytics.noop + +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import im.vector.app.features.analytics.plan.MobileScreen +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class NoopScreenTrackerTest { + @Test + fun `TrackScreen is no op`() = runTest { + val sut = NoopScreenTracker() + moleculeFlow(RecompositionMode.Immediate) { + sut.TrackScreen(MobileScreen.ScreenName.RoomMembers) + }.test { + assertThat(awaitItem()).isEqualTo(Unit) + } + } +} diff --git a/services/analytics/test/build.gradle.kts b/services/analytics/test/build.gradle.kts index 01b0544694..61ed796d48 100644 --- a/services/analytics/test/build.gradle.kts +++ b/services/analytics/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt b/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt index f8b250e37b..b9b33744e8 100644 --- a/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt +++ b/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,8 +12,13 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent import im.vector.app.features.analytics.itf.VectorAnalyticsScreen import im.vector.app.features.analytics.plan.SuperProperties import im.vector.app.features.analytics.plan.UserProperties +import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction +import io.element.android.services.analytics.api.AnalyticsSdkSpan import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.api.NoopAnalyticsSdkSpan +import io.element.android.services.analytics.api.NoopAnalyticsTransaction import io.element.android.services.analyticsproviders.api.AnalyticsProvider +import io.element.android.services.analyticsproviders.api.AnalyticsTransaction import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -27,6 +33,7 @@ class FakeAnalyticsService( val screenEvents = mutableListOf() val trackedErrors = mutableListOf() val capturedUserProperties = mutableListOf() + val longRunningTransactions = mutableMapOf() override fun getAvailableAnalyticsProviders(): Set = emptySet() @@ -64,4 +71,23 @@ class FakeAnalyticsService( override fun updateSuperProperties(updatedProperties: SuperProperties) { // No op } + + override fun startTransaction(name: String, operation: String?): AnalyticsTransaction = NoopAnalyticsTransaction + override fun startLongRunningTransaction( + longRunningTransaction: AnalyticsLongRunningTransaction, + parentTransaction: AnalyticsTransaction? + ): AnalyticsTransaction { + longRunningTransactions[longRunningTransaction] = NoopAnalyticsTransaction + return NoopAnalyticsTransaction + } + + override fun getLongRunningTransaction(longRunningTransaction: AnalyticsLongRunningTransaction): AnalyticsTransaction? { + return longRunningTransactions[longRunningTransaction] + } + + override fun removeLongRunningTransaction(longRunningTransaction: AnalyticsLongRunningTransaction): AnalyticsTransaction? { + return longRunningTransactions.remove(longRunningTransaction) + } + + override fun enterSdkSpan(name: String?, parentTraceId: String?): AnalyticsSdkSpan = NoopAnalyticsSdkSpan } diff --git a/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeScreenTracker.kt b/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeScreenTracker.kt index 021d377104..ce22a3dda7 100644 --- a/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeScreenTracker.kt +++ b/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeScreenTracker.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/watchers/FakeAnalyticsColdStartWatcher.kt b/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/watchers/FakeAnalyticsColdStartWatcher.kt new file mode 100644 index 0000000000..ad13c8422d --- /dev/null +++ b/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/watchers/FakeAnalyticsColdStartWatcher.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analytics.test.watchers + +import io.element.android.services.analytics.api.watchers.AnalyticsColdStartWatcher + +class FakeAnalyticsColdStartWatcher : AnalyticsColdStartWatcher { + override fun start() {} + override fun whenLoggingIn() {} + override fun onRoomListVisible() {} +} diff --git a/services/analyticsproviders/api/build.gradle.kts b/services/analyticsproviders/api/build.gradle.kts index 1df9377972..c357c14861 100644 --- a/services/analyticsproviders/api/build.gradle.kts +++ b/services/analyticsproviders/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { 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 46f5f3ef50..f90d924c81 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -19,4 +20,6 @@ interface AnalyticsProvider : AnalyticsTracker, ErrorTracker { fun init() fun stop() + + fun startTransaction(name: String, operation: String? = null): AnalyticsTransaction? } diff --git a/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/AnalyticsTransaction.kt b/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/AnalyticsTransaction.kt new file mode 100644 index 0000000000..b5f81ac67e --- /dev/null +++ b/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/AnalyticsTransaction.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analyticsproviders.api + +interface AnalyticsTransaction { + /** + * Start a child span from this transaction. + */ + fun startChild(operation: String, description: String? = null): AnalyticsTransaction + + /** + * Adds extra data to the transaction. This data is not indexed, it's just listed. + */ + fun putExtraData(key: String, value: String) + + /** + * Similar to [putExtraData], adds extra data that *will be indexed* and can be used for filtering in the analytics portal. + * + * **Do not add numerical values using this function, use [putExtraData] instead.** + */ + fun putIndexableData(key: String, value: String) + + /** + * Whether the transaction has finished. + */ + fun isFinished(): Boolean + + /** + * The optional trace id which can be used for distributed tracing. + */ + fun traceId(): String? + + /** + * Attach a throwable to the transaction, so we can know it failed. + */ + fun attachError(throwable: Throwable) + + /** + * Finish the transaction. This will schedule an upload of the data. + */ + fun finish() +} + +/** + * Records a child span from this transaction. + */ +inline fun AnalyticsTransaction.recordChildTransaction(operation: String, description: String? = null, block: (AnalyticsTransaction) -> T): T { + val child = startChild(operation, description) + try { + val result = block(child) + return result + } finally { + child.finish() + } +} diff --git a/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/AnalyticsUserData.kt b/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/AnalyticsUserData.kt new file mode 100644 index 0000000000..28ae4a5668 --- /dev/null +++ b/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/AnalyticsUserData.kt @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analyticsproviders.api + +object AnalyticsUserData { + const val HOMESERVER = "homeserver" + + const val STATE_STORE_SIZE = "state_store_size" + const val EVENT_CACHE_SIZE = "event_cache_size" + const val CRYPTO_STORE_SIZE = "crypto_store_size" + const val MEDIA_STORE_SIZE = "media_store_size" + + const val FIRST_SYNC_STATE = "first_sync_state" + const val TIMELINE_ITEM_COUNT = "timeline_item_count" +} diff --git a/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/trackers/AnalyticsTracker.kt b/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/trackers/AnalyticsTracker.kt index ceb1f68aa6..5bb63338a8 100644 --- a/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/trackers/AnalyticsTracker.kt +++ b/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/trackers/AnalyticsTracker.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -34,6 +35,18 @@ interface AnalyticsTracker { * Super properties are added to any tracked event automatically. */ fun updateSuperProperties(updatedProperties: SuperProperties) + + /** + * Adds extra data that will be sent with every event. + */ + fun addExtraData(key: String, value: String) {} + + /** + * Similar to [addExtraData], adds data that will be indexed in the analytics portal. + * + * **Do not add numerical values using this, use [addExtraData] instead.** + */ + fun addIndexableData(key: String, value: String) {} } fun AnalyticsTracker.captureInteraction(name: Interaction.Name, type: Interaction.InteractionType? = null) { diff --git a/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/trackers/ErrorTracker.kt b/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/trackers/ErrorTracker.kt index fcb186f693..5277493b64 100644 --- a/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/trackers/ErrorTracker.kt +++ b/services/analyticsproviders/api/src/main/kotlin/io/element/android/services/analyticsproviders/api/trackers/ErrorTracker.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/analyticsproviders/posthog/build.gradle.kts b/services/analyticsproviders/posthog/build.gradle.kts index ffcb2ba590..966522e2f2 100644 --- a/services/analyticsproviders/posthog/build.gradle.kts +++ b/services/analyticsproviders/posthog/build.gradle.kts @@ -4,9 +4,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PostHogFactory.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PostHogFactory.kt index 3cc842817e..95124529d2 100644 --- a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PostHogFactory.kt +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PostHogFactory.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 a8cfcc886b..9185b906c4 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -16,6 +17,7 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsScreen import im.vector.app.features.analytics.plan.SuperProperties import im.vector.app.features.analytics.plan.UserProperties import io.element.android.services.analyticsproviders.api.AnalyticsProvider +import io.element.android.services.analyticsproviders.api.AnalyticsTransaction import io.element.android.services.analyticsproviders.posthog.log.analyticsTag import timber.log.Timber @@ -121,6 +123,8 @@ class PosthogAnalyticsProvider( } return withSuperProperties.takeIf { it.isEmpty().not() } } + + override fun startTransaction(name: String, operation: String?): AnalyticsTransaction? = null } private fun Map.keepOnlyNonNullValues(): Map { diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfig.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfig.kt index 6d14ab6b14..c45a0ee995 100644 --- a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfig.kt +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfigProvider.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfigProvider.kt index 67705d0a45..eb59579d79 100644 --- a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfigProvider.kt +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogEndpointConfigProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/extensions/InteractionExt.kt b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/extensions/InteractionExt.kt index 8446fa9302..8501db6902 100644 --- a/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/extensions/InteractionExt.kt +++ b/services/analyticsproviders/posthog/src/main/kotlin/io/element/android/services/analyticsproviders/posthog/extensions/InteractionExt.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ 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 137dec0595..eb55ba783e 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 @@ -1,7 +1,8 @@ /* - * Copyright 2021-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2021-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/analyticsproviders/posthog/src/test/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProviderTest.kt b/services/analyticsproviders/posthog/src/test/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProviderTest.kt index f52852e09c..ce20a0d37a 100644 --- a/services/analyticsproviders/posthog/src/test/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProviderTest.kt +++ b/services/analyticsproviders/posthog/src/test/kotlin/io/element/android/services/analyticsproviders/posthog/PosthogAnalyticsProviderTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/analyticsproviders/sentry/build.gradle.kts b/services/analyticsproviders/sentry/build.gradle.kts index 149ffb55dd..02dde35ef4 100644 --- a/services/analyticsproviders/sentry/build.gradle.kts +++ b/services/analyticsproviders/sentry/build.gradle.kts @@ -2,11 +2,13 @@ import config.BuildTimeConfig import extension.buildConfigFieldStr import extension.readLocalProperty import extension.setupDependencyInjection +import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { @@ -31,6 +33,16 @@ android { } ?: "" ) + buildConfigFieldStr( + name = "SDK_SENTRY_DSN", + value = if (isEnterpriseBuild) { + BuildTimeConfig.SERVICES_SENTRY_DSN_RUST + } else { + System.getenv("ELEMENT_SDK_SENTRY_DSN") + ?: readLocalProperty("services.analyticsproviders.sdk.sentry.dsn") + } + ?: "" + ) } } @@ -40,5 +52,11 @@ dependencies { implementation(libs.sentry) implementation(projects.libraries.core) implementation(projects.libraries.di) + implementation(projects.libraries.matrix.api) implementation(projects.services.analyticsproviders.api) + implementation(projects.services.appnavstate.api) + + testCommonDependencies(libs, false) + testImplementation(projects.libraries.matrix.test) + testImplementation(projects.services.appnavstate.test) } diff --git a/services/analyticsproviders/sentry/src/main/AndroidManifest.xml b/services/analyticsproviders/sentry/src/main/AndroidManifest.xml index 3fa2813e24..2663552b45 100644 --- a/services/analyticsproviders/sentry/src/main/AndroidManifest.xml +++ b/services/analyticsproviders/sentry/src/main/AndroidManifest.xml @@ -1,7 +1,8 @@ diff --git a/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProvider.kt b/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProvider.kt index c83d756b30..7545563736 100644 --- a/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProvider.kt +++ b/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProvider.kt @@ -1,13 +1,15 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.services.analyticsproviders.sentry import android.content.Context +import androidx.annotation.VisibleForTesting import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesIntoSet import dev.zacsweers.metro.Inject @@ -15,22 +17,34 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent import im.vector.app.features.analytics.itf.VectorAnalyticsScreen import im.vector.app.features.analytics.plan.SuperProperties import im.vector.app.features.analytics.plan.UserProperties +import io.element.android.libraries.core.data.ByteUnit import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.di.annotations.ApplicationContext +import io.element.android.libraries.di.identifiers.SentryDsn +import io.element.android.libraries.matrix.api.analytics.GetDatabaseSizesUseCase import io.element.android.services.analyticsproviders.api.AnalyticsProvider +import io.element.android.services.analyticsproviders.api.AnalyticsTransaction +import io.element.android.services.analyticsproviders.api.AnalyticsUserData import io.element.android.services.analyticsproviders.sentry.log.analyticsTag +import io.element.android.services.appnavstate.api.AppNavigationStateService +import io.element.android.services.appnavstate.api.currentSessionId import io.sentry.Breadcrumb import io.sentry.Sentry import io.sentry.SentryOptions import io.sentry.android.core.SentryAndroid +import io.sentry.protocol.SentryTransaction +import kotlinx.coroutines.runBlocking import timber.log.Timber @ContributesIntoSet(AppScope::class) @Inject class SentryAnalyticsProvider( @ApplicationContext private val context: Context, + private val sentryDsn: SentryDsn?, private val buildMeta: BuildMeta, + private val getDatabaseSizesUseCase: GetDatabaseSizesUseCase, + private val appNavigationStateService: AppNavigationStateService, ) : AnalyticsProvider { override val name = SentryConfig.NAME @@ -38,18 +52,21 @@ class SentryAnalyticsProvider( Timber.tag(analyticsTag.value).d("Initializing Sentry") if (Sentry.isEnabled()) return - val dsn = SentryConfig.DSN.ifBlank { + val dsn = sentryDsn?.value ?: run { Timber.w("No Sentry DSN provided, Sentry will not be initialized") return } SentryAndroid.init(context) { options -> options.dsn = dsn - options.beforeSend = SentryOptions.BeforeSendCallback { event, _ -> event } + options.beforeSendTransaction = SentryOptions.BeforeSendTransactionCallback { transaction, _ -> + prepareTransactionBeforeSend(transaction) + } options.tracesSampleRate = 1.0 options.isEnableUserInteractionTracing = true options.environment = buildMeta.buildType.toSentryEnv() } + Timber.tag(analyticsTag.value).d("Sentry was initialized correctly") } override fun stop() { @@ -83,13 +100,51 @@ class SentryAnalyticsProvider( override fun updateSuperProperties(updatedProperties: SuperProperties) { } + override fun addExtraData(key: String, value: String) { + Sentry.setExtra(key, value) + } + + override fun addIndexableData(key: String, value: String) { + Sentry.setTag(key, value) + } + override fun trackError(throwable: Throwable) { Sentry.captureException(throwable) } + + override fun startTransaction(name: String, operation: String?): AnalyticsTransaction? { + return SentryAnalyticsTransaction(name, operation) + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal fun prepareTransactionBeforeSend(transaction: SentryTransaction): SentryTransaction { + // Ensure we'll never upload any session ids in extras or tags + val invalidExtras = transaction.extras?.filter { (it.value as? String)?.startsWith("@") == true }.orEmpty() + for (invalidExtra in invalidExtras) { + transaction.removeExtra(invalidExtra.key) + } + val invalidTags = transaction.tags?.filter { it.value.startsWith("@") }.orEmpty() + for (invalidTag in invalidTags) { + transaction.removeTag(invalidTag.key) + } + + val sessionId = appNavigationStateService.appNavigationState.value.navigationState.currentSessionId() + if (sessionId != null) { + // This runs in a separate thread, so although using `runBlocking` is not great, at least it shouldn't freeze the app + // Also, the method is fairly quick, so the blocking shouldn't take longer than a few ms + val databaseSizes = runBlocking { getDatabaseSizesUseCase(sessionId) }.getOrNull() + + databaseSizes?.stateStore?.let { transaction.setExtra(AnalyticsUserData.STATE_STORE_SIZE, it.into(ByteUnit.MB)) } + databaseSizes?.eventCacheStore?.let { transaction.setExtra(AnalyticsUserData.EVENT_CACHE_SIZE, it.into(ByteUnit.MB)) } + databaseSizes?.mediaStore?.let { transaction.setExtra(AnalyticsUserData.MEDIA_STORE_SIZE, it.into(ByteUnit.MB)) } + databaseSizes?.cryptoStore?.let { transaction.setExtra(AnalyticsUserData.CRYPTO_STORE_SIZE, it.into(ByteUnit.MB)) } + } + return transaction + } } private fun BuildType.toSentryEnv() = when (this) { BuildType.RELEASE -> SentryConfig.ENV_RELEASE - BuildType.NIGHTLY, + BuildType.NIGHTLY -> SentryConfig.ENV_NIGHTLY BuildType.DEBUG -> SentryConfig.ENV_DEBUG } diff --git a/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsTransaction.kt b/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsTransaction.kt new file mode 100644 index 0000000000..84314ee35f --- /dev/null +++ b/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsTransaction.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analyticsproviders.sentry + +import io.element.android.services.analyticsproviders.api.AnalyticsTransaction +import io.sentry.ISpan +import io.sentry.ITransaction +import io.sentry.Sentry +import timber.log.Timber + +class SentryAnalyticsTransaction private constructor(span: ISpan) : AnalyticsTransaction { + constructor(name: String, operation: String?) : this(Sentry.startTransaction(name, operation.orEmpty())) + private val inner = span + + override fun startChild(operation: String, description: String?): AnalyticsTransaction = SentryAnalyticsTransaction( + inner.startChild(operation, description) + ) + + override fun putIndexableData(key: String, value: String) = inner.setTag(key, value) + override fun putExtraData(key: String, value: String) = inner.setData(key, value) + override fun traceId(): String? = inner.toSentryTrace().value + override fun isFinished(): Boolean = inner.isFinished + override fun attachError(throwable: Throwable) { + inner.throwable = throwable + } + override fun finish() { + val name = if (inner is ITransaction) inner.name else inner.operation + Timber.d("Finishing transaction: $name") + inner.finish() + } +} 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 index 39ade3f9ff..993823a877 100644 --- 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,6 +11,8 @@ package io.element.android.services.analyticsproviders.sentry object SentryConfig { const val NAME = "Sentry" const val DSN = BuildConfig.SENTRY_DSN + const val SDK_DSN = BuildConfig.SDK_SENTRY_DSN const val ENV_DEBUG = "DEBUG" + const val ENV_NIGHTLY = "NIGHTLY" const val ENV_RELEASE = "RELEASE" } diff --git a/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/di/SentryModule.kt b/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/di/SentryModule.kt new file mode 100644 index 0000000000..74b7d56683 --- /dev/null +++ b/services/analyticsproviders/sentry/src/main/kotlin/io/element/android/services/analyticsproviders/sentry/di/SentryModule.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.analyticsproviders.sentry.di + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.BindingContainer +import dev.zacsweers.metro.ContributesTo +import dev.zacsweers.metro.Provides +import io.element.android.libraries.di.identifiers.SentryDsn +import io.element.android.libraries.di.identifiers.SentrySdkDsn +import io.element.android.services.analyticsproviders.sentry.SentryConfig + +@BindingContainer +@ContributesTo(AppScope::class) +object SentryModule { + @Provides + fun provideSentryDsn(): SentryDsn? = SentryConfig.DSN.takeIf { it.isNotBlank() }?.let(::SentryDsn) + + @Provides + fun provideSentrySdkDsn(): SentrySdkDsn? = SentryConfig.SDK_DSN.takeIf { it.isNotBlank() }?.let(::SentrySdkDsn) +} 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 index 0bdef84c96..698c29b9f4 100644 --- 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 @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/analyticsproviders/sentry/src/test/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProviderTest.kt b/services/analyticsproviders/sentry/src/test/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProviderTest.kt new file mode 100644 index 0000000000..24b938d9a5 --- /dev/null +++ b/services/analyticsproviders/sentry/src/test/kotlin/io/element/android/services/analyticsproviders/sentry/SentryAnalyticsProviderTest.kt @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +@file:Suppress("UnstableApiUsage") + +package io.element.android.services.analyticsproviders.sentry + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.google.common.truth.Truth.assertThat +import im.vector.app.features.analytics.itf.VectorAnalyticsEvent +import im.vector.app.features.analytics.itf.VectorAnalyticsScreen +import im.vector.app.features.analytics.plan.SuperProperties +import im.vector.app.features.analytics.plan.UserProperties +import io.element.android.libraries.core.data.megaBytes +import io.element.android.libraries.core.meta.BuildMeta +import io.element.android.libraries.di.identifiers.SentryDsn +import io.element.android.libraries.matrix.api.analytics.GetDatabaseSizesUseCase +import io.element.android.libraries.matrix.api.analytics.SdkStoreSizes +import io.element.android.libraries.matrix.test.A_SESSION_ID +import io.element.android.libraries.matrix.test.core.aBuildMeta +import io.element.android.services.analyticsproviders.api.AnalyticsUserData +import io.element.android.services.appnavstate.api.AppNavigationState +import io.element.android.services.appnavstate.api.NavigationState +import io.element.android.services.appnavstate.test.FakeAppNavigationStateService +import io.sentry.Sentry +import io.sentry.SentryTracer +import io.sentry.protocol.SentryId +import io.sentry.protocol.SentryTransaction +import kotlinx.coroutines.flow.MutableStateFlow +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SentryAnalyticsProviderTest { + @Test + fun `init enables Sentry if DSN is present`() { + createSentryAnalyticsProvider().run { + init() + } + assertThat(Sentry.isEnabled()).isTrue() + } + + @Test + fun `init does nothing if DSN is not present`() { + createSentryAnalyticsProvider(sentryDsn = null).run { + init() + } + assertThat(Sentry.isEnabled()).isTrue() + } + + @Test + fun `stop disables Sentry`() { + createSentryAnalyticsProvider().run { + init() + stop() + } + assertThat(Sentry.isEnabled()).isFalse() + } + + @Test + fun `capture adds a breadcrumb`() { + createSentryAnalyticsProvider().run { + init() + capture(object : VectorAnalyticsEvent { + override fun getName(): String = "Test" + override fun getProperties(): Map? = mapOf("foo" to "bar") + }) + } + assertThat(Sentry.getCurrentScopes().scope.breadcrumbs.isNotEmpty()).isTrue() + } + + @Test + fun `screen adds a breadcrumb`() { + createSentryAnalyticsProvider().run { + init() + screen(object : VectorAnalyticsScreen { + override fun getName(): String = "Test" + override fun getProperties(): Map? = mapOf("foo" to "bar") + }) + } + assertThat(Sentry.getCurrentScopes().scope.breadcrumbs.isNotEmpty()).isTrue() + } + + @Test + fun `updateUserProperties and updateSuperProperties do nothing`() { + createSentryAnalyticsProvider().run { + init() + updateUserProperties(UserProperties()) + updateSuperProperties(SuperProperties()) + } + val scope = Sentry.getCurrentScopes().scope + assertThat(scope.extras.isEmpty()).isTrue() + assertThat(scope.tags.isEmpty()).isTrue() + assertThat(scope.contexts.isEmpty()).isTrue() + } + + @Test + fun `addExtraData adds a global extra`() { + createSentryAnalyticsProvider().run { + init() + addExtraData("foo", "bar") + } + val scope = Sentry.getCurrentScopes().scope + assertThat(scope.extras.get("foo")).isEqualTo("bar") + } + + @Test + fun `addIndexableData adds a global tag`() { + createSentryAnalyticsProvider().run { + init() + addIndexableData("foo", "bar") + } + val scope = Sentry.getCurrentScopes().scope + assertThat(scope.tags.get("foo")).isEqualTo("bar") + } + + @Test + fun `trackError adds a throwable to the global scope`() { + var initialLastId: SentryId? = null + createSentryAnalyticsProvider().run { + init() + initialLastId = Sentry.getLastEventId() + trackError(IllegalStateException("foo")) + } + assertThat(Sentry.getLastEventId()).isNotEqualTo(initialLastId) + } + + @Test + fun `startTransaction starts a SentryAnalyticsTransaction`() { + val transaction = createSentryAnalyticsProvider().run { + init() + startTransaction("foo") + } + assertThat(transaction).isNotNull() + assertThat(transaction).isInstanceOf(SentryAnalyticsTransaction::class.java) + } + + @Test + fun `prepareTransactionBeforeSend removes unwanted data and adds DB size extras`() { + createSentryAnalyticsProvider( + getDatabaseSizesUseCase = GetDatabaseSizesUseCase { + Result.success( + SdkStoreSizes(stateStore = 10.megaBytes, eventCacheStore = 11.megaBytes, mediaStore = 12.megaBytes, cryptoStore = 13.megaBytes) + ) + }, + appNavigationStateService = FakeAppNavigationStateService( + MutableStateFlow(AppNavigationState(navigationState = NavigationState.Session("owner", A_SESSION_ID), isInForeground = true)) + ) + ).run { + init() + + val transaction = SentryTransaction(Sentry.startTransaction("foo", "bar") as SentryTracer) + // Add a user id value + transaction.setExtra("user", "@some:user") + transaction.setTag("user", "@some:user") + + val result = prepareTransactionBeforeSend(transaction) + + // The user id value should have been removed + assertThat(result.getExtra("user")).isNull() + assertThat(result.getTag("user")).isNull() + + // The DB sizes should be included + assertThat(result.getExtra(AnalyticsUserData.STATE_STORE_SIZE)).isEqualTo(10) + assertThat(result.getExtra(AnalyticsUserData.EVENT_CACHE_SIZE)).isEqualTo(11) + assertThat(result.getExtra(AnalyticsUserData.MEDIA_STORE_SIZE)).isEqualTo(12) + assertThat(result.getExtra(AnalyticsUserData.CRYPTO_STORE_SIZE)).isEqualTo(13) + } + } + + @Test + fun `prepareTransactionBeforeSend doesn't add DB info if no session id is provided`() { + createSentryAnalyticsProvider( + getDatabaseSizesUseCase = GetDatabaseSizesUseCase { + Result.success( + SdkStoreSizes(stateStore = 10.megaBytes, eventCacheStore = 11.megaBytes, mediaStore = 12.megaBytes, cryptoStore = 13.megaBytes) + ) + }, + appNavigationStateService = FakeAppNavigationStateService( + MutableStateFlow(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true)) + ) + ).run { + init() + + val transaction = SentryTransaction(Sentry.startTransaction("foo", "bar") as SentryTracer) + val result = prepareTransactionBeforeSend(transaction) + // The DB sizes are missing since there was no session id to query them + assertThat(result.extras).isNull() + } + } + + @Test + fun `prepareTransactionBeforeSend doesn't add DB info if no store sizes are available`() { + createSentryAnalyticsProvider( + getDatabaseSizesUseCase = GetDatabaseSizesUseCase { + Result.success( + SdkStoreSizes(stateStore = null, eventCacheStore = null, mediaStore = null, cryptoStore = null) + ) + }, + appNavigationStateService = FakeAppNavigationStateService( + MutableStateFlow(AppNavigationState(navigationState = NavigationState.Session("owner", A_SESSION_ID), isInForeground = true)) + ) + ).run { + init() + + val transaction = SentryTransaction(Sentry.startTransaction("foo", "bar") as SentryTracer) + val result = prepareTransactionBeforeSend(transaction) + + // The DB sizes are missing since there was no session id to query them + assertThat(result.extras).isNull() + } + } + + private fun createSentryAnalyticsProvider( + sentryDsn: SentryDsn? = SentryDsn("https://1234@sentry.com/a"), + buildMeta: BuildMeta = aBuildMeta(), + getDatabaseSizesUseCase: GetDatabaseSizesUseCase = GetDatabaseSizesUseCase { Result.success(SdkStoreSizes(null, null, null, null)) }, + appNavigationStateService: FakeAppNavigationStateService = FakeAppNavigationStateService( + MutableStateFlow(AppNavigationState(navigationState = NavigationState.Session("owner", A_SESSION_ID), isInForeground = true)) + ) + ) = SentryAnalyticsProvider( + context = InstrumentationRegistry.getInstrumentation().targetContext, + sentryDsn = sentryDsn, + buildMeta = buildMeta, + getDatabaseSizesUseCase = getDatabaseSizesUseCase, + appNavigationStateService = appNavigationStateService, + ) +} diff --git a/services/analyticsproviders/test/build.gradle.kts b/services/analyticsproviders/test/build.gradle.kts index 762222b8dc..455ee57f5e 100644 --- a/services/analyticsproviders/test/build.gradle.kts +++ b/services/analyticsproviders/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/services/analyticsproviders/test/src/main/kotlin/io/element/android/services/analyticsproviders/test/FakeAnalyticsProvider.kt b/services/analyticsproviders/test/src/main/kotlin/io/element/android/services/analyticsproviders/test/FakeAnalyticsProvider.kt index f38d0d25fe..9b0374bd9b 100644 --- a/services/analyticsproviders/test/src/main/kotlin/io/element/android/services/analyticsproviders/test/FakeAnalyticsProvider.kt +++ b/services/analyticsproviders/test/src/main/kotlin/io/element/android/services/analyticsproviders/test/FakeAnalyticsProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,6 +13,7 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsScreen import im.vector.app.features.analytics.plan.SuperProperties import im.vector.app.features.analytics.plan.UserProperties import io.element.android.services.analyticsproviders.api.AnalyticsProvider +import io.element.android.services.analyticsproviders.api.AnalyticsTransaction import io.element.android.tests.testutils.lambda.lambdaError class FakeAnalyticsProvider( @@ -31,4 +33,5 @@ class FakeAnalyticsProvider( override fun updateUserProperties(userProperties: UserProperties) = updateUserPropertiesLambda(userProperties) override fun trackError(throwable: Throwable) = trackErrorLambda(throwable) override fun updateSuperProperties(updatedProperties: SuperProperties) = updateSuperPropertiesLambda(updatedProperties) + override fun startTransaction(name: String, operation: String?): AnalyticsTransaction? = null } diff --git a/services/apperror/api/build.gradle.kts b/services/apperror/api/build.gradle.kts index 0996152de6..c0893bd7e8 100644 --- a/services/apperror/api/build.gradle.kts +++ b/services/apperror/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorState.kt b/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorState.kt index 9da3b6547b..366c49b755 100644 --- a/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorState.kt +++ b/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorStateProvider.kt b/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorStateProvider.kt index ed7a28abcc..e0b8fec507 100644 --- a/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorStateProvider.kt +++ b/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorStateProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorStateService.kt b/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorStateService.kt index 8070673804..7c592e2102 100644 --- a/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorStateService.kt +++ b/services/apperror/api/src/main/kotlin/io/element/android/services/apperror/api/AppErrorStateService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/apperror/impl/build.gradle.kts b/services/apperror/impl/build.gradle.kts index 673468e99a..b72b54c198 100644 --- a/services/apperror/impl/build.gradle.kts +++ b/services/apperror/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/apperror/impl/src/main/kotlin/io/element/android/services/apperror/impl/AppErrorView.kt b/services/apperror/impl/src/main/kotlin/io/element/android/services/apperror/impl/AppErrorView.kt index ff10bd1970..3794234c79 100644 --- a/services/apperror/impl/src/main/kotlin/io/element/android/services/apperror/impl/AppErrorView.kt +++ b/services/apperror/impl/src/main/kotlin/io/element/android/services/apperror/impl/AppErrorView.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/apperror/impl/src/main/kotlin/io/element/android/services/apperror/impl/DefaultAppErrorStateService.kt b/services/apperror/impl/src/main/kotlin/io/element/android/services/apperror/impl/DefaultAppErrorStateService.kt index 7d4dc8d64e..1b11a42bfb 100644 --- a/services/apperror/impl/src/main/kotlin/io/element/android/services/apperror/impl/DefaultAppErrorStateService.kt +++ b/services/apperror/impl/src/main/kotlin/io/element/android/services/apperror/impl/DefaultAppErrorStateService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.services.apperror.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.services.apperror.api.AppErrorState import io.element.android.services.apperror.api.AppErrorStateService @@ -19,7 +19,6 @@ import kotlinx.coroutines.flow.StateFlow @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) -@Inject class DefaultAppErrorStateService( private val stringProvider: StringProvider, ) : AppErrorStateService { diff --git a/services/apperror/impl/src/test/kotlin/io/element/android/services/apperror/impl/DefaultAppErrorStateServiceTest.kt b/services/apperror/impl/src/test/kotlin/io/element/android/services/apperror/impl/DefaultAppErrorStateServiceTest.kt index 6eb26c9c50..92bf6f6359 100644 --- a/services/apperror/impl/src/test/kotlin/io/element/android/services/apperror/impl/DefaultAppErrorStateServiceTest.kt +++ b/services/apperror/impl/src/test/kotlin/io/element/android/services/apperror/impl/DefaultAppErrorStateServiceTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/apperror/test/build.gradle.kts b/services/apperror/test/build.gradle.kts index c8ac902784..706fdc0a26 100644 --- a/services/apperror/test/build.gradle.kts +++ b/services/apperror/test/build.gradle.kts @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/apperror/test/src/main/kotlin/io/element/android/services/apperror/test/FakeAppErrorStateService.kt b/services/apperror/test/src/main/kotlin/io/element/android/services/apperror/test/FakeAppErrorStateService.kt index 0783d08e02..1c78491567 100644 --- a/services/apperror/test/src/main/kotlin/io/element/android/services/apperror/test/FakeAppErrorStateService.kt +++ b/services/apperror/test/src/main/kotlin/io/element/android/services/apperror/test/FakeAppErrorStateService.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/appnavstate/api/build.gradle.kts b/services/appnavstate/api/build.gradle.kts index f25cf59764..c9849de20f 100644 --- a/services/appnavstate/api/build.gradle.kts +++ b/services/appnavstate/api/build.gradle.kts @@ -1,9 +1,8 @@ -import extension.setupDependencyInjection - /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,8 +14,6 @@ android { namespace = "io.element.android.services.appnavstate.api" } -setupDependencyInjection() - dependencies { implementation(libs.coroutines.core) implementation(libs.androidx.lifecycle.runtime) diff --git a/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/ActiveRoomsHolder.kt b/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/ActiveRoomsHolder.kt index 07050d7bea..c1e84de079 100644 --- a/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/ActiveRoomsHolder.kt +++ b/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/ActiveRoomsHolder.kt @@ -1,69 +1,43 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.services.appnavstate.api -import dev.zacsweers.metro.AppScope -import dev.zacsweers.metro.Inject -import dev.zacsweers.metro.SingleIn 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.room.JoinedRoom -import java.util.concurrent.ConcurrentHashMap /** * Holds the active rooms for a given session so they can be reused instead of instantiating new ones. */ -@SingleIn(AppScope::class) -@Inject -class ActiveRoomsHolder { - private val rooms = ConcurrentHashMap>() - +interface ActiveRoomsHolder { /** * Adds a new held room for the given sessionId. */ - fun addRoom(room: JoinedRoom) { - val roomsForSessionId = rooms.getOrPut(key = room.sessionId, defaultValue = { mutableSetOf() }) - if (roomsForSessionId.none { it.roomId == room.roomId }) { - // We don't want to add the same room multiple times - roomsForSessionId.add(room) - } - } + fun addRoom(room: JoinedRoom) /** * Returns the last room added for the given [sessionId] or null if no room was added. */ - fun getActiveRoom(sessionId: SessionId): JoinedRoom? { - return rooms[sessionId]?.lastOrNull() - } + fun getActiveRoom(sessionId: SessionId): JoinedRoom? /** * Returns an active room associated to the given [sessionId], with the given [roomId], or null if none match. */ - fun getActiveRoomMatching(sessionId: SessionId, roomId: RoomId): JoinedRoom? { - return rooms[sessionId]?.find { it.roomId == roomId } - } + fun getActiveRoomMatching(sessionId: SessionId, roomId: RoomId): JoinedRoom? /** * Removes any room matching the provided [sessionId] and [roomId]. */ - fun removeRoom(sessionId: SessionId, roomId: RoomId) { - val roomsForSessionId = rooms[sessionId] ?: return - roomsForSessionId.removeIf { it.roomId == roomId } - } + fun removeRoom(sessionId: SessionId, roomId: RoomId) /** * Clears all the rooms for the given sessionId. */ - fun clear(sessionId: SessionId) { - val activeRooms = rooms.remove(sessionId) ?: return - for (room in activeRooms) { - // Destroy the room to reset the live timelines - room.destroy() - } - } + fun clear(sessionId: SessionId) } diff --git a/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/AppForegroundStateService.kt b/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/AppForegroundStateService.kt index 6b96476376..d6effec06f 100644 --- a/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/AppForegroundStateService.kt +++ b/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/AppForegroundStateService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/AppNavigationState.kt b/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/AppNavigationState.kt index d50d44a876..54e18d8b9f 100644 --- a/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/AppNavigationState.kt +++ b/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/AppNavigationState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/AppNavigationStateService.kt b/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/AppNavigationStateService.kt index 3db05d50e3..8387afca59 100644 --- a/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/AppNavigationStateService.kt +++ b/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/AppNavigationStateService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/IntentNavigationExtras.kt b/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/IntentNavigationExtras.kt new file mode 100644 index 0000000000..4851503984 --- /dev/null +++ b/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/IntentNavigationExtras.kt @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.appnavstate.api + +const val ROOM_OPENED_FROM_NOTIFICATION = "opened_from_notification" diff --git a/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/NavigationState.kt b/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/NavigationState.kt index e6cf041692..0ff606e051 100644 --- a/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/NavigationState.kt +++ b/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/NavigationState.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/NavigationStateExtension.kt b/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/NavigationStateExtension.kt index 45fb1803d7..bf336a38bf 100644 --- a/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/NavigationStateExtension.kt +++ b/services/appnavstate/api/src/main/kotlin/io/element/android/services/appnavstate/api/NavigationStateExtension.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/appnavstate/impl/build.gradle.kts b/services/appnavstate/impl/build.gradle.kts index ba58e96f69..4b533a4ea1 100644 --- a/services/appnavstate/impl/build.gradle.kts +++ b/services/appnavstate/impl/build.gradle.kts @@ -2,9 +2,10 @@ import extension.setupDependencyInjection import extension.testCommonDependencies /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2022-2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultActiveRoomsHolder.kt b/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultActiveRoomsHolder.kt new file mode 100644 index 0000000000..871ec32d8a --- /dev/null +++ b/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultActiveRoomsHolder.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.services.appnavstate.impl + +import dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.ContributesBinding +import dev.zacsweers.metro.SingleIn +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.room.JoinedRoom +import io.element.android.services.appnavstate.api.ActiveRoomsHolder +import java.util.concurrent.ConcurrentHashMap + +@SingleIn(AppScope::class) +@ContributesBinding(AppScope::class) +class DefaultActiveRoomsHolder : ActiveRoomsHolder { + private val rooms = ConcurrentHashMap>() + + override fun addRoom(room: JoinedRoom) { + val roomsForSessionId = rooms.getOrPut(key = room.sessionId, defaultValue = { mutableSetOf() }) + if (roomsForSessionId.none { it.roomId == room.roomId }) { + // We don't want to add the same room multiple times + roomsForSessionId.add(room) + } + } + + override fun getActiveRoom(sessionId: SessionId): JoinedRoom? { + return rooms[sessionId]?.lastOrNull() + } + + override fun getActiveRoomMatching(sessionId: SessionId, roomId: RoomId): JoinedRoom? { + return rooms[sessionId]?.find { it.roomId == roomId } + } + + override fun removeRoom(sessionId: SessionId, roomId: RoomId) { + val roomsForSessionId = rooms[sessionId] ?: return + roomsForSessionId.removeIf { it.roomId == roomId } + } + + override fun clear(sessionId: SessionId) { + val activeRooms = rooms.remove(sessionId) ?: return + for (room in activeRooms) { + // Destroy the room to reset the live timelines + room.destroy() + } + } +} diff --git a/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppForegroundStateService.kt b/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppForegroundStateService.kt index 9bd5004f13..c9fa31caca 100644 --- a/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppForegroundStateService.kt +++ b/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppForegroundStateService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateService.kt b/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateService.kt index 013ce5459f..e820bf9262 100644 --- a/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateService.kt +++ b/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,6 @@ package io.element.android.services.appnavstate.impl import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import dev.zacsweers.metro.SingleIn import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.di.annotations.AppCoroutineScope @@ -35,7 +35,6 @@ private val loggerTag = LoggerTag("Navigation") */ @ContributesBinding(AppScope::class) @SingleIn(AppScope::class) -@Inject class DefaultAppNavigationStateService( private val appForegroundStateService: AppForegroundStateService, @AppCoroutineScope diff --git a/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/di/AppNavStateModule.kt b/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/di/AppNavStateModule.kt index 326d4de059..4cf9c25b7b 100644 --- a/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/di/AppNavStateModule.kt +++ b/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/di/AppNavStateModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/initializer/AppForegroundStateServiceInitializer.kt b/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/initializer/AppForegroundStateServiceInitializer.kt index 0e5487197e..b2e906e707 100644 --- a/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/initializer/AppForegroundStateServiceInitializer.kt +++ b/services/appnavstate/impl/src/main/kotlin/io/element/android/services/appnavstate/impl/initializer/AppForegroundStateServiceInitializer.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultNavigationStateServiceTest.kt b/services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultNavigationStateServiceTest.kt index c6ddff060e..56f9c5e986 100644 --- a/services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultNavigationStateServiceTest.kt +++ b/services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultNavigationStateServiceTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/appnavstate/test/build.gradle.kts b/services/appnavstate/test/build.gradle.kts index d2f708cb87..af4995eb22 100644 --- a/services/appnavstate/test/build.gradle.kts +++ b/services/appnavstate/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/AppNavStateFixture.kt b/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/AppNavStateFixture.kt index e411e277b5..e9c2f6d2fa 100644 --- a/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/AppNavStateFixture.kt +++ b/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/AppNavStateFixture.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/FakeAppForegroundStateService.kt b/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/FakeAppForegroundStateService.kt index b8bb7b8a20..a61733cc22 100644 --- a/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/FakeAppForegroundStateService.kt +++ b/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/FakeAppForegroundStateService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/FakeAppNavigationStateService.kt b/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/FakeAppNavigationStateService.kt index aec2e9eb00..d01bc4f34b 100644 --- a/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/FakeAppNavigationStateService.kt +++ b/services/appnavstate/test/src/main/kotlin/io/element/android/services/appnavstate/test/FakeAppNavigationStateService.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/toolbox/api/build.gradle.kts b/services/toolbox/api/build.gradle.kts index 2a45c980d5..86829c0132 100644 --- a/services/toolbox/api/build.gradle.kts +++ b/services/toolbox/api/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/intent/ExternalIntentLauncher.kt b/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/intent/ExternalIntentLauncher.kt index 5aeebcaaa9..ba34a6a716 100644 --- a/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/intent/ExternalIntentLauncher.kt +++ b/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/intent/ExternalIntentLauncher.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/sdk/BuildVersionSdkIntProvider.kt b/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/sdk/BuildVersionSdkIntProvider.kt index bbbbec7fd8..26bbe23548 100644 --- a/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/sdk/BuildVersionSdkIntProvider.kt +++ b/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/sdk/BuildVersionSdkIntProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/strings/StringProvider.kt b/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/strings/StringProvider.kt index 6c3ea5ce12..9a8ff23eb5 100644 --- a/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/strings/StringProvider.kt +++ b/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/strings/StringProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/systemclock/SystemClock.kt b/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/systemclock/SystemClock.kt index 8cab823e9f..5436b44354 100644 --- a/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/systemclock/SystemClock.kt +++ b/services/toolbox/api/src/main/kotlin/io/element/android/services/toolbox/api/systemclock/SystemClock.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/toolbox/impl/build.gradle.kts b/services/toolbox/impl/build.gradle.kts index f436771591..d69e421839 100644 --- a/services/toolbox/impl/build.gradle.kts +++ b/services/toolbox/impl/build.gradle.kts @@ -1,9 +1,10 @@ import extension.setupDependencyInjection /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2023, 2024 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/intent/DefaultExternalIntentLauncher.kt b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/intent/DefaultExternalIntentLauncher.kt index 431bb49e57..d5cdad904b 100644 --- a/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/intent/DefaultExternalIntentLauncher.kt +++ b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/intent/DefaultExternalIntentLauncher.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -11,12 +12,10 @@ import android.content.Context import android.content.Intent import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.services.toolbox.api.intent.ExternalIntentLauncher @ContributesBinding(AppScope::class) -@Inject class DefaultExternalIntentLauncher( @ApplicationContext private val context: Context, ) : ExternalIntentLauncher { diff --git a/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/sdk/DefaultBuildVersionSdkIntProvider.kt b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/sdk/DefaultBuildVersionSdkIntProvider.kt index 75904f4dea..df08f672bd 100644 --- a/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/sdk/DefaultBuildVersionSdkIntProvider.kt +++ b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/sdk/DefaultBuildVersionSdkIntProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,11 +11,9 @@ package io.element.android.services.toolbox.impl.sdk import android.os.Build import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider @ContributesBinding(AppScope::class) -@Inject class DefaultBuildVersionSdkIntProvider : BuildVersionSdkIntProvider { override fun get() = Build.VERSION.SDK_INT diff --git a/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/AndroidStringProvider.kt b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/AndroidStringProvider.kt index 1e07f77f20..b095348c41 100644 --- a/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/AndroidStringProvider.kt +++ b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/strings/AndroidStringProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,11 +13,9 @@ import androidx.annotation.PluralsRes import androidx.annotation.StringRes import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.services.toolbox.api.strings.StringProvider @ContributesBinding(AppScope::class) -@Inject class AndroidStringProvider(private val resources: Resources) : StringProvider { override fun getString(@StringRes resId: Int): String { return resources.getString(resId) diff --git a/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/systemclock/DefaultSystemClock.kt b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/systemclock/DefaultSystemClock.kt index 74c5094868..169518f80c 100644 --- a/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/systemclock/DefaultSystemClock.kt +++ b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/systemclock/DefaultSystemClock.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,11 +10,9 @@ package io.element.android.services.toolbox.impl.systemclock import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.ContributesBinding -import dev.zacsweers.metro.Inject import io.element.android.services.toolbox.api.systemclock.SystemClock @ContributesBinding(AppScope::class) -@Inject class DefaultSystemClock : SystemClock { /** * Provides a UTC epoch in milliseconds diff --git a/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/systemclock/TimeModule.kt b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/systemclock/TimeModule.kt index c1aa2c8be4..766a63ebfd 100644 --- a/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/systemclock/TimeModule.kt +++ b/services/toolbox/impl/src/main/kotlin/io/element/android/services/toolbox/impl/systemclock/TimeModule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/toolbox/test/build.gradle.kts b/services/toolbox/test/build.gradle.kts index 8d2f2b8a2d..6976646bfc 100644 --- a/services/toolbox/test/build.gradle.kts +++ b/services/toolbox/test/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/intent/FakeExternalIntentLauncher.kt b/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/intent/FakeExternalIntentLauncher.kt index a8e9589b6d..21f618ebc5 100644 --- a/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/intent/FakeExternalIntentLauncher.kt +++ b/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/intent/FakeExternalIntentLauncher.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/sdk/FakeBuildVersionSdkIntProvider.kt b/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/sdk/FakeBuildVersionSdkIntProvider.kt index a09766b6f7..75a8b424e8 100644 --- a/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/sdk/FakeBuildVersionSdkIntProvider.kt +++ b/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/sdk/FakeBuildVersionSdkIntProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt b/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt index e6b33d3785..c2867dca3b 100644 --- a/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt +++ b/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/systemclock/FakeSystemClock.kt b/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/systemclock/FakeSystemClock.kt index 444502aea8..648e19ad22 100644 --- a/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/systemclock/FakeSystemClock.kt +++ b/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/systemclock/FakeSystemClock.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/settings.gradle.kts b/settings.gradle.kts index 9245034508..8c7b608465 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,15 +1,14 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ -import java.net.URI - pluginManagement { repositories { - includeBuild("plugins") + includeBuild("plugins") gradlePluginPortal() google() mavenCentral() @@ -18,14 +17,17 @@ pluginManagement { dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { - google() - mavenCentral() maven { - url = URI("https://www.jitpack.io") + url = uri("https://www.jitpack.io") content { includeModule("com.github.matrix-org", "matrix-analytics-events") } } + google() + mavenCentral() + maven { + url = uri("https://repo1.maven.org/maven2/") + } flatDir { dirs("libraries/matrix/libs") } diff --git a/tests/detekt-rules/src/main/kotlin/io/element/android/detektrules/ByPreferencesDataStoreRule.kt b/tests/detekt-rules/src/main/kotlin/io/element/android/detektrules/ByPreferencesDataStoreRule.kt index cb00dc18b1..eb33f89f1d 100644 --- a/tests/detekt-rules/src/main/kotlin/io/element/android/detektrules/ByPreferencesDataStoreRule.kt +++ b/tests/detekt-rules/src/main/kotlin/io/element/android/detektrules/ByPreferencesDataStoreRule.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/detekt-rules/src/main/kotlin/io/element/android/detektrules/ElementRuleSetProvider.kt b/tests/detekt-rules/src/main/kotlin/io/element/android/detektrules/ElementRuleSetProvider.kt index 9a7ba55dc6..1a0955c6c7 100644 --- a/tests/detekt-rules/src/main/kotlin/io/element/android/detektrules/ElementRuleSetProvider.kt +++ b/tests/detekt-rules/src/main/kotlin/io/element/android/detektrules/ElementRuleSetProvider.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/detekt-rules/src/main/kotlin/io/element/android/detektrules/RunCatchingRule.kt b/tests/detekt-rules/src/main/kotlin/io/element/android/detektrules/RunCatchingRule.kt index 094de51be3..e640318890 100644 --- a/tests/detekt-rules/src/main/kotlin/io/element/android/detektrules/RunCatchingRule.kt +++ b/tests/detekt-rules/src/main/kotlin/io/element/android/detektrules/RunCatchingRule.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/detekt-rules/src/test/kotlin/io/element/android/detektrules/RunCatchingRuleTest.kt b/tests/detekt-rules/src/test/kotlin/io/element/android/detektrules/RunCatchingRuleTest.kt index c11d4d8868..23fcf15f18 100644 --- a/tests/detekt-rules/src/test/kotlin/io/element/android/detektrules/RunCatchingRuleTest.kt +++ b/tests/detekt-rules/src/test/kotlin/io/element/android/detektrules/RunCatchingRuleTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/konsist/build.gradle.kts b/tests/konsist/build.gradle.kts index d920416193..5558b1a564 100644 --- a/tests/konsist/build.gradle.kts +++ b/tests/konsist/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/konsist/src/main/kotlin/io/element/android/tests/konsist/failures/FailingComposableWithNonImmutableSealedInterface.kt b/tests/konsist/src/main/kotlin/io/element/android/tests/konsist/failures/FailingComposableWithNonImmutableSealedInterface.kt index 43d682d55e..ee5266da4f 100644 --- a/tests/konsist/src/main/kotlin/io/element/android/tests/konsist/failures/FailingComposableWithNonImmutableSealedInterface.kt +++ b/tests/konsist/src/main/kotlin/io/element/android/tests/konsist/failures/FailingComposableWithNonImmutableSealedInterface.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/konsist/src/main/kotlin/io/element/android/tests/konsist/failures/FakeWrongClassName.kt b/tests/konsist/src/main/kotlin/io/element/android/tests/konsist/failures/FakeWrongClassName.kt index 599343e56f..ea78c93356 100644 --- a/tests/konsist/src/main/kotlin/io/element/android/tests/konsist/failures/FakeWrongClassName.kt +++ b/tests/konsist/src/main/kotlin/io/element/android/tests/konsist/failures/FakeWrongClassName.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistArchitectureTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistArchitectureTest.kt index a01c37ea6c..fe303f360c 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistArchitectureTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistArchitectureTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistCallbackTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistCallbackTest.kt index 57a9db4a20..9819bc9254 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistCallbackTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistCallbackTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt index 234a9c30ff..631def9f75 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistClassNameTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -62,6 +63,7 @@ class KonsistClassNameTest { .withAllParentsOf(PreviewParameterProvider::class) .withoutName( "AspectRatioProvider", + "EditableAvatarViewUriProvider", "LoginModeViewErrorProvider", "OverlapRatioProvider", "TextFileContentProvider", @@ -97,8 +99,11 @@ class KonsistClassNameTest { .classes() .withNameContaining("Fake") .withoutName( + "FakeAesKeyGenerator", "FakeFileSystem", "FakeImageLoader", + "FakeKeyStore", + "FakeListenableFuture", ) .assertTrue { val interfaceName = it.name @@ -148,6 +153,7 @@ class KonsistClassNameTest { "Factory", "TimelineController", "TimelineMediaGalleryDataSource", + "MetroWorkerFactory", ) .withoutNameStartingWith( "Accompanist", diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistComposableTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistComposableTest.kt index 181c63350b..b3db2afd38 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistComposableTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistComposableTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistConfigTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistConfigTest.kt index bd967bf13b..bd8c4b593d 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistConfigTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistConfigTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistContentTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistContentTest.kt index a63eb1d925..b7403f4e4b 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistContentTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistContentTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistDiTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistDiTest.kt index 093cfd4488..a5b2f1572c 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistDiTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistDiTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -10,8 +11,10 @@ package io.element.android.tests.konsist import com.lemonappdev.konsist.api.Konsist import com.lemonappdev.konsist.api.ext.list.withAnnotationOf import com.lemonappdev.konsist.api.ext.list.withParameter +import com.lemonappdev.konsist.api.verify.assertFalse import com.lemonappdev.konsist.api.verify.assertTrue import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.Inject import org.junit.Test @@ -32,4 +35,15 @@ class KonsistDiTest { .isEmpty() } } + + @Test + fun `class annotated with @ContributesBinding does not need to be annotated with @Inject anymore`() { + Konsist + .scopeFromProject() + .classes() + .withAnnotationOf(ContributesBinding::class) + .assertFalse { classDeclaration -> + classDeclaration.hasAnnotationOf(Inject::class) + } + } } diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistFieldTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistFieldTest.kt index c9b67854c1..bfbe76d358 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistFieldTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistFieldTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistFlowTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistFlowTest.kt index b9cca40740..4aa5e5db81 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistFlowTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistFlowTest.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistImmutableTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistImmutableTest.kt index 0aa68503e5..ceb0058124 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistImmutableTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistImmutableTest.kt @@ -1,14 +1,19 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ package io.element.android.tests.konsist +import androidx.compose.runtime.Immutable import com.lemonappdev.konsist.api.Konsist +import com.lemonappdev.konsist.api.ext.list.withAnnotationOf +import com.lemonappdev.konsist.api.ext.list.withNameEndingWith import com.lemonappdev.konsist.api.ext.list.withoutName +import com.lemonappdev.konsist.api.verify.assertEmpty import com.lemonappdev.konsist.api.verify.assertFalse import org.junit.Test @@ -60,4 +65,14 @@ class KonsistImmutableTest { it.text.contains(".toPersistentMap()") } } + + @Test + fun `Immutable annotation is not used on sealed interface for Presenter Events`() { + Konsist + .scopeFromProduction() + .interfaces() + .withNameEndingWith("Events") + .withAnnotationOf(Immutable::class) + .assertEmpty() + } } diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistImportTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistImportTest.kt index c921f3fd60..10a1903bd4 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistImportTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistImportTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistLicenseTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistLicenseTest.kt index c3a3d3e9e2..f47621da0e 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistLicenseTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistLicenseTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -15,16 +16,18 @@ import org.junit.Test class KonsistLicenseTest { private val publicLicense = """ /\* - (?:.*\n)* \* Copyright 20\d\d((, |-)20\d\d)? New Vector Ltd. + (?:.*\n)* \* Copyright \(c\) 20\d\d((, |-)20\d\d)? Element Creations Ltd\. (?:.*\n)* \* - \* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial - \* Please see LICENSE files in the repository root for full details. + \* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial\. + \* Please see LICENSE files in the repository root for full details\. \*/ """.trimIndent().toRegex() private val enterpriseLicense = """ /\* - \* © 20\d\d New Vector Limited, Element Software SARL, Element Software Inc\., + \* © 20\d\d((, |-)20\d\d)? Element Creations Ltd\. + (?:.*\n)* \* + \* Element Creations Ltd, Element Software SARL, Element Software Inc\., \* and Element Software GmbH \(the "Element Group"\) only make this file available \* under a proprietary license model\. \* @@ -80,7 +83,7 @@ class KonsistLicenseTest { it.name.startsWith("Template ").not() } .assertTrue { - it.text.count("New Vector") == 1 + it.text.count("Element Creations Ltd.") == 1 } } } diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistMethodNameTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistMethodNameTest.kt index 2b0e32186d..096885f976 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistMethodNameTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistMethodNameTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistParameterNameTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistParameterNameTest.kt index 340b5f3703..f202148464 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistParameterNameTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistParameterNameTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPresenterTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPresenterTest.kt index 2b92d9bd07..de8b960654 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPresenterTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt index 082a106be2..32f2908cf7 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistPreviewTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -79,16 +80,13 @@ class KonsistPreviewTest { "AsyncIndicatorFailurePreview", "AsyncIndicatorLoadingPreview", "BackgroundVerticalGradientDisabledPreview", - "BackgroundVerticalGradientEnterprisePreview", "BackgroundVerticalGradientPreview", "ColorAliasesPreview", - "DefaultRoomListTopBarMultiAccountPreview", - "DefaultRoomListTopBarWithIndicatorPreview", - "FocusedEventEnterprisePreview", "FocusedEventPreview", "GradientFloatingActionButtonCircleShapePreview", "HeaderFooterPageScrollablePreview", - "IconsCompoundPreview", + "HomeTopBarMultiAccountPreview", + "HomeTopBarWithIndicatorPreview", "IconsOtherPreview", "MarkdownTextComposerEditPreview", "MatrixBadgeAtomInfoPreview", @@ -99,6 +97,7 @@ class KonsistPreviewTest { "MessageComposerViewVoicePreview", "MessagesReactionButtonAddPreview", "MessagesReactionButtonExtraPreview", + "MessagesViewWithHistoryVisiblePreview", "MessagesViewWithIdentityChangePreview", "PendingMemberRowWithLongNamePreview", "PinUnlockViewInAppPreview", @@ -118,7 +117,6 @@ class KonsistPreviewTest { "ProgressDialogWithContentPreview", "ProgressDialogWithTextAndContentPreview", "ReadReceiptBottomSheetPreview", - "RoomMemberListViewBannedPreview", "SasEmojisPreview", "SecureBackupSetupViewChangePreview", "SelectedUserCannotRemovePreview", diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistTestTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistTestTest.kt index f480a0bb62..2e3fe383d1 100644 --- a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistTestTest.kt +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistTestTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/testutils/build.gradle.kts b/tests/testutils/build.gradle.kts index 8a7730095a..4acb06305f 100644 --- a/tests/testutils/build.gradle.kts +++ b/tests/testutils/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ plugins { diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/AssertThrowInDebug.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/AssertThrowInDebug.kt index 2626f48036..88ae13a29e 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/AssertThrowInDebug.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/AssertThrowInDebug.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EnsureCalledOnce.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EnsureCalledOnce.kt index 15d09e17a9..520bc22a7a 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EnsureCalledOnce.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EnsureCalledOnce.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EnsureNeverCalled.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EnsureNeverCalled.kt index fe45364f49..7c3d4ec600 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EnsureNeverCalled.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EnsureNeverCalled.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EventsRecorder.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EventsRecorder.kt index 6437894074..18db0d7d2c 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EventsRecorder.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/EventsRecorder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/InstrumentationStringProvider.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/InstrumentationStringProvider.kt index fd73c3931f..75d91c4a88 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/InstrumentationStringProvider.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/InstrumentationStringProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/LongTask.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/LongTask.kt index b8305e6f81..bbaa1cf741 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/LongTask.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/LongTask.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -33,7 +34,7 @@ suspend fun awaitWithLatch(timeout: Duration = 300.milliseconds, block: (Complet withTimeout(timeout) { latch.also(block).await() } - } catch (exception: TimeoutCancellationException) { + } catch (_: TimeoutCancellationException) { latch.complete(Unit) } } diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/MutablePresenter.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/MutablePresenter.kt index 9ad6a2cc29..ae8768e0b0 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/MutablePresenter.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/MutablePresenter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/PresenterTest.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/PresenterTest.kt index c5acc9d510..d7c53f6976 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/PresenterTest.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/PresenterTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,6 +13,7 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.TurbineTestContext import app.cash.turbine.test import io.element.android.libraries.architecture.Presenter +import org.junit.Assert.fail import kotlin.time.Duration suspend fun Presenter.test( @@ -19,7 +21,20 @@ suspend fun Presenter.test( name: String? = null, validate: suspend TurbineTestContext.() -> Unit, ) { - moleculeFlow(RecompositionMode.Immediate) { - present() - }.test(timeout, name, validate) + try { + moleculeFlow(RecompositionMode.Immediate) { + present() + }.test(timeout, name, validate) + } catch (t: Throwable) { + if (t::class.simpleName == "KotlinReflectionInternalError") { + // Give a more explicit error to the developer + fail(""" + It looks like you have an unconsumed event in your test. + If you get this error, it means that your test is missing to consume one or several events. + You can fix by consuming and check the event with `awaitItem()`, or you can also invoke + `cancelAndIgnoreRemainingEvents()`. + """.trimIndent()) + } + throw t + } } diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/ReceiveTurbine.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/ReceiveTurbine.kt index fe574796ec..80e8a03f75 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/ReceiveTurbine.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/ReceiveTurbine.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/RobolectricDispatcherCleaner.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/RobolectricDispatcherCleaner.kt index 620b5def40..12cfe44b44 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/RobolectricDispatcherCleaner.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/RobolectricDispatcherCleaner.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/SemanticsNodeInteractionsProviderExtensions.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/SemanticsNodeInteractionsProviderExtensions.kt index 55371a960d..6502882d7d 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/SemanticsNodeInteractionsProviderExtensions.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/SemanticsNodeInteractionsProviderExtensions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,34 +10,33 @@ package io.element.android.tests.testutils import androidx.activity.ComponentActivity import androidx.annotation.StringRes +import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import androidx.compose.ui.test.hasAnyAncestor import androidx.compose.ui.test.hasClickAction import androidx.compose.ui.test.hasContentDescription import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.isDialog import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.onFirst import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import io.element.android.libraries.ui.strings.CommonStrings import org.junit.rules.TestRule -fun AndroidComposeTestRule.clickOn(@StringRes res: Int) { +val trueMatcher = SemanticsMatcher("true matcher") { true } + +fun AndroidComposeTestRule.clickOn( + @StringRes res: Int, + inDialog: Boolean = false, +) { val text = activity.getString(res) - onNode(hasText(text) and hasClickAction()) + onNode( + hasText(text) and hasClickAction() and if (inDialog) hasAnyAncestor(isDialog()) else trueMatcher + ) .performClick() } -fun AndroidComposeTestRule.clickOnFirst(@StringRes res: Int) { - val text = activity.getString(res) - onAllNodes(hasText(text) and hasClickAction()).onFirst().performClick() -} - -fun AndroidComposeTestRule.clickOnLast(@StringRes res: Int) { - val text = activity.getString(res) - onAllNodes(hasText(text) and hasClickAction()).onFirst().performClick() -} - /** * Press the back button in the app bar. */ diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/TestComposable.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/TestComposable.kt index 673fb70bd5..a4f38cc6e8 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/TestComposable.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/TestComposable.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/TestCoroutineDispatchers.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/TestCoroutineDispatchers.kt index 84d5e1aebb..c4773bf21d 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/TestCoroutineDispatchers.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/TestCoroutineDispatchers.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/Timber.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/Timber.kt index 2aad499698..e1e4218b8c 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/Timber.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/Timber.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WaitingForAssertion.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WaitingForAssertion.kt index 1ffba02d4d..701542cd11 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WaitingForAssertion.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WaitingForAssertion.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WarmUpRule.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WarmUpRule.kt index e2bd73b5d5..d902eaa824 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WarmUpRule.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WarmUpRule.kt @@ -1,7 +1,8 @@ /* - * Copyright 2023, 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2023-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WithFakeLifecycleOwner.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WithFakeLifecycleOwner.kt index 16d4cfb2fd..5ff2bf06a3 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WithFakeLifecycleOwner.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/WithFakeLifecycleOwner.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -12,8 +13,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.InternalComposeApi import androidx.compose.runtime.Stable import androidx.compose.runtime.currentComposer -import androidx.compose.runtime.getValue -import androidx.compose.runtime.setValue import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry @@ -26,8 +25,6 @@ import io.element.android.libraries.architecture.Presenter /** * Composable that provides a fake [LifecycleOwner] to the composition. - * - * **WARNING: DO NOT USE OUTSIDE TESTS.** */ @OptIn(InternalComposeApi::class) @Stable @@ -44,19 +41,16 @@ fun withFakeLifecycleOwner( /** * Test a [Presenter] with a fake [LifecycleOwner]. - * - * **WARNING: DO NOT USE OUTSIDE TESTS.** */ suspend fun Presenter.testWithLifecycleOwner( lifecycleOwner: FakeLifecycleOwner = FakeLifecycleOwner(), block: suspend TurbineTestContext.() -> Unit ) { moleculeFlow(RecompositionMode.Immediate) { - val ret = withFakeLifecycleOwner(lifecycleOwner) { + withFakeLifecycleOwner(lifecycleOwner) { present() } - ret - }.test(validate = block) + }.test(validate = block) } @SuppressLint("VisibleForTests") diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/fake/FakeAndroidKeyStore.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/fake/FakeAndroidKeyStore.kt new file mode 100644 index 0000000000..9edbec9795 --- /dev/null +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/fake/FakeAndroidKeyStore.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025 Element Creations Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.tests.testutils.fake + +import java.io.InputStream +import java.io.OutputStream +import java.security.Key +import java.security.KeyStore +import java.security.KeyStoreSpi +import java.security.Provider +import java.security.SecureRandom +import java.security.Security +import java.security.cert.Certificate +import java.security.spec.AlgorithmParameterSpec +import java.util.Date +import java.util.Enumeration +import javax.crypto.KeyGenerator +import javax.crypto.KeyGeneratorSpi +import javax.crypto.SecretKey + +// Source - https://stackoverflow.com/questions/38213748/using-the-android-keystore-in-robolectric-tests/75763240#75763240 +// Posted by Victor Oliveira +// Retrieved 2025-12-19, License - CC BY-SA 4.0 + +object FakeAndroidKeyStore { + val setup by lazy { + Security.addProvider(object : Provider("AndroidKeyStore", 1.0, "") { + init { + put("KeyStore.AndroidKeyStore", FakeKeyStore::class.java.name) + put("KeyGenerator.AES", FakeAesKeyGenerator::class.java.name) + } + }) + } + + class FakeKeyStore : KeyStoreSpi() { + private val wrapped = KeyStore.getInstance(KeyStore.getDefaultType()) + + override fun engineIsKeyEntry(alias: String?): Boolean = wrapped.isKeyEntry(alias) + override fun engineIsCertificateEntry(alias: String?): Boolean = wrapped.isCertificateEntry(alias) + override fun engineGetCertificate(alias: String?): Certificate = wrapped.getCertificate(alias) + override fun engineGetCreationDate(alias: String?): Date = wrapped.getCreationDate(alias) + override fun engineDeleteEntry(alias: String?) = wrapped.deleteEntry(alias) + override fun engineSetKeyEntry(alias: String?, key: Key?, password: CharArray?, chain: Array?) = + wrapped.setKeyEntry(alias, key, password, chain) + + override fun engineSetKeyEntry(alias: String?, key: ByteArray?, chain: Array?) = wrapped.setKeyEntry(alias, key, chain) + override fun engineStore(stream: OutputStream?, password: CharArray?) = wrapped.store(stream, password) + override fun engineSize(): Int = wrapped.size() + override fun engineAliases(): Enumeration = wrapped.aliases() + override fun engineContainsAlias(alias: String?): Boolean = wrapped.containsAlias(alias) + override fun engineLoad(stream: InputStream?, password: CharArray?) = wrapped.load(stream, password) + override fun engineGetCertificateChain(alias: String?): Array = wrapped.getCertificateChain(alias) + override fun engineSetCertificateEntry(alias: String?, cert: Certificate?) = wrapped.setCertificateEntry(alias, cert) + override fun engineGetCertificateAlias(cert: Certificate?): String = wrapped.getCertificateAlias(cert) + override fun engineGetKey(alias: String?, password: CharArray?): Key = wrapped.getKey(alias, password) + } + + class FakeAesKeyGenerator : KeyGeneratorSpi() { + private val wrapped = KeyGenerator.getInstance("AES") + + override fun engineInit(random: SecureRandom?) = Unit + override fun engineInit(params: AlgorithmParameterSpec?, random: SecureRandom?) = Unit + override fun engineInit(keysize: Int, random: SecureRandom?) = Unit + override fun engineGenerateKey(): SecretKey = wrapped.generateKey() + } +} diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/fake/FakeTemporaryUriDeleter.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/fake/FakeTemporaryUriDeleter.kt index 2493167eb9..e6fc1c2d8c 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/fake/FakeTemporaryUriDeleter.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/fake/FakeTemporaryUriDeleter.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Assertions.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Assertions.kt index af43e4fbd8..db4e381456 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Assertions.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Assertions.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Error.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Error.kt index f9e2ec0cbb..95140b942d 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Error.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Error.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/LambdaRecorder.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/LambdaRecorder.kt index 0966dd1cb7..e90518dee4 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/LambdaRecorder.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/LambdaRecorder.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/ParameterMatcher.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/ParameterMatcher.kt index 2b16b099bf..7b15c29bc8 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/ParameterMatcher.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/ParameterMatcher.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/node/TestParentNode.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/node/TestParentNode.kt index 79def028f7..c994733340 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/node/TestParentNode.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/node/TestParentNode.kt @@ -1,7 +1,8 @@ /* + * Copyright (c) 2025 Element Creations Ltd. * Copyright 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/uitests/build.gradle.kts b/tests/uitests/build.gradle.kts index 6893a780b4..466167981c 100644 --- a/tests/uitests/build.gradle.kts +++ b/tests/uitests/build.gradle.kts @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -19,6 +20,11 @@ android { namespace = "ui" } +tasks.withType(Test::class) { + // Don't fail the test run if there are no tests, this can happen if we run them with screenshot test disabled + failOnNoDiscoveredTests = false +} + dependencies { // Paparazzi 1.3.2 workaround (see https://github.com/cashapp/paparazzi/blob/master/CHANGELOG.md#132---2024-01-13) constraints.add("testImplementation", "com.google.guava:guava") { diff --git a/tests/uitests/src/test/kotlin/base/BaseDeviceConfig.kt b/tests/uitests/src/test/kotlin/base/BaseDeviceConfig.kt index 56cbc41e65..486fb8e1e4 100644 --- a/tests/uitests/src/test/kotlin/base/BaseDeviceConfig.kt +++ b/tests/uitests/src/test/kotlin/base/BaseDeviceConfig.kt @@ -1,7 +1,8 @@ /* - * Copyright 2022-2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2022-2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ diff --git a/tests/uitests/src/test/kotlin/base/ComposablePreviewProvider.kt b/tests/uitests/src/test/kotlin/base/ComposablePreviewProvider.kt index 477e6a2da2..5ae7c75d71 100644 --- a/tests/uitests/src/test/kotlin/base/ComposablePreviewProvider.kt +++ b/tests/uitests/src/test/kotlin/base/ComposablePreviewProvider.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -9,7 +10,7 @@ package base -import com.google.testing.junit.testparameterinjector.TestParameter +import com.google.testing.junit.testparameterinjector.TestParameterValuesProvider import sergio.sastre.composable.preview.scanner.android.AndroidComposablePreviewScanner import sergio.sastre.composable.preview.scanner.android.AndroidPreviewInfo import sergio.sastre.composable.preview.scanner.core.preview.ComposablePreview @@ -24,8 +25,8 @@ private val PACKAGE_TREES = arrayOf( "io.element.android.x", ) -object ComposablePreviewProvider : TestParameter.TestParameterValuesProvider { - private val values: List>> by lazy { +object ComposablePreviewProvider : TestParameterValuesProvider() { + val values: List>> by lazy { AndroidComposablePreviewScanner() .scanPackageTrees(*PACKAGE_TREES) .getPreviews() @@ -34,10 +35,10 @@ object ComposablePreviewProvider : TestParameter.TestParameterValuesProvider { .toList() } - override fun provideValues(): List>> = values + override fun provideValues(context: Context): List>> = values } -object ComposableA11yPreviewProvider : TestParameter.TestParameterValuesProvider { +object ComposableA11yPreviewProvider : TestParameterValuesProvider() { private val values: List> by lazy { AndroidComposablePreviewScanner() .scanPackageTrees(*PACKAGE_TREES) @@ -46,25 +47,25 @@ object ComposableA11yPreviewProvider : TestParameter.TestParameterValuesProvider .toList() } - override fun provideValues(): List> = values + override fun provideValues(context: Context): List> = values } -object Shard1ComposablePreviewProvider : TestParameter.TestParameterValuesProvider { - override fun provideValues(): List> = - ComposablePreviewProvider.provideValues().filter { it.index % 4 == 0 }.map { it.value } +object Shard1ComposablePreviewProvider : TestParameterValuesProvider() { + override fun provideValues(context: Context): List> = + ComposablePreviewProvider.values.filter { it.index % 4 == 0 }.map { it.value } } -object Shard2ComposablePreviewProvider : TestParameter.TestParameterValuesProvider { - override fun provideValues(): List> = - ComposablePreviewProvider.provideValues().filter { it.index % 4 == 1 }.map { it.value } +object Shard2ComposablePreviewProvider : TestParameterValuesProvider() { + override fun provideValues(context: Context): List> = + ComposablePreviewProvider.values.filter { it.index % 4 == 1 }.map { it.value } } -object Shard3ComposablePreviewProvider : TestParameter.TestParameterValuesProvider { - override fun provideValues(): List> = - ComposablePreviewProvider.provideValues().filter { it.index % 4 == 2 }.map { it.value } +object Shard3ComposablePreviewProvider : TestParameterValuesProvider() { + override fun provideValues(context: Context): List> = + ComposablePreviewProvider.values.filter { it.index % 4 == 2 }.map { it.value } } -object Shard4ComposablePreviewProvider : TestParameter.TestParameterValuesProvider { - override fun provideValues(): List> = - ComposablePreviewProvider.provideValues().filter { it.index % 4 == 3 }.map { it.value } +object Shard4ComposablePreviewProvider : TestParameterValuesProvider() { + override fun provideValues(context: Context): List> = + ComposablePreviewProvider.values.filter { it.index % 4 == 3 }.map { it.value } } diff --git a/tests/uitests/src/test/kotlin/base/ScreenshotTest.kt b/tests/uitests/src/test/kotlin/base/ScreenshotTest.kt index 114b2775f0..0b088b5de0 100644 --- a/tests/uitests/src/test/kotlin/base/ScreenshotTest.kt +++ b/tests/uitests/src/test/kotlin/base/ScreenshotTest.kt @@ -1,7 +1,8 @@ /* - * Copyright 2024 New Vector Ltd. + * Copyright (c) 2025 Element Creations Ltd. + * Copyright 2024, 2025 New Vector Ltd. * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * Please see LICENSE files in the repository root for full details. */ @@ -82,7 +83,7 @@ private fun Paparazzi.fixScreenshotName(preview: ComposablePreview diff --git a/tools/localazy/checkForbiddenTerms.py b/tools/localazy/checkForbiddenTerms.py index dc522ebf33..e190fcea68 100755 --- a/tools/localazy/checkForbiddenTerms.py +++ b/tools/localazy/checkForbiddenTerms.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 -# Copyright 2024 New Vector Ltd. +# Copyright (c) 2025 Element Creations Ltd. +# Copyright 2024, 2025 New Vector Ltd. # -# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. # Please see LICENSE files in the repository root for full details. +import re import sys from xml.dom import minidom @@ -13,7 +15,7 @@ file = sys.argv[1] # Dict of forbidden terms, with exceptions for some String name # Keys are the terms, values are the exceptions. forbiddenTerms = { - "Element": [ + r"\bElement\b": [ # Those 2 strings are only used in debug version "screen_advanced_settings_element_call_base_url", "screen_advanced_settings_element_call_base_url_description", @@ -47,7 +49,8 @@ for elem in content.getElementsByTagName('string'): value = child.nodeValue # If value contains a forbidden term, add the error to errors for (term, exceptions) in forbiddenTerms.items(): - if term in value and name not in exceptions: + matches = re.search(term, value) + if matches and name not in exceptions: errors.append('Forbidden term "' + term + '" in string: "' + name + '": ' + value) ### Plurals @@ -64,7 +67,8 @@ for elem in content.getElementsByTagName('plurals'): value = child.nodeValue # If value contains a forbidden term, add the error to errors for (term, exceptions) in forbiddenTerms.items(): - if term in value and name not in exceptions: + matches = re.search(term, value) + if matches and name not in exceptions: errors.append('Forbidden term "' + term + '" in plural: "' + name + '": ' + value) # If errors is not empty print the report diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 82c273db2e..a8572fb660 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -129,6 +129,7 @@ "includeRegex" : [ "push_.*", "notification_.*", + "notification\\..*", "troubleshoot_notifications\\.test_blocked_users\\..*", "troubleshoot_notifications_test_current_push_provider.*", "troubleshoot_notifications_test_detect_push_provider.*", @@ -154,6 +155,17 @@ "troubleshoot_notifications_test_unified_push_.*" ] }, + { + "name" : ":features:linknewdevice:impl", + "includeRegex" : [ + "screen\\.link_new_device\\..*", + "screen_qr_code_login_error_.*", + "screen_qr_code_login_connection_note_secure_state.*", + "screen_qr_code_login_unknown_error_description", + "screen_qr_code_login_invalid_scan_state_.*", + "screen_qr_code_login_no_camera_permission_state_.*" + ] + }, { "name" : ":features:login:impl", "includeRegex" : [ @@ -197,6 +209,7 @@ "screen_room_details_.*", "screen\\.room_details\\..*", "screen_room_member_list_.*", + "screen\\.room_member_list\\..*", "screen_room_notification_settings_.*", "screen_notification_settings_edit_failed_updating_default_mode", "screen_polls_history_title", @@ -207,10 +220,20 @@ "screen\\.security_and_privacy\\..*" ] }, + { + "name" : ":features:roomdetailsedit:impl", + "includeRegex" : [ + "screen_room_details_edit_room_title", + "screen_room_details_edition_error", + "screen_room_details_edition_error_title", + "screen_room_details_updating_room" + ] + }, { "name" : ":features:space:impl", "includeRegex" : [ - "screen\\.leave_space\\..*" + "screen\\.leave_space\\..*", + "screen\\.space_settings\\..*" ] }, { @@ -366,11 +389,21 @@ ] }, { - "name" : ":features:changeroommemberroles:impl", + "name" : ":features:rolesandpermissions:impl", "includeRegex" : [ "screen_room_change_.*", + "screen\\.room_change_permissions\\..*", "screen_room_roles_.*", - "screen_room_member_list.*" + "screen\\.room_roles_and_permissions\\..*", + "screen_room_member_list.*", + "screen\\.room_member_list\\..*" + ] + }, + { + "name" : ":features:securityandprivacy:impl", + "includeRegex" : [ + "screen\\.edit_room_address\\..*", + "screen\\.security_and_privacy\\..*" ] } ] diff --git a/tools/localazy/downloadStrings.sh b/tools/localazy/downloadStrings.sh index 9d855115c3..5a60656f35 100755 --- a/tools/localazy/downloadStrings.sh +++ b/tools/localazy/downloadStrings.sh @@ -1,8 +1,9 @@ #! /bin/bash +# Copyright (c) 2025 Element Creations Ltd. # Copyright 2023-2024 New Vector Ltd. # -# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. # Please see LICENSE files in the repository root for full details. set -e diff --git a/tools/localazy/formatXmlResourcesFile.py b/tools/localazy/formatXmlResourcesFile.py index fed34f1674..c1775c939b 100755 --- a/tools/localazy/formatXmlResourcesFile.py +++ b/tools/localazy/formatXmlResourcesFile.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -# Copyright 2024 New Vector Ltd. +# Copyright (c) 2025 Element Creations Ltd. +# Copyright 2024, 2025 New Vector Ltd. # -# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. # Please see LICENSE files in the repository root for full details. import re diff --git a/tools/localazy/generateLocalazyConfig.py b/tools/localazy/generateLocalazyConfig.py index cab23b17ee..c241a60cee 100755 --- a/tools/localazy/generateLocalazyConfig.py +++ b/tools/localazy/generateLocalazyConfig.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -# Copyright 2024 New Vector Ltd. +# Copyright (c) 2025 Element Creations Ltd. +# Copyright 2024, 2025 New Vector Ltd. # -# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. # Please see LICENSE files in the repository root for full details. import json diff --git a/tools/localazy/importSupportedLocalesFromLocalazy.py b/tools/localazy/importSupportedLocalesFromLocalazy.py index 526dc23b4f..e060d3c715 100755 --- a/tools/localazy/importSupportedLocalesFromLocalazy.py +++ b/tools/localazy/importSupportedLocalesFromLocalazy.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -# Copyright 2024 New Vector Ltd. +# Copyright (c) 2025 Element Creations Ltd. +# Copyright 2024, 2025 New Vector Ltd. # -# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. # Please see LICENSE files in the repository root for full details. import os diff --git a/tools/quality/check.sh b/tools/quality/check.sh index ce4544cd04..c4c8779dd3 100755 --- a/tools/quality/check.sh +++ b/tools/quality/check.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash +# Copyright (c) 2025 Element Creations Ltd. # Copyright 2023-2024 New Vector Ltd. # -# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. # Please see LICENSE files in the repository root for full details. # List of tasks to run before creating a PR, to limit the risk of getting rejected by the CI. diff --git a/tools/release/fix-pg-map-id.py b/tools/release/fix-pg-map-id.py index d074316034..c37fec2528 100644 --- a/tools/release/fix-pg-map-id.py +++ b/tools/release/fix-pg-map-id.py @@ -1,7 +1,7 @@ #!/usr/bin/python3 # encoding: utf-8 # SPDX-FileCopyrightText: 2024 FC (Fay) Stegerman -# SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-License-Identifier: GPL-3.0-or-later. import hashlib import os diff --git a/tools/release/inplace-fix.py b/tools/release/inplace-fix.py index 5a57f97d10..a791e69140 100644 --- a/tools/release/inplace-fix.py +++ b/tools/release/inplace-fix.py @@ -1,7 +1,7 @@ #!/usr/bin/python3 # encoding: utf-8 # SPDX-FileCopyrightText: 2024 FC (Fay) Stegerman -# SPDX-License-Identifier: GPL-3.0-or-later +# SPDX-License-Identifier: GPL-3.0-or-later. import argparse import os diff --git a/tools/release/release.sh b/tools/release/release.sh index 59a29dc13c..d89cc1ca0f 100755 --- a/tools/release/release.sh +++ b/tools/release/release.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash +# Copyright (c) 2025 Element Creations Ltd. # Copyright 2023-2024 New Vector Ltd. # -# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. # Please see LICENSE files in the repository root for full details. # do not exit when any command fails (issue with git flow) diff --git a/tools/rte/build_rte.sh b/tools/rte/build_rte.sh index 9ad52fce56..bd1aa43791 100755 --- a/tools/rte/build_rte.sh +++ b/tools/rte/build_rte.sh @@ -1,5 +1,10 @@ #!/usr/bin/env bash +# Copyright (c) 2025 Element Creations Ltd. +# +# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. +# Please see LICENSE files in the repository root for full details. + # Exit on error set -e diff --git a/tools/sas/import_sas_emojis.py b/tools/sas/import_sas_emojis.py index c399eec85e..6009ce2956 100755 --- a/tools/sas/import_sas_emojis.py +++ b/tools/sas/import_sas_emojis.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -# Copyright 2020-2024 New Vector Ltd. +# Copyright (c) 2025 Element Creations Ltd. +# Copyright 2020-2025 New Vector Ltd. # -# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. # Please see LICENSE files in the repository root for full details. import argparse diff --git a/tools/sas/import_sas_strings.py b/tools/sas/import_sas_strings.py index 78e4109221..3cf88b3a05 100755 --- a/tools/sas/import_sas_strings.py +++ b/tools/sas/import_sas_strings.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -# Copyright 2020-2024 New Vector Ltd. +# Copyright (c) 2025 Element Creations Ltd. +# Copyright 2020-2025 New Vector Ltd. # -# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. # Please see LICENSE files in the repository root for full details. import argparse diff --git a/tools/sdk/build_rust_sdk.sh b/tools/sdk/build_rust_sdk.sh index 91d6472426..0b74b0c4cb 100755 --- a/tools/sdk/build_rust_sdk.sh +++ b/tools/sdk/build_rust_sdk.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash +# Copyright (c) 2025 Element Creations Ltd. # Copyright 2024 New Vector Ltd. # -# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. # Please see LICENSE files in the repository root for full details. # Exit on error diff --git a/tools/templates/files/fileTemplates/Template Module Feature Entry Point API.kt b/tools/templates/files/fileTemplates/Template Module Feature Entry Point API.kt index 37f0500444..c4357b2d3c 100644 --- a/tools/templates/files/fileTemplates/Template Module Feature Entry Point API.kt +++ b/tools/templates/files/fileTemplates/Template Module Feature Entry Point API.kt @@ -6,12 +6,11 @@ import com.bumble.appyx.core.plugin.Plugin import io.element.android.libraries.architecture.FeatureEntryPoint interface ${FEATURE_NAME}EntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder - - interface NodeBuilder { - fun callback(callback: Callback): NodeBuilder - fun build(): Node - } + fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: Callback, + ): Node interface Callback : Plugin { // Add your callbacks diff --git a/tools/templates/files/fileTemplates/Template Module Feature Entry Point Flow Impl.kt b/tools/templates/files/fileTemplates/Template Module Feature Entry Point Flow Impl.kt index e3089e8d5e..3642f8e821 100644 --- a/tools/templates/files/fileTemplates/Template Module Feature Entry Point Flow Impl.kt +++ b/tools/templates/files/fileTemplates/Template Module Feature Entry Point Flow Impl.kt @@ -7,25 +7,15 @@ import dev.zacsweers.metro.ContributesBinding import io.element.android.features.${MODULE_NAME}.api.${FEATURE_NAME}EntryPoint import io.element.android.libraries.architecture.createNode import dev.zacsweers.metro.AppScope -import dev.zacsweers.metro.Inject @ContributesBinding(AppScope::class) -@Inject class Default${FEATURE_NAME}EntryPoint() : ${FEATURE_NAME}EntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): ${FEATURE_NAME}EntryPoint.NodeBuilder { - val plugins = ArrayList() - - return object : ${FEATURE_NAME}EntryPoint.NodeBuilder { - - override fun callback(callback: ${FEATURE_NAME}EntryPoint.Callback): ${FEATURE_NAME}EntryPoint.NodeBuilder { - plugins += callback - return this - } - - override fun build(): Node { - return parentNode.createNode<${FEATURE_NAME}FlowNode>(buildContext, plugins) - } - } + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: ${FEATURE_NAME}EntryPoint.Callback, + ): Node { + return parentNode.createNode<${FEATURE_NAME}FlowNode>(buildContext, listOf(callback)) } } diff --git a/tools/templates/files/fileTemplates/Template Presentation Classes.kt b/tools/templates/files/fileTemplates/Template Presentation Classes.kt index 91ec2165f7..a527a37b57 100644 --- a/tools/templates/files/fileTemplates/Template Presentation Classes.kt +++ b/tools/templates/files/fileTemplates/Template Presentation Classes.kt @@ -10,14 +10,14 @@ class ${NAME}Presenter() : Presenter<${NAME}State> { @Composable override fun present(): ${NAME}State { - fun handleEvents(event: ${NAME}Events) { + fun handleEvent(event: ${NAME}Event) { when (event) { - ${NAME}Events.MyEvent -> Unit + ${NAME}Event.MyEvent -> Unit } } return ${NAME}State( - eventSink = ::handleEvents + eventSink = ::handleEvent, ) } } diff --git a/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.3.kt b/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.3.kt index 3fcdd7f219..f182b62d96 100644 --- a/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.3.kt +++ b/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.3.kt @@ -1,7 +1,6 @@ #if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME}#end // TODO add your ui models. Remove the eventSink if you don't have events. -// Do not use default value, so no member get forgotten in the presenters. data class ${NAME}State( - val eventSink: (${NAME}Events) -> Unit + val eventSink: (${NAME}Event) -> Unit ) diff --git a/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.4.kt b/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.4.kt index 4b47069304..8ef5400fd9 100644 --- a/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.4.kt +++ b/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.4.kt @@ -1,6 +1,6 @@ #if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME}#end // TODO Add your events or remove the file completely if no events -sealed interface ${NAME}Events { - data object MyEvent: ${NAME}Events +sealed interface ${NAME}Event { + data object MyEvent: ${NAME}Event } diff --git a/tools/templates/files/options/file.template.settings.xml b/tools/templates/files/options/file.template.settings.xml index c7d26d1fb7..a284c63140 100644 --- a/tools/templates/files/options/file.template.settings.xml +++ b/tools/templates/files/options/file.template.settings.xml @@ -6,13 +6,13 @@