diff --git a/.github/workflows/post-pr.yml b/.github/workflows/post-pr.yml
index 8fe51eb8d5..49669e4201 100644
--- a/.github/workflows/post-pr.yml
+++ b/.github/workflows/post-pr.yml
@@ -29,200 +29,6 @@ jobs:
steps:
- run: echo "Run those tests!" # no-op success
- # Run Android Tests
- integration-tests:
- name: Matrix SDK - Running Integration Tests
- needs: should-i-run
- runs-on: macos-latest
- strategy:
- fail-fast: false
- matrix:
- api-level: [ 28 ]
- steps:
- - uses: actions/checkout@v3
- - uses: gradle/wrapper-validation-action@v1
- - uses: actions/setup-java@v3
- with:
- distribution: 'adopt'
- java-version: 11
- - name: Set up Python 3.8
- uses: actions/setup-python@v3
- with:
- python-version: 3.8
- - uses: actions/cache@v3
- with:
- path: |
- ~/.gradle/caches
- ~/.gradle/wrapper
- key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- restore-keys: |
- ${{ runner.os }}-gradle-
- - name: Start synapse server
- uses: michaelkaye/setup-matrix-synapse@v1.0.3
- with:
- uploadLogs: true
- httpPort: 8080
- disableRateLimiting: true
- public_baseurl: "http://10.0.2.2:8080/"
- # package: org.matrix.android.sdk.session
- - name: Run integration tests for Matrix SDK [org.matrix.android.sdk.session] API[${{ matrix.api-level }}]
- uses: reactivecircus/android-emulator-runner@v2
- with:
- api-level: ${{ matrix.api-level }}
- arch: x86
- profile: Nexus 5X
- force-avd-creation: false
- emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
- emulator-build: 7425822
- script: |
- adb root
- adb logcat -c
- touch emulator-session.log
- chmod 777 emulator-session.log
- adb logcat >> emulator-session.log &
- ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.package='org.matrix.android.sdk.session' matrix-sdk-android:connectedDebugAndroidTest
- - name: Read Results [org.matrix.android.sdk.session]
- if: always()
- id: get-comment-body-session
- run: python3 ./tools/ci/render_test_output.py session ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml
- - name: Remove adb logcat
- if: always()
- run: pkill -9 adb
- - name: Run integration tests for Matrix SDK [org.matrix.android.sdk.account] API[${{ matrix.api-level }}]
- if: always()
- uses: reactivecircus/android-emulator-runner@v2
- with:
- api-level: ${{ matrix.api-level }}
- arch: x86
- profile: Nexus 5X
- force-avd-creation: false
- emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
- emulator-build: 7425822
- script: |
- adb root
- adb logcat -c
- touch emulator-account.log
- chmod 777 emulator-account.log
- adb logcat >> emulator-account.log &
- ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.package='org.matrix.android.sdk.account' matrix-sdk-android:connectedDebugAndroidTest
- - name: Read Results [org.matrix.android.sdk.account]
- if: always()
- id: get-comment-body-account
- run: python3 ./tools/ci/render_test_output.py account ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml
- - name: Remove adb logcat
- if: always()
- run: pkill -9 adb
- # package: org.matrix.android.sdk.internal
- - name: Run integration tests for Matrix SDK [org.matrix.android.sdk.internal] API[${{ matrix.api-level }}]
- if: always()
- uses: reactivecircus/android-emulator-runner@v2
- with:
- api-level: ${{ matrix.api-level }}
- arch: x86
- profile: Nexus 5X
- force-avd-creation: false
- emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
- emulator-build: 7425822
- script: |
- adb root
- adb logcat -c
- touch emulator-internal.log
- chmod 777 emulator-internal.log
- adb logcat >> emulator-internal.log &
- ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.package='org.matrix.android.sdk.internal' matrix-sdk-android:connectedDebugAndroidTest
- - name: Read Results [org.matrix.android.sdk.internal]
- if: always()
- id: get-comment-body-internal
- run: python3 ./tools/ci/render_test_output.py internal ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml
- - name: Remove adb logcat
- if: always()
- run: pkill -9 adb
- # package: org.matrix.android.sdk.ordering
- - name: Run integration tests for Matrix SDK [org.matrix.android.sdk.ordering] API[${{ matrix.api-level }}]
- if: always()
- uses: reactivecircus/android-emulator-runner@v2
- with:
- api-level: ${{ matrix.api-level }}
- arch: x86
- profile: Nexus 5X
- force-avd-creation: false
- emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
- emulator-build: 7425822
- script: |
- adb root
- adb logcat -c
- touch emulator-ordering.log
- chmod 777 emulator-ordering.log
- adb logcat >> emulator-ordering.log &
- ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.package='org.matrix.android.sdk.ordering' matrix-sdk-android:connectedDebugAndroidTest
- - name: Read Results [org.matrix.android.sdk.ordering]
- if: always()
- id: get-comment-body-ordering
- run: python3 ./tools/ci/render_test_output.py ordering ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml
- - name: Remove adb logcat
- if: always()
- run: pkill -9 adb
- # package: class PermalinkParserTest
- - name: Run integration tests for Matrix SDK class [org.matrix.android.sdk.PermalinkParserTest] API[${{ matrix.api-level }}]
- if: always()
- uses: reactivecircus/android-emulator-runner@v2
- with:
- api-level: ${{ matrix.api-level }}
- arch: x86
- profile: Nexus 5X
- force-avd-creation: false
- emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
- emulator-build: 7425822
- script: |
- adb root
- adb logcat -c
- touch emulator-permalink.log
- chmod 777 emulator-permalink.log
- adb logcat >> emulator-permalink.log &
- ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.class='org.matrix.android.sdk.PermalinkParserTest' matrix-sdk-android:connectedDebugAndroidTest
- - name: Read Results [org.matrix.android.sdk.PermalinkParserTest]
- if: always()
- id: get-comment-body-permalink
- run: python3 ./tools/ci/render_test_output.py permalink ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml
- - name: Remove adb logcat
- if: always()
- run: pkill -9 adb
- # package: class PermalinkParserTest
- - name: Find Comment
- if: always() && github.event_name == 'pull_request'
- uses: peter-evans/find-comment@v2
- id: fc
- with:
- issue-number: ${{ github.event.pull_request.number }}
- comment-author: 'github-actions[bot]'
- body-includes: Integration Tests Results
- - name: Publish results to PR
- if: always() && github.event_name == 'pull_request'
- uses: peter-evans/create-or-update-comment@v2
- with:
- comment-id: ${{ steps.fc.outputs.comment-id }}
- issue-number: ${{ github.event.pull_request.number }}
- body: |
- ### Matrix SDK
- ## Integration Tests Results:
- - `[org.matrix.android.sdk.session]`
${{ steps.get-comment-body-session.outputs.session }}
- - `[org.matrix.android.sdk.account]`
${{ steps.get-comment-body-account.outputs.account }}
- - `[org.matrix.android.sdk.internal]`
${{ steps.get-comment-body-internal.outputs.internal }}
- - `[org.matrix.android.sdk.ordering]`
${{ steps.get-comment-body-ordering.outputs.ordering }}
- - `[org.matrix.android.sdk.PermalinkParserTest]`
${{ steps.get-comment-body-permalink.outputs.permalink }}
- edit-mode: replace
- - name: Upload Test Report Log
- uses: actions/upload-artifact@v3
- if: always()
- with:
- name: integrationtest-error-results
- path: |
- emulator-permalink.log
- emulator-internal.log
- emulator-ordering.log
- emulator-account.log
- emulator-session.log
-
ui-tests:
name: UI Tests (Synapse)
needs: should-i-run
@@ -282,42 +88,13 @@ jobs:
emulator.log
failure_screenshots/
- codecov-units:
- name: Unit tests with code coverage
- needs: should-i-run
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-java@v3
- with:
- distribution: 'adopt'
- java-version: '11'
- - uses: actions/cache@v3
- with:
- path: |
- ~/.gradle/caches
- ~/.gradle/wrapper
- key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- restore-keys: |
- ${{ runner.os }}-gradle-
- - run: ./gradlew allCodeCoverageReport $CI_GRADLE_ARG_PROPERTIES
- - name: Upload Codecov data
- uses: actions/upload-artifact@v3
- if: always()
- with:
- name: codecov-xml
- path: |
- build/reports/jacoco/allCodeCoverageReport/allCodeCoverageReport.xml
-
# Notify the channel about delayed failures
notify:
name: Notify matrix
runs-on: ubuntu-latest
needs:
- should-i-run
- - integration-tests
- ui-tests
- - codecov-units
if: always() && (needs.should-i-run.result == 'success' ) && ((needs.codecov-units.result != 'success' ) || (needs.ui-tests.result != 'success') || (needs.integration-tests.result != 'success'))
# No concurrency required, runs every time on a schedule.
steps:
diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml
deleted file mode 100644
index 6809751d91..0000000000
--- a/.github/workflows/sonarqube.yml
+++ /dev/null
@@ -1,81 +0,0 @@
-name: Sonarqube nightly
-
-on:
- schedule:
- - cron: '0 20 * * *'
-
-# Enrich gradle.properties for CI/CD
-env:
- CI_GRADLE_ARG_PROPERTIES: >
- -Porg.gradle.jvmargs=-Xmx4g
- -Porg.gradle.parallel=false
-jobs:
- codecov-units:
- name: Unit tests with code coverage
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-java@v3
- with:
- distribution: 'adopt'
- java-version: '11'
- - uses: actions/cache@v3
- with:
- path: |
- ~/.gradle/caches
- ~/.gradle/wrapper
- key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- restore-keys: |
- ${{ runner.os }}-gradle-
- - run: ./gradlew allCodeCoverageReport $CI_GRADLE_ARG_PROPERTIES
- - name: Upload Codecov data
- uses: actions/upload-artifact@v3
- if: always()
- with:
- name: codecov-xml
- path: |
- build/reports/jacoco/allCodeCoverageReport/allCodeCoverageReport.xml
-
- sonarqube:
- name: Sonarqube upload
- runs-on: ubuntu-latest
- needs:
- - codecov-units
- steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-java@v3
- with:
- distribution: 'adopt'
- java-version: '11'
- - uses: actions/cache@v3
- with:
- path: |
- ~/.gradle/caches
- ~/.gradle/wrapper
- key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- restore-keys: |
- ${{ runner.os }}-gradle-
- - uses: actions/download-artifact@v3
- with:
- name: codecov-xml # will restore to allCodeCoverageReport.xml by default; we restore to the same location in following tasks
- - run: mkdir -p build/reports/jacoco/allCodeCoverageReport/
- - run: mv allCodeCoverageReport.xml build/reports/jacoco/allCodeCoverageReport/
- - run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES
- env:
- ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }}
-
-# Notify the channel about sonarqube failures
- notify:
- name: Notify matrix
- runs-on: ubuntu-latest
- needs:
- - sonarqube
- - codecov-units
- if: always() && (needs.sonarqube.result != 'success' || needs.codecov-units.result != 'success')
- steps:
- - uses: michaelkaye/matrix-hookshot-action@v1.0.0
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
- hookshot_url: ${{ secrets.ELEMENT_ANDROID_HOOKSHOT_URL }}
- text_template: "Sonarqube run (on ${{ github.ref }}): {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}"
- html_template: "Sonarqube run (on ${{ github.ref }}): {{#each job_statuses }}{{#with this }}{{#if completed }}
{{icon conclusion}} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}"
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 3e8de8979c..5959fe9bb3 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -12,73 +12,98 @@ env:
-Porg.gradle.parallel=false
jobs:
- # Build Android Tests
- build-android-tests:
- name: Build Android Tests
- runs-on: ubuntu-latest
- concurrency:
- group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('build-android-tests-{0}', github.ref) }}
- cancel-in-progress: true
- steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-java@v3
- with:
- distribution: 'adopt'
- java-version: 11
- - uses: actions/cache@v3
- with:
- path: |
- ~/.gradle/caches
- ~/.gradle/wrapper
- key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- restore-keys: |
- ${{ runner.os }}-gradle-
- - name: Build Android Tests
- run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace
-
- unit-tests:
- name: Run Unit Tests
- runs-on: ubuntu-latest
+ tests:
+ name: Runs all tests
+ runs-on: macos-latest # for the emulator
# Allow all jobs on main and develop. Just one per PR.
concurrency:
group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('unit-tests-{0}', github.ref) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v3
- - uses: actions/cache@v3
with:
- path: |
- ~/.gradle/caches
- ~/.gradle/wrapper
- key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- restore-keys: |
- ${{ runner.os }}-gradle-
- - name: Run unit tests
- run: ./gradlew clean test $CI_GRADLE_ARG_PROPERTIES --stacktrace
+ fetch-depth: 0
+ - uses: actions/setup-java@v3
+ with:
+ distribution: 'adopt'
+ java-version: '11'
+ - uses: gradle/gradle-build-action@v2
+ - uses: actions/setup-python@v3
+ with:
+ python-version: 3.8
+ - uses: michaelkaye/setup-matrix-synapse@v1.0.3
+ with:
+ uploadLogs: true
+ httpPort: 8080
+ disableRateLimiting: true
+ public_baseurl: "http://10.0.2.2:8080/"
+ - name: Run all the codecoverage tests at once
+ id: tests
+ uses: reactivecircus/android-emulator-runner@v2
+ with:
+ api-level: 28
+ arch: x86
+ profile: Nexus 5X
+ force-avd-creation: false
+ emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+ disable-animations: true
+ emulator-build: 7425822
+ script: ./gradlew theCodeCoverageReport --stacktrace $CI_GRADLE_ARG_PROPERTIES
+ - name: Run all the codecoverage tests at once (retry if emulator failed)
+ uses: reactivecircus/android-emulator-runner@v2
+ if: always() && steps.tests.outcome == 'failure' # don't run if previous step succeeded.
+ with:
+ api-level: 28
+ arch: x86
+ profile: Nexus 5X
+ force-avd-creation: false
+ emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+ disable-animations: true
+ emulator-build: 7425822
+ script: ./gradlew theCodeCoverageReport --stacktrace $CI_GRADLE_ARG_PROPERTIES
+ - run: ./gradlew sonarqube $CI_GRADLE_ARG_PROPERTIES
+ if: always() # we may have failed a previous step and retried, that's OK
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }}
+
- name: Format unit test results
if: always()
run: python3 ./tools/ci/render_test_output.py unit ./**/build/test-results/**/*.xml
- - name: Publish Unit Test Results
- uses: EnricoMi/publish-unit-test-result-action@v1
- if: always() &&
- github.event.sender.login != 'dependabot[bot]' &&
- ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository )
- with:
- files: ./**/build/test-results/**/*.xml
-# Notify the channel about runs against develop or main that have failures, as PRs should have caught these first.
- notify:
- runs-on: ubuntu-latest
- needs:
- - unit-tests
- - build-android-tests
- if: ${{ (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main' ) && failure() }}
- steps:
- - uses: michaelkaye/matrix-hookshot-action@v0.3.0
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
- matrix_access_token: ${{ secrets.ELEMENT_ANDROID_NOTIFICATION_ACCESS_TOKEN }}
- matrix_room_id: ${{ secrets.ELEMENT_ANDROID_INTERNAL_ROOM_ID }}
- text_template: "Build is broken for ${{ github.ref }}: {{#each job_statuses }}{{#with this }}{{#if completed }}{{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}"
- html_template: "Build is broken for ${{ github.ref }}: {{#each job_statuses }}{{#with this }}{{#if completed }}
{{icon conclusion }} {{name}} {{conclusion}} at {{completed_at}} [details]{{/if}}{{/with}}{{/each}}"
+# can't be run on macos due to containers.
+# - name: Publish Unit Test Results
+# uses: EnricoMi/publish-unit-test-result-action@v1
+# if: always() &&
+# github.event.sender.login != 'dependabot[bot]' &&
+# ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository )
+# with:
+# files: ./**/build/test-results/**/*.xml
+
+# Unneeded as part of the test suite above, kept around in case we want to re-enable them.
+#
+# # Build Android Tests
+# build-android-tests:
+# name: Build Android Tests
+# runs-on: ubuntu-latest
+# concurrency:
+# group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('build-android-tests-{0}', github.ref) }}
+# cancel-in-progress: true
+# steps:
+# - uses: actions/checkout@v3
+# - uses: actions/setup-java@v3
+# with:
+# distribution: 'adopt'
+# java-version: 11
+# - uses: actions/cache@v3
+# with:
+# path: |
+# ~/.gradle/caches
+# ~/.gradle/wrapper
+# key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
+# restore-keys: |
+# ${{ runner.os }}-gradle-
+# - name: Build Android Tests
+# run: ./gradlew clean assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace
diff --git a/CHANGES.md b/CHANGES.md
index b0203c39e9..166453dfad 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,11 @@
+Changes in Element 1.4.19 (2022-06-07)
+======================================
+
+Bugfixes 🐛
+----------
+ - Fix | performance regression on roomlist + proper display of space parents in explore rooms. ([#6233](https://github.com/vector-im/element-android/issues/6233))
+
+
Changes in Element v1.4.18 (2022-05-31)
=======================================
diff --git a/README.md b/README.md
index 54dfb7b288..7acb5aa638 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
[![Buildkite](https://badge.buildkite.com/ad0065c1b70f557cd3b1d3d68f9c2154010f83c4d6f71706a9.svg?branch=develop)](https://buildkite.com/matrix-dot-org/element-android/builds?branch=develop)
[![Weblate](https://translate.element.io/widgets/element-android/-/svg-badge.svg)](https://translate.element.io/engage/element-android/?utm_source=widget)
[![Element Android Matrix room #element-android:matrix.org](https://img.shields.io/matrix/element-android:matrix.org.svg?label=%23element-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-android:matrix.org)
-[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=im.vector.app.android&metric=alert_status)](https://sonarcloud.io/dashboard?id=im.vector.app.android)
-[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=im.vector.app.android&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=im.vector.app.android)
-[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=im.vector.app.android&metric=bugs)](https://sonarcloud.io/dashboard?id=im.vector.app.android)
+[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vector-im_element-android&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=vector-im_element-android)
+[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=vector-im_element-android&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=vector-im_element-android)
+[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=vector-im_element-android&metric=bugs)](https://sonarcloud.io/summary/new_code?id=vector-im_element-android)
# Element Android
diff --git a/build.gradle b/build.gradle
index 635db83e14..03e175927c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -180,8 +180,8 @@ apply plugin: 'org.sonarqube'
sonarqube {
properties {
- property "sonar.projectName", "Element-Android"
- property "sonar.projectKey", "im.vector.app.android"
+ property "sonar.projectName", "element-android"
+ property "sonar.projectKey", "vector-im_element-android"
property "sonar.host.url", "https://sonarcloud.io"
property "sonar.projectVersion", project(":vector").android.defaultConfig.versionName
property "sonar.sourceEncoding", "UTF-8"
@@ -191,7 +191,7 @@ sonarqube {
property "sonar.links.issue", "https://github.com/vector-im/element-android/issues"
property "sonar.organization", "new_vector_ltd_organization"
property "sonar.java.coveragePlugin", "jacoco"
- property "sonar.coverage.jacoco.xmlReportPaths", "${project.buildDir}/reports/jacoco/allCodeCoverageReport/allCodeCoverageReport.xml"
+ property "sonar.coverage.jacoco.xmlReportPaths", "${project.buildDir}/reports/jacoco/theCodeCoverageReport/theCodeCoverageReport.xml"
property "sonar.login", project.hasProperty("SONAR_LOGIN") ? SONAR_LOGIN : "invalid"
}
}
diff --git a/changelog.d/5285.wip b/changelog.d/5285.wip
new file mode 100644
index 0000000000..1dd68597be
--- /dev/null
+++ b/changelog.d/5285.wip
@@ -0,0 +1 @@
+FTUE - Adds Sign Up tracking
diff --git a/changelog.d/6017.misc b/changelog.d/6017.misc
new file mode 100644
index 0000000000..2597f2d796
--- /dev/null
+++ b/changelog.d/6017.misc
@@ -0,0 +1 @@
+Adds support for parsing homeserver versions without a patch number
diff --git a/changelog.d/6146.feature b/changelog.d/6146.feature
new file mode 100644
index 0000000000..9d1e117ddb
--- /dev/null
+++ b/changelog.d/6146.feature
@@ -0,0 +1 @@
+Allow .well-known configuration to override key sharing mode
diff --git a/changelog.d/6169.sdk b/changelog.d/6169.sdk
new file mode 100644
index 0000000000..dfee158c3f
--- /dev/null
+++ b/changelog.d/6169.sdk
@@ -0,0 +1 @@
+Allows new passwords to be passed at the point of confirmation when resetting a password
diff --git a/changelog.d/6222.bugfix b/changelog.d/6222.bugfix
new file mode 100644
index 0000000000..ef430ee024
--- /dev/null
+++ b/changelog.d/6222.bugfix
@@ -0,0 +1 @@
+Fix StackOverflowError while recording voice message
diff --git a/changelog.d/6232.bugfix b/changelog.d/6232.bugfix
new file mode 100644
index 0000000000..df04655701
--- /dev/null
+++ b/changelog.d/6232.bugfix
@@ -0,0 +1 @@
+Text cropped: "Secure backup"
diff --git a/coverage.gradle b/coverage.gradle
index b62ce0b4a0..fb2352f47f 100644
--- a/coverage.gradle
+++ b/coverage.gradle
@@ -2,7 +2,10 @@ def excludes = [ ]
def initializeReport(report, projects, classExcludes) {
projects.each { project -> project.apply plugin: 'jacoco' }
- report.executionData { fileTree(rootProject.rootDir.absolutePath).include("**/build/jacoco/*.exec") }
+ report.executionData { fileTree(rootProject.rootDir.absolutePath).include(
+ "**/build/outputs/unit_test_code_coverage/**/*.exec",
+ "**/build/outputs/code_coverage/**/coverage.ec"
+ ) }
report.reports {
xml.enabled true
@@ -18,11 +21,13 @@ def initializeReport(report, projects, classExcludes) {
switch (project) {
case { project.plugins.hasPlugin("com.android.application") }:
androidClassDirs.add("${project.buildDir}/tmp/kotlin-classes/gplayDebug")
+ androidSourceDirs.add("${project.buildDir}/generated/source/kapt/gplayDebug")
androidSourceDirs.add("${project.projectDir}/src/main/kotlin")
androidSourceDirs.add("${project.projectDir}/src/main/java")
break
case { project.plugins.hasPlugin("com.android.library") }:
androidClassDirs.add("${project.buildDir}/tmp/kotlin-classes/debug")
+ androidSourceDirs.add("${project.buildDir}/generated/source/kapt/debug")
androidSourceDirs.add("${project.projectDir}/src/main/kotlin")
androidSourceDirs.add("${project.projectDir}/src/main/java")
break
@@ -43,13 +48,17 @@ def collectProjects(predicate) {
return subprojects.findAll { it.buildFile.isFile() && predicate(it) }
}
-task allCodeCoverageReport(type: JacocoReport) {
+task theCodeCoverageReport(type: JacocoReport) {
outputs.upToDateWhen { false }
rootProject.apply plugin: 'jacoco'
- // to limit projects in a specific report, add
- // def excludedProjects = [ ... ]
- // def projects = collectProjects { !excludedProjects.contains(it.name) }
- def projects = collectProjects { true }
- dependsOn { projects*.test }
+ tasks.withType(Test) {
+ jacoco.includeNoLocationClasses = true
+ }
+ def projects = collectProjects { ['vector','matrix-sdk-android'].contains(it.name) }
+ dependsOn {
+ [':matrix-sdk-android:testDebugUnitTest'] +
+ [':vector:testGplayDebugUnitTest'] +
+ [':matrix-sdk-android:connectedDebugAndroidTest']
+ }
initializeReport(it, projects, excludes)
}
diff --git a/fastlane/metadata/android/en-US/changelogs/40104190.txt b/fastlane/metadata/android/en-US/changelogs/40104190.txt
new file mode 100644
index 0000000000..61db61727a
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/40104190.txt
@@ -0,0 +1,2 @@
+Main changes in this version: Various bug fixes and stability improvements.
+Full changelog: https://github.com/vector-im/element-android/releases
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 3829063836..fbed8442a9 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -74,6 +74,7 @@ android {
buildTypes {
debug {
+ testCoverageEnabled true
// Set to true to log privacy or sensible data, such as token
buildConfigField "boolean", "LOG_PRIVATE_DATA", project.property("vector.debugPrivateData")
// Set to BODY instead of NONE to enable logging
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt
index a763766821..f08f0a28ed 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt
@@ -19,10 +19,14 @@ package org.matrix.android.sdk
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import org.junit.Rule
+import org.matrix.android.sdk.common.RetryTestRule
import org.matrix.android.sdk.test.shared.createTimberTestRule
interface InstrumentedTest {
+ @Rule
+ fun retryTestRule() = RetryTestRule(3)
+
@Rule
fun timberTestRule() = createTimberTestRule()
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt
index f5f585a1e0..17c7c28d81 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt
@@ -22,6 +22,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -40,6 +41,7 @@ import java.util.UUID
@Suppress("SpellCheckingInspection")
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Ignore
class AttachmentEncryptionTest {
private fun checkDecryption(input: String, encryptedFileInfo: EncryptedFileInfo): String {
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt
index cd6c146f03..dbc6929e34 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt
@@ -22,6 +22,7 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNull
import org.junit.Before
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,6 +38,7 @@ import org.matrix.olm.OlmSession
private const val DUMMY_DEVICE_KEY = "DeviceKey"
@RunWith(AndroidJUnit4::class)
+@Ignore
class CryptoStoreTest : InstrumentedTest {
@get:Rule val rule = RetryTestRule(3)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt
index 2f2d54b7e3..bddb31fc92 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ExportEncryptionTest.kt
@@ -21,6 +21,7 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -30,6 +31,7 @@ import org.junit.runners.MethodSorters
*/
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Ignore
class ExportEncryptionTest {
@Test
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
index 5fe7376184..e8a474a54a 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
@@ -21,6 +21,7 @@ import org.amshove.kluent.shouldBe
import org.junit.Assert
import org.junit.Before
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -59,6 +60,7 @@ import kotlin.coroutines.resume
*/
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
+@Ignore
class UnwedgingTest : InstrumentedTest {
private lateinit var messagesReceivedByBob: List
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt
index 05790bfb7d..8cb38ddc87 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt
@@ -25,6 +25,7 @@ import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -47,6 +48,7 @@ import kotlin.coroutines.resume
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@LargeTest
+@Ignore
class XSigningTest : InstrumentedTest {
@Test
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
index b16e4b82eb..994a055e5a 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
@@ -25,6 +25,7 @@ import org.amshove.kluent.internal.assertEquals
import org.junit.Assert
import org.junit.Assert.assertNull
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -50,6 +51,7 @@ import org.matrix.android.sdk.mustFail
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
@LargeTest
+@Ignore
class KeyShareTests : InstrumentedTest {
@get:Rule val rule = RetryTestRule(3)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
index 0aac4297e4..ae420a09b3 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
@@ -21,6 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import org.junit.Assert
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -46,6 +47,7 @@ import org.matrix.android.sdk.mustFail
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
@LargeTest
+@Ignore
class WithHeldTests : InstrumentedTest {
@get:Rule val rule = RetryTestRule(3)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
index c6e17e8c44..fb498e0de5 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
@@ -24,6 +24,7 @@ import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -55,6 +56,7 @@ import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
@LargeTest
+@Ignore
class KeysBackupTest : InstrumentedTest {
@get:Rule val rule = RetryTestRule(3)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt
index bd0f9d1641..c2e74abc59 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt
@@ -52,6 +52,7 @@ import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Ignore
class SASTest : InstrumentedTest {
@Test
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt
index 265b7c8c4c..3f22906965 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.verification.qrcode
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.amshove.kluent.shouldBe
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -41,6 +42,7 @@ import kotlin.coroutines.resume
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
+@Ignore
class VerificationTest : InstrumentedTest {
data class ExpectedResult(
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt
index 7ed0be927c..59b3b14532 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt
@@ -20,6 +20,7 @@ import androidx.test.filters.LargeTest
import kotlinx.coroutines.runBlocking
import org.amshove.kluent.internal.assertEquals
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -38,6 +39,7 @@ import org.matrix.android.sdk.common.TestConstants
@RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
@LargeTest
+@Ignore
class TimelineSimpleBackPaginationTest : InstrumentedTest {
@Test
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt
index 0d8a9058a2..0cc0ef57c4 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt
@@ -22,6 +22,7 @@ import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -98,6 +99,7 @@ class SpaceCreationTest : InstrumentedTest {
}
@Test
+ @Ignore
fun testJoinSimplePublicSpace() = runSessionTest(context()) { commonTestHelper ->
val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt
index f5670875c2..5b8d2328c7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt
@@ -65,16 +65,14 @@ interface LoginWizard {
* [resetPasswordMailConfirmed] is successfully called.
*
* @param email an email previously associated to the account the user wants the password to be reset.
- * @param newPassword the desired new password
*/
- suspend fun resetPassword(
- email: String,
- newPassword: String
- )
+ suspend fun resetPassword(email: String)
/**
* Confirm the new password, once the user has checked their email
* When this method succeed, tha account password will be effectively modified.
+ *
+ * @param newPassword the desired new password
*/
- suspend fun resetPasswordMailConfirmed()
+ suspend fun resetPasswordMailConfirmed(newPassword: String)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt
index a376ede0d4..9f979098f8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/extensions/Strings.kt
@@ -27,3 +27,8 @@ fun CharSequence.ensurePrefix(prefix: CharSequence): CharSequence {
* Append a new line and then the provided string.
*/
fun StringBuilder.appendNl(str: String) = append("\n").append(str)
+
+/**
+ * Returns null if the string is empty.
+ */
+fun String.ensureNotEmpty() = ifEmpty { null }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
index 0a495f3552..5dfb8961e3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt
@@ -226,12 +226,19 @@ interface RoomService {
): LiveData>
/**
- * TODO Doc.
+ * Get's a live paged list from a filter that can be dynamically updated.
+ *
+ * @param queryParams The filter to use
+ * @param pagedListConfig The paged list configuration (page size, initial load, prefetch distance...)
+ * @param sortOrder defines how to sort the results
+ * @param getFlattenParents When true, the list of known parents and grand parents summaries will be resolved.
+ * This can have significant impact on performance, better be used only on manageable list (filtered by displayName, ..).
*/
fun getFilteredPagedRoomSummariesLive(
queryParams: RoomSummaryQueryParams,
pagedListConfig: PagedList.Config = defaultPagedListConfig,
- sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY
+ sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY,
+ getFlattenParents: Boolean = false,
): UpdatableLivePageResult
/**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt
index 0a189f86e6..20b056f1c7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt
@@ -103,7 +103,7 @@ internal class DefaultLoginWizard(
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
}
- override suspend fun resetPassword(email: String, newPassword: String) {
+ override suspend fun resetPassword(email: String) {
val param = RegisterAddThreePidTask.Params(
RegisterThreePid.Email(email),
pendingSessionData.clientSecret,
@@ -117,18 +117,16 @@ internal class DefaultLoginWizard(
authAPI.resetPassword(AddThreePidRegistrationParams.from(param))
}
- pendingSessionData = pendingSessionData.copy(resetPasswordData = ResetPasswordData(newPassword, result))
+ pendingSessionData = pendingSessionData.copy(resetPasswordData = ResetPasswordData(result))
.also { pendingSessionStore.savePendingSessionData(it) }
}
- override suspend fun resetPasswordMailConfirmed() {
- val safeResetPasswordData = pendingSessionData.resetPasswordData
- ?: throw IllegalStateException("developer error, no reset password in progress")
-
+ override suspend fun resetPasswordMailConfirmed(newPassword: String) {
+ val resetPasswordData = pendingSessionData.resetPasswordData ?: throw IllegalStateException("Developer error - Must call resetPassword first")
val param = ResetPasswordMailConfirmed.create(
pendingSessionData.clientSecret,
- safeResetPasswordData.addThreePidRegistrationResponse.sid,
- safeResetPasswordData.newPassword
+ resetPasswordData.addThreePidRegistrationResponse.sid,
+ newPassword
)
executeRequest(null) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordData.kt
index a65ec38d6d..87a7b346dc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordData.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordData.kt
@@ -24,6 +24,5 @@ import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistration
*/
@JsonClass(generateAdapter = true)
internal data class ResetPasswordData(
- val newPassword: String,
val addThreePidRegistrationResponse: AddThreePidRegistrationResponse
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt
index 9ab6f9d274..cd38b68a85 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt
@@ -16,6 +16,8 @@
package org.matrix.android.sdk.internal.auth.version
+import org.matrix.android.sdk.api.extensions.ensureNotEmpty
+
/**
* Values will take the form "rX.Y.Z".
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-versions
@@ -38,14 +40,14 @@ internal data class HomeServerVersion(
}
companion object {
- internal val pattern = Regex("""[r|v](\d+)\.(\d+)\.(\d+)""")
+ internal val pattern = Regex("""[r|v](\d+)\.(\d+)(?:\.(\d+))?""")
internal fun parse(value: String): HomeServerVersion? {
val result = pattern.matchEntire(value) ?: return null
return HomeServerVersion(
major = result.groupValues[1].toInt(),
minor = result.groupValues[2].toInt(),
- patch = result.groupValues[3].toInt()
+ patch = result.groupValues.getOrNull(index = 3)?.ensureNotEmpty()?.toInt() ?: 0
)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
index 7a7804c354..5e6d052443 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt
@@ -139,9 +139,10 @@ internal class DefaultRoomService @Inject constructor(
override fun getFilteredPagedRoomSummariesLive(
queryParams: RoomSummaryQueryParams,
pagedListConfig: PagedList.Config,
- sortOrder: RoomSortOrder
+ sortOrder: RoomSortOrder,
+ getFlattenParents: Boolean
): UpdatableLivePageResult {
- return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder, getFlattenedParents = true)
+ return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder, getFlattenParents)
}
override fun getRoomCountLive(queryParams: RoomSummaryQueryParams): LiveData {
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersionTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersionTest.kt
new file mode 100644
index 0000000000..413af9c434
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersionTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.matrix.android.sdk.internal.auth.version
+
+import org.amshove.kluent.internal.assertEquals
+import org.junit.Test
+
+class HomeServerVersionTest {
+
+ @Test
+ fun `given a semantic version, when parsing, then converts to home server version`() {
+ val supportedVersions = listOf(
+ case("1.5", expected = aVersion(1, 5, 0)),
+ case("0.5.1", expected = aVersion(0, 5, 1)),
+ case("1.0.0", expected = aVersion(1, 0, 0)),
+ case("1.10.3", expected = aVersion(1, 10, 3)),
+ ).withPrefixes("v", "r")
+
+ val unsupportedVersions = listOf(
+ case("v-1.5.1", expected = null),
+ case("1.4.", expected = null),
+ case("1.5.1.", expected = null),
+ case("r1", expected = null),
+ case("a", expected = null),
+ case("1a.2b.3c", expected = null),
+ case("r", expected = null),
+ )
+
+ (supportedVersions + unsupportedVersions).forEach { (input, expected) ->
+ val result = HomeServerVersion.parse(input)
+
+ assertEquals(expected, result, "Expected $input to be $expected but got $result")
+ }
+ }
+}
+
+private fun aVersion(major: Int, minor: Int, patch: Int) = HomeServerVersion(major, minor, patch)
+private fun case(input: String, expected: HomeServerVersion?) = Case(input, expected)
+
+private fun List.withPrefixes(vararg prefixes: String) = map { case ->
+ prefixes.map { prefix -> case.copy(input = "$prefix${case.input}") }
+}.flatten()
+
+private data class Case(val input: String, val expected: HomeServerVersion?)
diff --git a/vector/build.gradle b/vector/build.gradle
index dea95b607a..751d15122b 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -244,6 +244,7 @@ android {
buildConfigField "boolean", "ENABLE_STRICT_MODE_LOGS", "false"
signingConfig signingConfigs.debug
+ testCoverageEnabled true
}
release {
diff --git a/vector/src/main/java/im/vector/app/features/analytics/extensions/SignUpExt.kt b/vector/src/main/java/im/vector/app/features/analytics/extensions/SignUpExt.kt
new file mode 100644
index 0000000000..e63aafbfc4
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/analytics/extensions/SignUpExt.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.analytics.extensions
+
+import im.vector.app.features.analytics.plan.Signup
+import im.vector.app.features.onboarding.AuthenticationDescription
+
+fun AuthenticationDescription.AuthenticationType.toAnalyticsType() = when (this) {
+ AuthenticationDescription.AuthenticationType.Password -> Signup.AuthenticationType.Password
+ AuthenticationDescription.AuthenticationType.Apple -> Signup.AuthenticationType.Apple
+ AuthenticationDescription.AuthenticationType.Facebook -> Signup.AuthenticationType.Facebook
+ AuthenticationDescription.AuthenticationType.GitHub -> Signup.AuthenticationType.GitHub
+ AuthenticationDescription.AuthenticationType.GitLab -> Signup.AuthenticationType.GitLab
+ AuthenticationDescription.AuthenticationType.Google -> Signup.AuthenticationType.Google
+ AuthenticationDescription.AuthenticationType.SSO -> Signup.AuthenticationType.SSO
+ AuthenticationDescription.AuthenticationType.Other -> Signup.AuthenticationType.Other
+}
diff --git a/vector/src/main/java/im/vector/app/features/crypto/keysrequest/OutboundSessionKeySharingStrategy.kt b/vector/src/main/java/im/vector/app/features/crypto/keysrequest/OutboundSessionKeySharingStrategy.kt
index 2018a5b053..5396ce908e 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/keysrequest/OutboundSessionKeySharingStrategy.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/keysrequest/OutboundSessionKeySharingStrategy.kt
@@ -19,6 +19,7 @@ package im.vector.app.features.crypto.keysrequest
enum class OutboundSessionKeySharingStrategy {
/**
* Keys will be sent for the first time when the first message is sent.
+ * This is handled by the Matrix SDK so there's no need to do it in Vector.
*/
WhenSendingEvent,
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
index af3be0d3d5..244b77eefe 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt
@@ -56,6 +56,7 @@ import im.vector.app.features.matrixto.MatrixToBottomSheet
import im.vector.app.features.matrixto.OriginOfMatrixTo
import im.vector.app.features.navigation.Navigator
import im.vector.app.features.notifications.NotificationDrawerManager
+import im.vector.app.features.onboarding.AuthenticationDescription
import im.vector.app.features.permalink.NavigationInterceptor
import im.vector.app.features.permalink.PermalinkHandler
import im.vector.app.features.permalink.PermalinkHandler.Companion.MATRIX_TO_CUSTOM_SCHEME_URL_BASE
@@ -91,7 +92,7 @@ import javax.inject.Inject
@Parcelize
data class HomeActivityArgs(
val clearNotification: Boolean,
- val accountCreation: Boolean,
+ val authenticationDescription: AuthenticationDescription? = null,
val hasExistingSession: Boolean = false,
val inviteNotificationRoomId: String? = null
) : Parcelable
@@ -612,13 +613,13 @@ class HomeActivity :
fun newIntent(
context: Context,
clearNotification: Boolean = false,
- accountCreation: Boolean = false,
+ authenticationDescription: AuthenticationDescription? = null,
existingSession: Boolean = false,
inviteNotificationRoomId: String? = null
): Intent {
val args = HomeActivityArgs(
clearNotification = clearNotification,
- accountCreation = accountCreation,
+ authenticationDescription = authenticationDescription,
hasExistingSession = existingSession,
inviteNotificationRoomId = inviteNotificationRoomId
)
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
index 2144334790..581e11ac8a 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt
@@ -28,17 +28,25 @@ import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.features.analytics.AnalyticsTracker
+import im.vector.app.features.analytics.extensions.toAnalyticsType
+import im.vector.app.features.analytics.plan.Signup
import im.vector.app.features.analytics.store.AnalyticsStore
import im.vector.app.features.login.ReAuthHelper
+import im.vector.app.features.onboarding.AuthenticationDescription
import im.vector.app.features.raw.wellknown.ElementWellKnown
import im.vector.app.features.raw.wellknown.getElementWellknown
import im.vector.app.features.raw.wellknown.isSecureBackupRequired
+import im.vector.app.features.raw.wellknown.withElementWellKnown
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.UIABaseAuth
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
@@ -72,7 +80,8 @@ class HomeActivityViewModel @AssistedInject constructor(
private val reAuthHelper: ReAuthHelper,
private val analyticsStore: AnalyticsStore,
private val lightweightSettingsStorage: LightweightSettingsStorage,
- private val vectorPreferences: VectorPreferences
+ private val vectorPreferences: VectorPreferences,
+ private val analyticsTracker: AnalyticsTracker
) : VectorViewModel(initialState) {
@AssistedFactory
@@ -84,7 +93,7 @@ class HomeActivityViewModel @AssistedInject constructor(
override fun initialState(viewModelContext: ViewModelContext): HomeActivityViewState? {
val activity: HomeActivity = viewModelContext.activity()
val args: HomeActivityArgs? = activity.intent.getParcelableExtra(Mavericks.KEY_ARG)
- return args?.let { HomeActivityViewState(accountCreation = it.accountCreation) }
+ return args?.let { HomeActivityViewState(authenticationDescription = it.authenticationDescription) }
?: super.initialState(viewModelContext)
}
}
@@ -113,9 +122,32 @@ class HomeActivityViewModel @AssistedInject constructor(
}
}
.launchIn(viewModelScope)
+
+ when (val recentAuthentication = initialState.authenticationDescription) {
+ is AuthenticationDescription.Register -> {
+ viewModelScope.launch {
+ analyticsStore.onUserGaveConsent {
+ analyticsTracker.capture(Signup(authenticationType = recentAuthentication.type.toAnalyticsType()))
+ }
+ }
+ }
+ AuthenticationDescription.Login -> {
+ // do nothing
+ }
+ null -> {
+ // do nothing
+ }
+ }
}
}
+ private suspend fun AnalyticsStore.onUserGaveConsent(action: () -> Unit) {
+ userConsentFlow
+ .takeWhile { !it }
+ .onCompletion { action() }
+ .collect()
+ }
+
private fun cleanupFiles() {
// Mitigation: delete all cached decrypted files each time the application is started.
activeSessionHolder.getSafeActiveSession()?.fileService()?.clearDecryptedCache()
@@ -134,9 +166,8 @@ class HomeActivityViewModel @AssistedInject constructor(
.onEach { info ->
val isVerified = info.getOrNull()?.isTrusted() ?: false
if (!isVerified && onceTrusted) {
- viewModelScope.launch(Dispatchers.IO) {
- val elementWellKnown = rawService.getElementWellknown(safeActiveSession.sessionParams)
- sessionHasBeenUnverified(elementWellKnown)
+ rawService.withElementWellKnown(viewModelScope, safeActiveSession.sessionParams) {
+ sessionHasBeenUnverified(it)
}
}
onceTrusted = isVerified
@@ -285,7 +316,7 @@ class HomeActivityViewModel @AssistedInject constructor(
val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired() ?: false
// In case of account creation, it is already done before
- if (initialState.accountCreation) {
+ if (initialState.authenticationDescription is AuthenticationDescription.Register) {
if (isSecureBackupRequired) {
_viewEvents.post(HomeActivityViewEvents.StartRecoverySetupFlow)
} else {
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt
index 45fe04fc61..95ab75549f 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewState.kt
@@ -17,9 +17,10 @@
package im.vector.app.features.home
import com.airbnb.mvrx.MavericksState
+import im.vector.app.features.onboarding.AuthenticationDescription
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
data class HomeActivityViewState(
val syncStatusServiceStatus: SyncStatusService.Status = SyncStatusService.Status.Idle,
- val accountCreation: Boolean = false
+ val authenticationDescription: AuthenticationDescription? = null
) : MavericksState
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
index 1b630801cc..2a6357e088 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
@@ -29,7 +29,6 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import im.vector.app.AppStateHandler
-import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
@@ -56,6 +55,8 @@ import im.vector.app.features.home.room.typing.TypingHelper
import im.vector.app.features.location.LocationSharingServiceConnection
import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
+import im.vector.app.features.raw.wellknown.getOutboundSessionKeySharingStrategyOrDefault
+import im.vector.app.features.raw.wellknown.withElementWellKnown
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.settings.VectorPreferences
@@ -76,6 +77,7 @@ import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.query.QueryStringValue
+import org.matrix.android.sdk.api.raw.RawService
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.EventType
@@ -118,6 +120,7 @@ class TimelineViewModel @AssistedInject constructor(
private val vectorDataStore: VectorDataStore,
private val stringProvider: StringProvider,
private val session: Session,
+ private val rawService: RawService,
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
private val stickerPickerActionHandler: StickerPickerActionHandler,
private val typingHelper: TypingHelper,
@@ -196,8 +199,13 @@ class TimelineViewModel @AssistedInject constructor(
chatEffectManager.delegate = this
// Ensure to share the outbound session keys with all members
- if (OutboundSessionKeySharingStrategy.WhenEnteringRoom == BuildConfig.outboundSessionKeySharingStrategy && room.roomCryptoService().isEncrypted()) {
- prepareForEncryption()
+ if (room.roomCryptoService().isEncrypted()) {
+ rawService.withElementWellKnown(viewModelScope, session.sessionParams) {
+ val strategy = it.getOutboundSessionKeySharingStrategyOrDefault()
+ if (strategy == OutboundSessionKeySharingStrategy.WhenEnteringRoom) {
+ prepareForEncryption()
+ }
+ }
}
// If the user had already accepted the invitation in the room list
@@ -667,10 +675,13 @@ class TimelineViewModel @AssistedInject constructor(
private fun handleComposerFocusChange(action: RoomDetailAction.ComposerFocusChange) {
// Ensure outbound session keys
- if (OutboundSessionKeySharingStrategy.WhenTyping == BuildConfig.outboundSessionKeySharingStrategy && room.roomCryptoService().isEncrypted()) {
- if (action.focused) {
- // Should we add some rate limit here, or do it only once per model lifecycle?
- prepareForEncryption()
+ if (room.roomCryptoService().isEncrypted()) {
+ rawService.withElementWellKnown(viewModelScope, session.sessionParams) {
+ val strategy = it.getOutboundSessionKeySharingStrategyOrDefault()
+ if (strategy == OutboundSessionKeySharingStrategy.WhenTyping && action.focused) {
+ // Should we add some rate limit here, or do it only once per model lifecycle?
+ prepareForEncryption()
+ }
}
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt
index 8f7ab46191..a8a30349c7 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt
@@ -71,7 +71,7 @@ class RoomListSectionBuilderGroup(
},
{ qpm ->
val name = stringProvider.getString(R.string.bottom_action_rooms)
- val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(qpm)
+ val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(qpm, getFlattenParents = true)
onUpdatable(updatableFilterLivePageResult)
val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt
index a39da8f473..171b05637a 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt
@@ -332,7 +332,7 @@ class RoomListSectionBuilderSpace(
},
{ queryParams ->
val name = stringProvider.getString(R.string.bottom_action_rooms)
- val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(queryParams)
+ val updatableFilterLivePageResult = session.roomService().getFilteredPagedRoomSummariesLive(queryParams, getFlattenParents = true)
onUpdatable(updatableFilterLivePageResult)
val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt
index 0023a1249e..f50cec5149 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt
@@ -207,7 +207,7 @@ class RoomSummaryItemFactory @Inject constructor(
private fun getSearchResultSubtitle(roomSummary: RoomSummary): String {
val userId = roomSummary.directUserId
- val spaceName = roomSummary.spaceParents?.firstOrNull()?.roomSummary?.name
+ val spaceName = roomSummary.flattenParents.lastOrNull()?.name
val canonicalAlias = roomSummary.canonicalAlias
return (userId ?: spaceName ?: canonicalAlias).orEmpty()
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
index 15e5efe54c..722133f585 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginActivity.kt
@@ -42,6 +42,7 @@ import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.home.HomeActivity
import im.vector.app.features.login.terms.LoginTermsFragment
import im.vector.app.features.login.terms.LoginTermsFragmentArgument
+import im.vector.app.features.onboarding.AuthenticationDescription
import im.vector.app.features.pin.UnlockedActivity
import org.matrix.android.sdk.api.auth.registration.FlowResult
import org.matrix.android.sdk.api.auth.registration.Stage
@@ -218,10 +219,8 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA
// change the screen name
analyticsScreenName = MobileScreen.ScreenName.Register
}
- val intent = HomeActivity.newIntent(
- this,
- accountCreation = loginViewState.signMode == SignMode.SignUp
- )
+ val authDescription = inferAuthDescription(loginViewState)
+ val intent = HomeActivity.newIntent(this, authenticationDescription = authDescription)
startActivity(intent)
finish()
return
@@ -231,6 +230,13 @@ open class LoginActivity : VectorBaseActivity(), UnlockedA
views.loginLoading.isVisible = loginViewState.isLoading()
}
+ private fun inferAuthDescription(loginViewState: LoginViewState) = when (loginViewState.signMode) {
+ SignMode.Unknown -> null
+ SignMode.SignUp -> AuthenticationDescription.Register(type = AuthenticationDescription.AuthenticationType.Other)
+ SignMode.SignIn -> AuthenticationDescription.Login
+ SignMode.SignInWithMatrixId -> AuthenticationDescription.Login
+ }
+
private fun onWebLoginError(onWebLoginError: LoginViewEvents.OnWebLoginError) {
// Pop the backstack
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
diff --git a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt
index 1d25732ee4..9c598c400b 100644
--- a/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/login/LoginFragment.kt
@@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
+import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.failure.isInvalidPassword
@@ -202,11 +203,11 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment?, mode: SocialLoginButtonsView.Mode, listener: (String?) -> Unit) {
+fun SocialLoginButtonsView.render(ssoProviders: List?, mode: SocialLoginButtonsView.Mode, listener: (SsoIdentityProvider?) -> Unit) {
this.mode = mode
this.ssoIdentityProviders = ssoProviders?.sorted()
this.listener = SocialLoginButtonsView.InteractionListener { listener(it) }
diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt
index f9917a4c31..a7c4b25344 100644
--- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt
+++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentSignupUsername2.kt
@@ -35,6 +35,7 @@ import im.vector.app.features.login.SocialLoginButtonsView
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
+import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import reactivecircus.flowbinding.android.widget.textChanges
import javax.inject.Inject
@@ -96,11 +97,11 @@ class LoginFragmentSignupUsername2 @Inject constructor() : AbstractSSOLoginFragm
views.loginSocialLoginContainer.isVisible = true
views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted()
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
- override fun onProviderSelected(id: String?) {
+ override fun onProviderSelected(provider: SsoIdentityProvider?) {
loginViewModel.getSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
- providerId = id
+ providerId = provider?.id
)
?.let { openInCustomTab(it) }
}
diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt
index 064889876b..cc143b9255 100644
--- a/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt
+++ b/vector/src/main/java/im/vector/app/features/login2/LoginFragmentToAny2.kt
@@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
+import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.failure.isInvalidPassword
@@ -123,11 +124,11 @@ class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2 {
setState {
copy(
- resetPasswordEmail = null
+ resetPasswordEmail = null,
+ resetPasswordNewPassword = null
)
}
}
@@ -443,7 +444,7 @@ class LoginViewModel2 @AssistedInject constructor(
currentJob = viewModelScope.launch {
try {
- safeLoginWizard.resetPassword(action.email, action.newPassword)
+ safeLoginWizard.resetPassword(action.email)
} catch (failure: Throwable) {
_viewEvents.post(LoginViewEvents2.Failure(failure))
setState { copy(isLoading = false) }
@@ -453,7 +454,8 @@ class LoginViewModel2 @AssistedInject constructor(
setState {
copy(
isLoading = false,
- resetPasswordEmail = action.email
+ resetPasswordEmail = action.email,
+ resetPasswordNewPassword = action.newPassword
)
}
@@ -472,7 +474,8 @@ class LoginViewModel2 @AssistedInject constructor(
currentJob = viewModelScope.launch {
try {
- safeLoginWizard.resetPasswordMailConfirmed()
+ val state = awaitState()
+ safeLoginWizard.resetPasswordMailConfirmed(state.resetPasswordNewPassword!!)
} catch (failure: Throwable) {
_viewEvents.post(LoginViewEvents2.Failure(failure))
setState { copy(isLoading = false) }
@@ -481,7 +484,8 @@ class LoginViewModel2 @AssistedInject constructor(
setState {
copy(
isLoading = false,
- resetPasswordEmail = null
+ resetPasswordEmail = null,
+ resetPasswordNewPassword = null
)
}
diff --git a/vector/src/main/java/im/vector/app/features/login2/LoginViewState2.kt b/vector/src/main/java/im/vector/app/features/login2/LoginViewState2.kt
index 276d1111bb..8405381c4f 100644
--- a/vector/src/main/java/im/vector/app/features/login2/LoginViewState2.kt
+++ b/vector/src/main/java/im/vector/app/features/login2/LoginViewState2.kt
@@ -36,6 +36,8 @@ data class LoginViewState2(
@PersistState
val resetPasswordEmail: String? = null,
@PersistState
+ val resetPasswordNewPassword: String? = null,
+ @PersistState
val homeServerUrlFromUser: String? = null,
// Can be modified after a Wellknown request
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/AuthenticationDescription.kt b/vector/src/main/java/im/vector/app/features/onboarding/AuthenticationDescription.kt
new file mode 100644
index 0000000000..3672b8eef3
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/onboarding/AuthenticationDescription.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.onboarding
+
+import android.os.Parcelable
+import im.vector.app.features.onboarding.AuthenticationDescription.AuthenticationType
+import kotlinx.parcelize.Parcelize
+import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
+
+sealed interface AuthenticationDescription : Parcelable {
+ @Parcelize
+ object Login : AuthenticationDescription
+
+ @Parcelize
+ data class Register(val type: AuthenticationType) : AuthenticationDescription
+
+ enum class AuthenticationType {
+ Password,
+ Apple,
+ Facebook,
+ GitHub,
+ GitLab,
+ Google,
+ SSO,
+ Other
+ }
+}
+
+fun SsoIdentityProvider?.toAuthenticationType() = when (this?.brand) {
+ SsoIdentityProvider.BRAND_GOOGLE -> AuthenticationType.Google
+ SsoIdentityProvider.BRAND_GITHUB -> AuthenticationType.GitHub
+ SsoIdentityProvider.BRAND_APPLE -> AuthenticationType.Apple
+ SsoIdentityProvider.BRAND_FACEBOOK -> AuthenticationType.Facebook
+ SsoIdentityProvider.BRAND_GITLAB -> AuthenticationType.GitLab
+ SsoIdentityProvider.BRAND_TWITTER -> AuthenticationType.SSO
+ null -> AuthenticationType.SSO
+ else -> AuthenticationType.SSO
+}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt
index b6013b70b7..0d7c83e360 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/Login2Variant.kt
@@ -276,7 +276,7 @@ class Login2Variant(
is LoginViewEvents2.OnLoginModeNotSupported ->
onLoginModeNotSupported(event.supportedTypes)
is LoginViewEvents2.OnSessionCreated -> handleOnSessionCreated(event)
- is LoginViewEvents2.Finish -> terminate(true)
+ is LoginViewEvents2.Finish -> terminate()
is LoginViewEvents2.CancelRegistration -> handleCancelRegistration()
}
}
@@ -296,14 +296,13 @@ class Login2Variant(
option = commonOption
)
} else {
- terminate(false)
+ terminate()
}
}
- private fun terminate(newAccount: Boolean) {
+ private fun terminate() {
val intent = HomeActivity.newIntent(
- activity,
- accountCreation = newAccount
+ activity
)
activity.startActivity(intent)
activity.finish()
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
index 6e59ef03f5..61877a5f47 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
@@ -54,6 +54,7 @@ import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
+import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import org.matrix.android.sdk.api.auth.login.LoginWizard
import org.matrix.android.sdk.api.auth.registration.FlowResult
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
@@ -127,7 +128,7 @@ class OnboardingViewModel @AssistedInject constructor(
val isRegistrationStarted: Boolean
get() = authenticationService.isRegistrationStarted()
- private val loginWizard: LoginWizard?
+ private val loginWizard: LoginWizard
get() = authenticationService.getLoginWizard()
private var loginConfig: LoginConfig? = null
@@ -245,21 +246,15 @@ class OnboardingViewModel @AssistedInject constructor(
private fun handleLoginWithToken(action: OnboardingAction.LoginWithToken) {
val safeLoginWizard = loginWizard
+ setState { copy(isLoading = true) }
- if (safeLoginWizard == null) {
- setState { copy(isLoading = false) }
- _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration")))
- } else {
- setState { copy(isLoading = true) }
-
- currentJob = viewModelScope.launch {
- try {
- val result = safeLoginWizard.loginWithToken(action.loginToken)
- onSessionCreated(result, isAccountCreated = false)
- } catch (failure: Throwable) {
- setState { copy(isLoading = false) }
- _viewEvents.post(OnboardingViewEvents.Failure(failure))
- }
+ currentJob = viewModelScope.launch {
+ try {
+ val result = safeLoginWizard.loginWithToken(action.loginToken)
+ onSessionCreated(result, authenticationDescription = AuthenticationDescription.Login)
+ } catch (failure: Throwable) {
+ setState { copy(isLoading = false) }
+ _viewEvents.post(OnboardingViewEvents.Failure(failure))
}
}
}
@@ -289,7 +284,11 @@ class OnboardingViewModel @AssistedInject constructor(
// do nothing
}
else -> when (it) {
- is RegistrationResult.Complete -> onSessionCreated(it.session, isAccountCreated = true)
+ is RegistrationResult.Complete -> onSessionCreated(
+ it.session,
+ authenticationDescription = awaitState().selectedAuthenticationState.description
+ ?: AuthenticationDescription.Register(AuthenticationDescription.AuthenticationType.Other)
+ )
is RegistrationResult.NextStep -> onFlowResponse(it.flowResult, onNextRegistrationStepAction)
is RegistrationResult.SendEmailSuccess -> _viewEvents.post(OnboardingViewEvents.OnSendEmailSuccess(it.email))
is RegistrationResult.Error -> _viewEvents.post(OnboardingViewEvents.Failure(it.cause))
@@ -319,6 +318,10 @@ class OnboardingViewModel @AssistedInject constructor(
private fun OnboardingViewState.hasSelectedMatrixOrg() = selectedHomeserver.userFacingUrl == matrixOrgUrl
private fun handleRegisterWith(action: AuthenticateAction.Register) {
+ setState {
+ val authDescription = AuthenticationDescription.Register(AuthenticationDescription.AuthenticationType.Password)
+ copy(selectedAuthenticationState = SelectedAuthenticationState(authDescription))
+ }
reAuthHelper.data = action.password
handleRegisterAction(
RegisterAction.CreateAccount(
@@ -368,7 +371,7 @@ class OnboardingViewModel @AssistedInject constructor(
setState {
copy(
isLoading = false,
- resetPasswordEmail = null
+ resetState = ResetState()
)
}
}
@@ -438,59 +441,52 @@ class OnboardingViewModel @AssistedInject constructor(
private fun handleResetPassword(action: OnboardingAction.ResetPassword) {
val safeLoginWizard = loginWizard
-
- if (safeLoginWizard == null) {
- setState { copy(isLoading = false) }
- _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration")))
- } else {
- setState { copy(isLoading = true) }
-
- currentJob = viewModelScope.launch {
- try {
- safeLoginWizard.resetPassword(action.email, action.newPassword)
- } catch (failure: Throwable) {
- setState { copy(isLoading = false) }
- _viewEvents.post(OnboardingViewEvents.Failure(failure))
- return@launch
- }
-
- setState {
- copy(
- isLoading = false,
- resetPasswordEmail = action.email
- )
- }
-
- _viewEvents.post(OnboardingViewEvents.OnResetPasswordSendThreePidDone)
- }
+ setState { copy(isLoading = true) }
+ currentJob = viewModelScope.launch {
+ runCatching { safeLoginWizard.resetPassword(action.email) }.fold(
+ onSuccess = {
+ setState {
+ copy(
+ isLoading = false,
+ resetState = ResetState(email = action.email, newPassword = action.newPassword)
+ )
+ }
+ _viewEvents.post(OnboardingViewEvents.OnResetPasswordSendThreePidDone)
+ },
+ onFailure = {
+ setState { copy(isLoading = false) }
+ _viewEvents.post(OnboardingViewEvents.Failure(it))
+ }
+ )
}
}
private fun handleResetPasswordMailConfirmed() {
- val safeLoginWizard = loginWizard
-
- if (safeLoginWizard == null) {
- setState { copy(isLoading = false) }
- _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration")))
- } else {
- setState { copy(isLoading = false) }
-
- currentJob = viewModelScope.launch {
- try {
- safeLoginWizard.resetPasswordMailConfirmed()
- } catch (failure: Throwable) {
+ setState { copy(isLoading = true) }
+ currentJob = viewModelScope.launch {
+ val resetState = awaitState().resetState
+ when (val newPassword = resetState.newPassword) {
+ null -> {
setState { copy(isLoading = false) }
- _viewEvents.post(OnboardingViewEvents.Failure(failure))
- return@launch
+ _viewEvents.post(OnboardingViewEvents.Failure(IllegalStateException("Developer error - No new password has been set")))
}
- setState {
- copy(
- isLoading = false,
- resetPasswordEmail = null
+ else -> {
+ runCatching { loginWizard.resetPasswordMailConfirmed(newPassword) }.fold(
+ onSuccess = {
+ setState {
+ copy(
+ isLoading = false,
+ resetState = ResetState()
+ )
+ }
+ _viewEvents.post(OnboardingViewEvents.OnResetPasswordMailConfirmationSuccess)
+ },
+ onFailure = {
+ setState { copy(isLoading = false) }
+ _viewEvents.post(OnboardingViewEvents.Failure(it))
+ }
)
}
-
- _viewEvents.post(OnboardingViewEvents.OnResetPasswordMailConfirmationSuccess)
}
}
}
@@ -499,7 +495,7 @@ class OnboardingViewModel @AssistedInject constructor(
setState { copy(isLoading = true) }
currentJob = viewModelScope.launch {
directLoginUseCase.execute(action, homeServerConnectionConfig).fold(
- onSuccess = { onSessionCreated(it, isAccountCreated = false) },
+ onSuccess = { onSessionCreated(it, authenticationDescription = AuthenticationDescription.Login) },
onFailure = {
setState { copy(isLoading = false) }
_viewEvents.post(OnboardingViewEvents.Failure(it))
@@ -510,25 +506,19 @@ class OnboardingViewModel @AssistedInject constructor(
private fun handleLogin(action: AuthenticateAction.Login) {
val safeLoginWizard = loginWizard
-
- if (safeLoginWizard == null) {
- setState { copy(isLoading = false) }
- _viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration")))
- } else {
- setState { copy(isLoading = true) }
- currentJob = viewModelScope.launch {
- try {
- val result = safeLoginWizard.login(
- action.username,
- action.password,
- action.initialDeviceName
- )
- reAuthHelper.data = action.password
- onSessionCreated(result, isAccountCreated = false)
- } catch (failure: Throwable) {
- setState { copy(isLoading = false) }
- _viewEvents.post(OnboardingViewEvents.Failure(failure))
- }
+ setState { copy(isLoading = true) }
+ currentJob = viewModelScope.launch {
+ try {
+ val result = safeLoginWizard.login(
+ action.username,
+ action.password,
+ action.initialDeviceName
+ )
+ reAuthHelper.data = action.password
+ onSessionCreated(result, authenticationDescription = AuthenticationDescription.Login)
+ } catch (failure: Throwable) {
+ setState { copy(isLoading = false) }
+ _viewEvents.post(OnboardingViewEvents.Failure(failure))
}
}
}
@@ -553,7 +543,7 @@ class OnboardingViewModel @AssistedInject constructor(
internalRegisterAction(RegisterAction.RegisterDummy, onNextRegistrationStepAction)
}
- private suspend fun onSessionCreated(session: Session, isAccountCreated: Boolean) {
+ private suspend fun onSessionCreated(session: Session, authenticationDescription: AuthenticationDescription) {
val state = awaitState()
state.useCase?.let { useCase ->
session.vectorStore(applicationContext).setUseCase(useCase)
@@ -564,15 +554,15 @@ class OnboardingViewModel @AssistedInject constructor(
authenticationService.reset()
session.configureAndStart(applicationContext)
- when (isAccountCreated) {
- true -> {
+ when (authenticationDescription) {
+ is AuthenticationDescription.Register -> {
val personalizationState = createPersonalizationState(session, state)
setState {
copy(isLoading = false, personalizationState = personalizationState)
}
_viewEvents.post(OnboardingViewEvents.OnAccountCreated)
}
- false -> {
+ AuthenticationDescription.Login -> {
setState { copy(isLoading = false) }
_viewEvents.post(OnboardingViewEvents.OnAccountSignedIn)
}
@@ -603,7 +593,7 @@ class OnboardingViewModel @AssistedInject constructor(
currentJob = viewModelScope.launch {
try {
val result = authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials)
- onSessionCreated(result, isAccountCreated = false)
+ onSessionCreated(result, authenticationDescription = AuthenticationDescription.Login)
} catch (failure: Throwable) {
setState { copy(isLoading = false) }
}
@@ -745,8 +735,12 @@ class OnboardingViewModel @AssistedInject constructor(
return loginConfig?.homeServerUrl
}
- fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? {
- return authenticationService.getSsoUrl(redirectUrl, deviceId, providerId)
+ fun fetchSsoUrl(redirectUrl: String, deviceId: String?, provider: SsoIdentityProvider?): String? {
+ setState {
+ val authDescription = AuthenticationDescription.Register(provider.toAuthenticationType())
+ copy(selectedAuthenticationState = SelectedAuthenticationState(authDescription))
+ }
+ return authenticationService.getSsoUrl(redirectUrl, deviceId, provider?.id)
}
fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String? {
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt
index 442a0a7df1..268b1e7d49 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewState.kt
@@ -39,7 +39,7 @@ data class OnboardingViewState(
@PersistState
val signMode: SignMode = SignMode.Unknown,
@PersistState
- val resetPasswordEmail: String? = null,
+ val resetState: ResetState = ResetState(),
// For SSO session recovery
@PersistState
@@ -51,6 +51,9 @@ data class OnboardingViewState(
@PersistState
val selectedHomeserver: SelectedHomeserverState = SelectedHomeserverState(),
+ @PersistState
+ val selectedAuthenticationState: SelectedAuthenticationState = SelectedAuthenticationState(),
+
@PersistState
val personalizationState: PersonalizationState = PersonalizationState()
) : MavericksState
@@ -80,3 +83,14 @@ data class PersonalizationState(
fun supportsPersonalization() = supportsChangingDisplayName || supportsChangingProfilePicture
}
+
+@Parcelize
+data class ResetState(
+ val email: String? = null,
+ val newPassword: String? = null,
+) : Parcelable
+
+@Parcelize
+data class SelectedAuthenticationState(
+ val description: AuthenticationDescription? = null,
+) : Parcelable
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt
index cfd860c526..7766523de9 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractFtueAuthFragment.kt
@@ -153,7 +153,7 @@ abstract class AbstractFtueAuthFragment : VectorBaseFragment
// True when email is sent with success to the homeserver
- isResetPasswordStarted = state.resetPasswordEmail.isNullOrBlank().not()
+ isResetPasswordStarted = state.resetState.email.isNullOrBlank().not()
updateWithState(state)
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt
index a032181e4d..1b764f4ce6 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/AbstractSSOFtueAuthFragment.kt
@@ -90,10 +90,10 @@ abstract class AbstractSSOFtueAuthFragment : AbstractFtueAuthF
withState(viewModel) { state ->
if (state.selectedHomeserver.preferredLoginMode.hasSso() && state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders().isNullOrEmpty()) {
// in this case we can prefetch (not other cases for privacy concerns)
- viewModel.getSsoUrl(
+ viewModel.fetchSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
- providerId = null
+ provider = null
)
?.let { prefetchUrl(it) }
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt
index 91a83b432a..10b9cf4683 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt
@@ -131,10 +131,10 @@ class FtueAuthCombinedLoginFragment @Inject constructor(
views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true
views.ssoButtonsHeader.isVisible = views.ssoGroup.isVisible && views.loginEntryGroup.isVisible
views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id ->
- viewModel.getSsoUrl(
+ viewModel.fetchSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = deviceId,
- providerId = id
+ provider = id
)?.let { openInCustomTab(it) }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt
index ef68fd619e..e19f7837c3 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt
@@ -164,11 +164,11 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
private fun renderSsoProviders(deviceId: String?, ssoProviders: List?) {
views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true
- views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id ->
- viewModel.getSsoUrl(
+ views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { provider ->
+ viewModel.fetchSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = deviceId,
- providerId = id
+ provider = provider
)?.let { openInCustomTab(it) }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt
index c9383a2df7..9f551f9f25 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt
@@ -45,6 +45,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
+import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import org.matrix.android.sdk.api.failure.isInvalidPassword
import org.matrix.android.sdk.api.failure.isInvalidUsername
import org.matrix.android.sdk.api.failure.isLoginEmailUnknown
@@ -216,11 +217,11 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
views.loginSocialLoginContainer.isVisible = true
views.loginSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders?.sorted()
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
- override fun onProviderSelected(id: String?) {
- viewModel.getSsoUrl(
+ override fun onProviderSelected(provider: SsoIdentityProvider?) {
+ viewModel.fetchSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
- providerId = id
+ provider = provider
)
?.let { openInCustomTab(it) }
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt
index fd7f14b1cc..76fbae6f40 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthResetPasswordMailConfirmationFragment.kt
@@ -44,7 +44,7 @@ class FtueAuthResetPasswordMailConfirmationFragment @Inject constructor() : Abst
}
private fun setupUi(state: OnboardingViewState) {
- views.resetPasswordMailConfirmationNotice.text = getString(R.string.login_reset_password_mail_confirmation_notice, state.resetPasswordEmail)
+ views.resetPasswordMailConfirmationNotice.text = getString(R.string.login_reset_password_mail_confirmation_notice, state.resetState.email)
}
private fun submit() {
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt
index 902065ef86..6723e48bcc 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSignUpSignInSelectionFragment.kt
@@ -34,6 +34,7 @@ import im.vector.app.features.login.SocialLoginButtonsView
import im.vector.app.features.login.ssoIdentityProviders
import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.OnboardingViewState
+import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import javax.inject.Inject
/**
@@ -81,11 +82,11 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF
views.loginSignupSigninSignInSocialLoginContainer.isVisible = true
views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders()?.sorted()
views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
- override fun onProviderSelected(id: String?) {
- viewModel.getSsoUrl(
+ override fun onProviderSelected(provider: SsoIdentityProvider?) {
+ viewModel.fetchSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
- providerId = id
+ provider = provider
)
?.let { openInCustomTab(it) }
}
@@ -123,10 +124,10 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF
private fun submit() = withState(viewModel) { state ->
if (state.selectedHomeserver.preferredLoginMode is LoginMode.Sso) {
- viewModel.getSsoUrl(
+ viewModel.fetchSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
- providerId = null
+ provider = null
)
?.let { openInCustomTab(it) }
} else {
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt
index e8c478b7ff..f8ad700b40 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt
@@ -216,7 +216,7 @@ class FtueAuthVariant(
is OnboardingViewEvents.OnAccountCreated -> onAccountCreated()
OnboardingViewEvents.OnAccountSignedIn -> onAccountSignedIn()
OnboardingViewEvents.OnChooseDisplayName -> onChooseDisplayName()
- OnboardingViewEvents.OnTakeMeHome -> navigateToHome(createdAccount = true)
+ OnboardingViewEvents.OnTakeMeHome -> navigateToHome()
OnboardingViewEvents.OnChooseProfilePicture -> onChooseProfilePicture()
OnboardingViewEvents.OnPersonalizationComplete -> onPersonalizationComplete()
OnboardingViewEvents.OnBack -> activity.popBackstack()
@@ -467,7 +467,7 @@ class FtueAuthVariant(
}
private fun onAccountSignedIn() {
- navigateToHome(createdAccount = false)
+ navigateToHome()
}
private fun onAccountCreated() {
@@ -479,10 +479,12 @@ class FtueAuthVariant(
)
}
- private fun navigateToHome(createdAccount: Boolean) {
- val intent = HomeActivity.newIntent(activity, accountCreation = createdAccount)
- activity.startActivity(intent)
- activity.finish()
+ private fun navigateToHome() {
+ withState(onboardingViewModel) {
+ val intent = HomeActivity.newIntent(activity, authenticationDescription = it.selectedAuthenticationState.description)
+ activity.startActivity(intent)
+ activity.finish()
+ }
}
private fun onChooseDisplayName() {
diff --git a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt
index 3c4d514e47..0df5f0e9cf 100644
--- a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt
+++ b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnown.kt
@@ -65,7 +65,14 @@ data class E2EWellKnownConfig(
* clients should fallback to the default value of: ["key", "passphrase"].
*/
@Json(name = "secure_backup_setup_methods")
- val secureBackupSetupMethods: List? = null
+ val secureBackupSetupMethods: List? = null,
+
+ /**
+ * Configuration for sharing keys strategy which should be used instead of [im.vector.app.BuildConfig.outboundSessionKeySharingStrategy].
+ * One of on_room_opening, on_typing or disabled.
+ */
+ @Json(name = "outbound_keys_pre_sharing_mode")
+ val outboundsKeyPreSharingMode: String? = null,
)
@JsonClass(generateAdapter = true)
diff --git a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt
index fce91b8f15..73662613f7 100644
--- a/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt
+++ b/vector/src/main/java/im/vector/app/features/raw/wellknown/ElementWellKnownExt.kt
@@ -16,6 +16,11 @@
package im.vector.app.features.raw.wellknown
+import im.vector.app.BuildConfig
+import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixPatterns.getServerName
import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.extensions.tryOrNull
@@ -30,6 +35,25 @@ suspend fun RawService.getElementWellknown(sessionParams: SessionParams): Elemen
fun ElementWellKnown.isE2EByDefault() = elementE2E?.e2eDefault ?: riotE2E?.e2eDefault ?: true
+fun ElementWellKnown?.getOutboundSessionKeySharingStrategyOrDefault(): OutboundSessionKeySharingStrategy {
+ return when (this?.elementE2E?.outboundsKeyPreSharingMode) {
+ "on_room_opening" -> OutboundSessionKeySharingStrategy.WhenEnteringRoom
+ "on_typing" -> OutboundSessionKeySharingStrategy.WhenTyping
+ "disabled" -> OutboundSessionKeySharingStrategy.WhenSendingEvent
+ else -> BuildConfig.outboundSessionKeySharingStrategy
+ }
+}
+
+fun RawService.withElementWellKnown(
+ coroutineScope: CoroutineScope,
+ sessionParams: SessionParams,
+ block: ((ElementWellKnown?) -> Unit)
+) = with(coroutineScope) {
+ launch(Dispatchers.IO) {
+ block(getElementWellknown(sessionParams))
+ }
+}
+
fun ElementWellKnown.isSecureBackupRequired() = elementE2E?.secureBackupRequired
?: riotE2E?.secureBackupRequired
?: false
diff --git a/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
index 9fcf1e6321..0af0488090 100644
--- a/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
+++ b/vector/src/main/java/im/vector/app/features/voice/AudioWaveformView.kt
@@ -151,12 +151,14 @@ class AudioWaveformView @JvmOverloads constructor(
private fun handleNewFftList(fftList: List) {
val maxVisibleBarCount = getMaxVisibleBarCount()
+
fftList.forEach { fft ->
rawFftList.add(fft)
val barHeight = max(fft.value / MAX_FFT * (height - verticalPadding * 2), barMinHeight)
visibleBarHeights.add(FFT(barHeight, fft.color))
+
if (visibleBarHeights.size > maxVisibleBarCount) {
- visibleBarHeights = visibleBarHeights.subList(visibleBarHeights.size - maxVisibleBarCount, visibleBarHeights.size)
+ visibleBarHeights = visibleBarHeights.takeLast(maxVisibleBarCount).toMutableList()
}
}
}
diff --git a/vector/src/main/res/layout/bottom_sheet_bootstrap.xml b/vector/src/main/res/layout/bottom_sheet_bootstrap.xml
index 3818e50566..be209c2dd4 100644
--- a/vector/src/main/res/layout/bottom_sheet_bootstrap.xml
+++ b/vector/src/main/res/layout/bottom_sheet_bootstrap.xml
@@ -37,6 +37,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
+ android:layout_marginTop="16dp"
android:ellipsize="end"
android:textColor="?vctr_content_primary"
android:textStyle="bold"
diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
index 1abfa7e9a8..77539da232 100644
--- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
+++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
@@ -31,6 +31,7 @@ import im.vector.app.test.fakes.FakeContext
import im.vector.app.test.fakes.FakeDirectLoginUseCase
import im.vector.app.test.fakes.FakeHomeServerConnectionConfigFactory
import im.vector.app.test.fakes.FakeHomeServerHistoryService
+import im.vector.app.test.fakes.FakeLoginWizard
import im.vector.app.test.fakes.FakeRegisterActionHandler
import im.vector.app.test.fakes.FakeRegistrationWizard
import im.vector.app.test.fakes.FakeSession
@@ -67,6 +68,8 @@ private val A_DIRECT_LOGIN = OnboardingAction.AuthenticateAction.LoginDirect("@a
private const val A_HOMESERVER_URL = "https://edited-homeserver.org"
private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(FakeUri().instance)
private val SELECTED_HOMESERVER_STATE = SelectedHomeserverState(preferredLoginMode = LoginMode.Password)
+private const val AN_EMAIL = "hello@example.com"
+private const val A_PASSWORD = "a-password"
class OnboardingViewModelTest {
@@ -85,6 +88,7 @@ class OnboardingViewModelTest {
private val fakeHomeServerConnectionConfigFactory = FakeHomeServerConnectionConfigFactory()
private val fakeStartAuthenticationFlowUseCase = FakeStartAuthenticationFlowUseCase()
private val fakeHomeServerHistoryService = FakeHomeServerHistoryService()
+ private val fakeLoginWizard = FakeLoginWizard()
private var initialState = OnboardingViewState()
private lateinit var viewModel: OnboardingViewModel
@@ -466,6 +470,43 @@ class OnboardingViewModelTest {
.finish()
}
+ @Test
+ fun `given can successfully reset password, when resetting password, then emits reset done event`() = runTest {
+ val test = viewModel.test()
+ fakeLoginWizard.givenResetPasswordSuccess(AN_EMAIL)
+ fakeAuthenticationService.givenLoginWizard(fakeLoginWizard)
+
+ viewModel.handle(OnboardingAction.ResetPassword(email = AN_EMAIL, newPassword = A_PASSWORD))
+
+ test
+ .assertStatesChanges(
+ initialState,
+ { copy(isLoading = true) },
+ { copy(isLoading = false, resetState = ResetState(AN_EMAIL, A_PASSWORD)) }
+ )
+ .assertEvents(OnboardingViewEvents.OnResetPasswordSendThreePidDone)
+ .finish()
+ }
+
+ @Test
+ fun `given can successfully confirm reset password, when confirm reset password, then emits reset success`() = runTest {
+ viewModelWith(initialState.copy(resetState = ResetState(AN_EMAIL, A_PASSWORD)))
+ val test = viewModel.test()
+ fakeLoginWizard.givenConfirmResetPasswordSuccess(A_PASSWORD)
+ fakeAuthenticationService.givenLoginWizard(fakeLoginWizard)
+
+ viewModel.handle(OnboardingAction.ResetPasswordMailConfirmed)
+
+ test
+ .assertStatesChanges(
+ initialState,
+ { copy(isLoading = true) },
+ { copy(isLoading = false, resetState = ResetState()) }
+ )
+ .assertEvents(OnboardingViewEvents.OnResetPasswordMailConfirmationSuccess)
+ .finish()
+ }
+
private fun viewModelWith(state: OnboardingViewState) {
OnboardingViewModel(
state,
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt
index 0456bbd474..cc606497f5 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeAuthenticationService.kt
@@ -23,6 +23,7 @@ import io.mockk.mockk
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
+import org.matrix.android.sdk.api.auth.login.LoginWizard
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
@@ -36,6 +37,10 @@ class FakeAuthenticationService : AuthenticationService by mockk() {
every { isRegistrationStarted() } returns started
}
+ fun givenLoginWizard(loginWizard: LoginWizard) {
+ every { getLoginWizard() } returns loginWizard
+ }
+
fun givenLoginFlow(config: HomeServerConnectionConfig, result: LoginFlowResult) {
coEvery { getLoginFlow(config) } returns result
}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeLoginWizard.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeLoginWizard.kt
new file mode 100644
index 0000000000..38bb75087c
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeLoginWizard.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.test.fakes
+
+import io.mockk.coJustRun
+import io.mockk.mockk
+import org.matrix.android.sdk.api.auth.login.LoginWizard
+
+class FakeLoginWizard : LoginWizard by mockk() {
+
+ fun givenResetPasswordSuccess(email: String) {
+ coJustRun { resetPassword(email) }
+ }
+
+ fun givenConfirmResetPasswordSuccess(password: String) {
+ coJustRun { resetPasswordMailConfirmed(password) }
+ }
+}