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 @@
[](https://sonarcloud.io/summary/new_code?id=element-x-android)
[](https://sonarcloud.io/summary/new_code?id=element-x-android)
[](https://sonarcloud.io/summary/new_code?id=element-x-android)
-[](https://codecov.io/github/vector-im/element-x-android)
+[](https://codecov.io/github/element-hq/element-x-android)
[](https://matrix.to/#/#element-x-android:matrix.org)
[](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