mirror of
https://github.com/element-hq/element-android
synced 2024-11-23 18:05:36 +03:00
Merge branch 'develop' into task/eric/when-arrow-alignment
This commit is contained in:
commit
c290dd6c1d
75 changed files with 696 additions and 572 deletions
223
.github/workflows/post-pr.yml
vendored
223
.github/workflows/post-pr.yml
vendored
|
@ -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]`<br>${{ steps.get-comment-body-session.outputs.session }}
|
||||
- `[org.matrix.android.sdk.account]`<br>${{ steps.get-comment-body-account.outputs.account }}
|
||||
- `[org.matrix.android.sdk.internal]`<br>${{ steps.get-comment-body-internal.outputs.internal }}
|
||||
- `[org.matrix.android.sdk.ordering]`<br>${{ steps.get-comment-body-ordering.outputs.ordering }}
|
||||
- `[org.matrix.android.sdk.PermalinkParserTest]`<br>${{ 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:
|
||||
|
|
81
.github/workflows/sonarqube.yml
vendored
81
.github/workflows/sonarqube.yml
vendored
|
@ -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 }}<br />{{icon conclusion}} {{name}} <font color='{{color conclusion}}'>{{conclusion}} at {{completed_at}} <a href=\"{{html_url}}\">[details]</a></font>{{/if}}{{/with}}{{/each}}"
|
141
.github/workflows/tests.yml
vendored
141
.github/workflows/tests.yml
vendored
|
@ -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 }}<br />{{icon conclusion }} {{name}} <font color='{{color conclusion }}'>{{conclusion}} at {{completed_at}} <a href=\"{{html_url}}\">[details]</a></font>{{/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
|
||||
|
||||
|
|
|
@ -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)
|
||||
=======================================
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
1
changelog.d/5285.wip
Normal file
1
changelog.d/5285.wip
Normal file
|
@ -0,0 +1 @@
|
|||
FTUE - Adds Sign Up tracking
|
1
changelog.d/6017.misc
Normal file
1
changelog.d/6017.misc
Normal file
|
@ -0,0 +1 @@
|
|||
Adds support for parsing homeserver versions without a patch number
|
1
changelog.d/6146.feature
Normal file
1
changelog.d/6146.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Allow .well-known configuration to override key sharing mode
|
1
changelog.d/6169.sdk
Normal file
1
changelog.d/6169.sdk
Normal file
|
@ -0,0 +1 @@
|
|||
Allows new passwords to be passed at the point of confirmation when resetting a password
|
1
changelog.d/6222.bugfix
Normal file
1
changelog.d/6222.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Fix StackOverflowError while recording voice message
|
1
changelog.d/6232.bugfix
Normal file
1
changelog.d/6232.bugfix
Normal file
|
@ -0,0 +1 @@
|
|||
Text cropped: "Secure backup"
|
|
@ -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)
|
||||
}
|
||||
|
|
2
fastlane/metadata/android/en-US/changelogs/40104190.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40104190.txt
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<TimelineEvent>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -52,6 +52,7 @@ import java.util.concurrent.CountDownLatch
|
|||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
@Ignore
|
||||
class SASTest : InstrumentedTest {
|
||||
|
||||
@Test
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -226,12 +226,19 @@ interface RoomService {
|
|||
): LiveData<PagedList<RoomSummary>>
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Int> {
|
||||
|
|
|
@ -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<Case>.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?)
|
|
@ -244,6 +244,7 @@ android {
|
|||
buildConfigField "boolean", "ENABLE_STRICT_MODE_LOGS", "false"
|
||||
|
||||
signingConfig signingConfigs.debug
|
||||
testCoverageEnabled true
|
||||
}
|
||||
|
||||
release {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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<ActivityLoginBinding>(), 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<ActivityLoginBinding>(), 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)
|
||||
|
|
|
@ -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<FragmentLog
|
|||
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) }
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import com.airbnb.mvrx.withState
|
|||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.toReducedUrl
|
||||
import im.vector.app.databinding.FragmentLoginSignupSigninSelectionBinding
|
||||
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
@ -74,11 +75,11 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOLogi
|
|||
views.loginSignupSigninSignInSocialLoginContainer.isVisible = true
|
||||
views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders()?.sorted()
|
||||
views.loginSignupSigninSocialLoginButtons.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) }
|
||||
}
|
||||
|
|
|
@ -413,7 +413,8 @@ class LoginViewModel @AssistedInject constructor(
|
|||
copy(
|
||||
asyncResetPassword = Uninitialized,
|
||||
asyncResetMailConfirmed = Uninitialized,
|
||||
resetPasswordEmail = null
|
||||
resetPasswordEmail = null,
|
||||
resetPasswordNewPassword = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -488,7 +489,7 @@ class LoginViewModel @AssistedInject constructor(
|
|||
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
safeLoginWizard.resetPassword(action.email, action.newPassword)
|
||||
safeLoginWizard.resetPassword(action.email)
|
||||
} catch (failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
|
@ -501,7 +502,8 @@ class LoginViewModel @AssistedInject constructor(
|
|||
setState {
|
||||
copy(
|
||||
asyncResetPassword = Success(Unit),
|
||||
resetPasswordEmail = action.email
|
||||
resetPasswordEmail = action.email,
|
||||
resetPasswordNewPassword = action.newPassword
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -529,24 +531,35 @@ class LoginViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
currentJob = viewModelScope.launch {
|
||||
try {
|
||||
safeLoginWizard.resetPasswordMailConfirmed()
|
||||
} catch (failure: Throwable) {
|
||||
val state = awaitState()
|
||||
|
||||
if (state.resetPasswordNewPassword == null) {
|
||||
setState {
|
||||
copy(
|
||||
asyncResetMailConfirmed = Fail(failure)
|
||||
asyncResetPassword = Uninitialized,
|
||||
asyncResetMailConfirmed = Fail(Throwable("Developer error - New password not set"))
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
} else {
|
||||
try {
|
||||
safeLoginWizard.resetPasswordMailConfirmed(state.resetPasswordNewPassword)
|
||||
} catch (failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
asyncResetMailConfirmed = Fail(failure)
|
||||
)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
setState {
|
||||
copy(
|
||||
asyncResetMailConfirmed = Success(Unit),
|
||||
resetPasswordEmail = null,
|
||||
resetPasswordNewPassword = null
|
||||
)
|
||||
}
|
||||
_viewEvents.post(LoginViewEvents.OnResetPasswordMailConfirmationSuccess)
|
||||
}
|
||||
setState {
|
||||
copy(
|
||||
asyncResetMailConfirmed = Success(Unit),
|
||||
resetPasswordEmail = null
|
||||
)
|
||||
}
|
||||
|
||||
_viewEvents.post(LoginViewEvents.OnResetPasswordMailConfirmationSuccess)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,8 @@ data class LoginViewState(
|
|||
@PersistState
|
||||
val resetPasswordEmail: String? = null,
|
||||
@PersistState
|
||||
val resetPasswordNewPassword: String? = null,
|
||||
@PersistState
|
||||
val homeServerUrlFromUser: String? = null,
|
||||
|
||||
// Can be modified after a Wellknown request
|
||||
|
|
|
@ -31,7 +31,7 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs:
|
|||
LinearLayout(context, attrs, defStyle) {
|
||||
|
||||
fun interface InteractionListener {
|
||||
fun onProviderSelected(id: String?)
|
||||
fun onProviderSelected(provider: SsoIdentityProvider?)
|
||||
}
|
||||
|
||||
enum class Mode {
|
||||
|
@ -113,7 +113,7 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs:
|
|||
button.text = getButtonTitle(identityProvider.name)
|
||||
button.setTag(R.id.loginSignupSigninSocialLoginButtons, identityProvider.id)
|
||||
button.setOnClickListener {
|
||||
listener?.onProviderSelected(identityProvider.id)
|
||||
listener?.onProviderSelected(identityProvider)
|
||||
}
|
||||
addView(button)
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs:
|
|||
}
|
||||
}
|
||||
|
||||
fun SocialLoginButtonsView.render(ssoProviders: List<SsoIdentityProvider>?, mode: SocialLoginButtonsView.Mode, listener: (String?) -> Unit) {
|
||||
fun SocialLoginButtonsView.render(ssoProviders: List<SsoIdentityProvider>?, mode: SocialLoginButtonsView.Mode, listener: (SsoIdentityProvider?) -> Unit) {
|
||||
this.mode = mode
|
||||
this.ssoIdentityProviders = ssoProviders?.sorted()
|
||||
this.listener = SocialLoginButtonsView.InteractionListener { listener(it) }
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
@ -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<Frag
|
|||
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) }
|
||||
}
|
||||
|
|
|
@ -392,7 +392,8 @@ class LoginViewModel2 @AssistedInject constructor(
|
|||
LoginAction2.ResetResetPassword -> {
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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? {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -153,7 +153,7 @@ abstract class AbstractFtueAuthFragment<VB : ViewBinding> : VectorBaseFragment<V
|
|||
|
||||
final override fun invalidate() = withState(viewModel) { state ->
|
||||
// True when email is sent with success to the homeserver
|
||||
isResetPasswordStarted = state.resetPasswordEmail.isNullOrBlank().not()
|
||||
isResetPasswordStarted = state.resetState.email.isNullOrBlank().not()
|
||||
|
||||
updateWithState(state)
|
||||
}
|
||||
|
|
|
@ -90,10 +90,10 @@ abstract class AbstractSSOFtueAuthFragment<VB : ViewBinding> : 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) }
|
||||
}
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -164,11 +164,11 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
|
|||
|
||||
private fun renderSsoProviders(deviceId: String?, ssoProviders: List<SsoIdentityProvider>?) {
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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<String>? = null
|
||||
val secureBackupSetupMethods: List<String>? = 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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -151,12 +151,14 @@ class AudioWaveformView @JvmOverloads constructor(
|
|||
|
||||
private fun handleNewFftList(fftList: List<FFT>) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue