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:
|
steps:
|
||||||
- run: echo "Run those tests!" # no-op success
|
- 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:
|
ui-tests:
|
||||||
name: UI Tests (Synapse)
|
name: UI Tests (Synapse)
|
||||||
needs: should-i-run
|
needs: should-i-run
|
||||||
|
@ -282,42 +88,13 @@ jobs:
|
||||||
emulator.log
|
emulator.log
|
||||||
failure_screenshots/
|
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 the channel about delayed failures
|
||||||
notify:
|
notify:
|
||||||
name: Notify matrix
|
name: Notify matrix
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- should-i-run
|
- should-i-run
|
||||||
- integration-tests
|
|
||||||
- ui-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'))
|
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.
|
# No concurrency required, runs every time on a schedule.
|
||||||
steps:
|
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
|
-Porg.gradle.parallel=false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Build Android Tests
|
tests:
|
||||||
build-android-tests:
|
name: Runs all tests
|
||||||
name: Build Android Tests
|
runs-on: macos-latest # for the emulator
|
||||||
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
|
|
||||||
# Allow all jobs on main and develop. Just one per PR.
|
# Allow all jobs on main and develop. Just one per PR.
|
||||||
concurrency:
|
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) }}
|
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
|
cancel-in-progress: true
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/cache@v3
|
|
||||||
with:
|
with:
|
||||||
path: |
|
fetch-depth: 0
|
||||||
~/.gradle/caches
|
- uses: actions/setup-java@v3
|
||||||
~/.gradle/wrapper
|
with:
|
||||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
distribution: 'adopt'
|
||||||
restore-keys: |
|
java-version: '11'
|
||||||
${{ runner.os }}-gradle-
|
- uses: gradle/gradle-build-action@v2
|
||||||
- name: Run unit tests
|
- uses: actions/setup-python@v3
|
||||||
run: ./gradlew clean test $CI_GRADLE_ARG_PROPERTIES --stacktrace
|
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
|
- name: Format unit test results
|
||||||
if: always()
|
if: always()
|
||||||
run: python3 ./tools/ci/render_test_output.py unit ./**/build/test-results/**/*.xml
|
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.
|
# can't be run on macos due to containers.
|
||||||
notify:
|
# - name: Publish Unit Test Results
|
||||||
runs-on: ubuntu-latest
|
# uses: EnricoMi/publish-unit-test-result-action@v1
|
||||||
needs:
|
# if: always() &&
|
||||||
- unit-tests
|
# github.event.sender.login != 'dependabot[bot]' &&
|
||||||
- build-android-tests
|
# ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository )
|
||||||
if: ${{ (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main' ) && failure() }}
|
# with:
|
||||||
steps:
|
# files: ./**/build/test-results/**/*.xml
|
||||||
- uses: michaelkaye/matrix-hookshot-action@v0.3.0
|
|
||||||
with:
|
# Unneeded as part of the test suite above, kept around in case we want to re-enable them.
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
#
|
||||||
matrix_access_token: ${{ secrets.ELEMENT_ANDROID_NOTIFICATION_ACCESS_TOKEN }}
|
# # Build Android Tests
|
||||||
matrix_room_id: ${{ secrets.ELEMENT_ANDROID_INTERNAL_ROOM_ID }}
|
# build-android-tests:
|
||||||
text_template: "Build is broken for ${{ github.ref }}: {{#each job_statuses }}{{#with this }}{{#if completed }}{{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}"
|
# name: Build Android Tests
|
||||||
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}}"
|
# 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)
|
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)
|
[![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)
|
[![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)
|
[![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)
|
[![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=im.vector.app.android&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=im.vector.app.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=im.vector.app.android&metric=bugs)](https://sonarcloud.io/dashboard?id=im.vector.app.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
|
# Element Android
|
||||||
|
|
||||||
|
|
|
@ -180,8 +180,8 @@ apply plugin: 'org.sonarqube'
|
||||||
|
|
||||||
sonarqube {
|
sonarqube {
|
||||||
properties {
|
properties {
|
||||||
property "sonar.projectName", "Element-Android"
|
property "sonar.projectName", "element-android"
|
||||||
property "sonar.projectKey", "im.vector.app.android"
|
property "sonar.projectKey", "vector-im_element-android"
|
||||||
property "sonar.host.url", "https://sonarcloud.io"
|
property "sonar.host.url", "https://sonarcloud.io"
|
||||||
property "sonar.projectVersion", project(":vector").android.defaultConfig.versionName
|
property "sonar.projectVersion", project(":vector").android.defaultConfig.versionName
|
||||||
property "sonar.sourceEncoding", "UTF-8"
|
property "sonar.sourceEncoding", "UTF-8"
|
||||||
|
@ -191,7 +191,7 @@ sonarqube {
|
||||||
property "sonar.links.issue", "https://github.com/vector-im/element-android/issues"
|
property "sonar.links.issue", "https://github.com/vector-im/element-android/issues"
|
||||||
property "sonar.organization", "new_vector_ltd_organization"
|
property "sonar.organization", "new_vector_ltd_organization"
|
||||||
property "sonar.java.coveragePlugin", "jacoco"
|
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"
|
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) {
|
def initializeReport(report, projects, classExcludes) {
|
||||||
projects.each { project -> project.apply plugin: 'jacoco' }
|
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 {
|
report.reports {
|
||||||
xml.enabled true
|
xml.enabled true
|
||||||
|
@ -18,11 +21,13 @@ def initializeReport(report, projects, classExcludes) {
|
||||||
switch (project) {
|
switch (project) {
|
||||||
case { project.plugins.hasPlugin("com.android.application") }:
|
case { project.plugins.hasPlugin("com.android.application") }:
|
||||||
androidClassDirs.add("${project.buildDir}/tmp/kotlin-classes/gplayDebug")
|
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/kotlin")
|
||||||
androidSourceDirs.add("${project.projectDir}/src/main/java")
|
androidSourceDirs.add("${project.projectDir}/src/main/java")
|
||||||
break
|
break
|
||||||
case { project.plugins.hasPlugin("com.android.library") }:
|
case { project.plugins.hasPlugin("com.android.library") }:
|
||||||
androidClassDirs.add("${project.buildDir}/tmp/kotlin-classes/debug")
|
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/kotlin")
|
||||||
androidSourceDirs.add("${project.projectDir}/src/main/java")
|
androidSourceDirs.add("${project.projectDir}/src/main/java")
|
||||||
break
|
break
|
||||||
|
@ -43,13 +48,17 @@ def collectProjects(predicate) {
|
||||||
return subprojects.findAll { it.buildFile.isFile() && predicate(it) }
|
return subprojects.findAll { it.buildFile.isFile() && predicate(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
task allCodeCoverageReport(type: JacocoReport) {
|
task theCodeCoverageReport(type: JacocoReport) {
|
||||||
outputs.upToDateWhen { false }
|
outputs.upToDateWhen { false }
|
||||||
rootProject.apply plugin: 'jacoco'
|
rootProject.apply plugin: 'jacoco'
|
||||||
// to limit projects in a specific report, add
|
tasks.withType(Test) {
|
||||||
// def excludedProjects = [ ... ]
|
jacoco.includeNoLocationClasses = true
|
||||||
// def projects = collectProjects { !excludedProjects.contains(it.name) }
|
}
|
||||||
def projects = collectProjects { true }
|
def projects = collectProjects { ['vector','matrix-sdk-android'].contains(it.name) }
|
||||||
dependsOn { projects*.test }
|
dependsOn {
|
||||||
|
[':matrix-sdk-android:testDebugUnitTest'] +
|
||||||
|
[':vector:testGplayDebugUnitTest'] +
|
||||||
|
[':matrix-sdk-android:connectedDebugAndroidTest']
|
||||||
|
}
|
||||||
initializeReport(it, projects, excludes)
|
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 {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
|
testCoverageEnabled true
|
||||||
// Set to true to log privacy or sensible data, such as token
|
// Set to true to log privacy or sensible data, such as token
|
||||||
buildConfigField "boolean", "LOG_PRIVATE_DATA", project.property("vector.debugPrivateData")
|
buildConfigField "boolean", "LOG_PRIVATE_DATA", project.property("vector.debugPrivateData")
|
||||||
// Set to BODY instead of NONE to enable logging
|
// Set to BODY instead of NONE to enable logging
|
||||||
|
|
|
@ -19,10 +19,14 @@ package org.matrix.android.sdk
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
|
import org.matrix.android.sdk.common.RetryTestRule
|
||||||
import org.matrix.android.sdk.test.shared.createTimberTestRule
|
import org.matrix.android.sdk.test.shared.createTimberTestRule
|
||||||
|
|
||||||
interface InstrumentedTest {
|
interface InstrumentedTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
fun retryTestRule() = RetryTestRule(3)
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
fun timberTestRule() = createTimberTestRule()
|
fun timberTestRule() = createTimberTestRule()
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertNotEquals
|
import org.junit.Assert.assertNotEquals
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.MethodSorters
|
import org.junit.runners.MethodSorters
|
||||||
|
@ -40,6 +41,7 @@ import java.util.UUID
|
||||||
@Suppress("SpellCheckingInspection")
|
@Suppress("SpellCheckingInspection")
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
|
@Ignore
|
||||||
class AttachmentEncryptionTest {
|
class AttachmentEncryptionTest {
|
||||||
|
|
||||||
private fun checkDecryption(input: String, encryptedFileInfo: EncryptedFileInfo): String {
|
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.assertNotEquals
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
|
import org.junit.Ignore
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
@ -37,6 +38,7 @@ import org.matrix.olm.OlmSession
|
||||||
private const val DUMMY_DEVICE_KEY = "DeviceKey"
|
private const val DUMMY_DEVICE_KEY = "DeviceKey"
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@Ignore
|
||||||
class CryptoStoreTest : InstrumentedTest {
|
class CryptoStoreTest : InstrumentedTest {
|
||||||
|
|
||||||
@get:Rule val rule = RetryTestRule(3)
|
@get:Rule val rule = RetryTestRule(3)
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Assert.fail
|
import org.junit.Assert.fail
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.MethodSorters
|
import org.junit.runners.MethodSorters
|
||||||
|
@ -30,6 +31,7 @@ import org.junit.runners.MethodSorters
|
||||||
*/
|
*/
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
|
@Ignore
|
||||||
class ExportEncryptionTest {
|
class ExportEncryptionTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.amshove.kluent.shouldBe
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.MethodSorters
|
import org.junit.runners.MethodSorters
|
||||||
|
@ -59,6 +60,7 @@ import kotlin.coroutines.resume
|
||||||
*/
|
*/
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
@Ignore
|
||||||
class UnwedgingTest : InstrumentedTest {
|
class UnwedgingTest : InstrumentedTest {
|
||||||
|
|
||||||
private lateinit var messagesReceivedByBob: List<TimelineEvent>
|
private lateinit var messagesReceivedByBob: List<TimelineEvent>
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.junit.Assert.assertNull
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Assert.fail
|
import org.junit.Assert.fail
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.MethodSorters
|
import org.junit.runners.MethodSorters
|
||||||
|
@ -47,6 +48,7 @@ import kotlin.coroutines.resume
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
@LargeTest
|
@LargeTest
|
||||||
|
@Ignore
|
||||||
class XSigningTest : InstrumentedTest {
|
class XSigningTest : InstrumentedTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.amshove.kluent.internal.assertEquals
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Ignore
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
@ -50,6 +51,7 @@ import org.matrix.android.sdk.mustFail
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
@LargeTest
|
@LargeTest
|
||||||
|
@Ignore
|
||||||
class KeyShareTests : InstrumentedTest {
|
class KeyShareTests : InstrumentedTest {
|
||||||
|
|
||||||
@get:Rule val rule = RetryTestRule(3)
|
@get:Rule val rule = RetryTestRule(3)
|
||||||
|
|
|
@ -21,6 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.filters.LargeTest
|
import androidx.test.filters.LargeTest
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Ignore
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
@ -46,6 +47,7 @@ import org.matrix.android.sdk.mustFail
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
@LargeTest
|
@LargeTest
|
||||||
|
@Ignore
|
||||||
class WithHeldTests : InstrumentedTest {
|
class WithHeldTests : InstrumentedTest {
|
||||||
|
|
||||||
@get:Rule val rule = RetryTestRule(3)
|
@get:Rule val rule = RetryTestRule(3)
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.junit.Assert.assertNotNull
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Ignore
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
@ -55,6 +56,7 @@ import java.util.concurrent.CountDownLatch
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
@LargeTest
|
@LargeTest
|
||||||
|
@Ignore
|
||||||
class KeysBackupTest : InstrumentedTest {
|
class KeysBackupTest : InstrumentedTest {
|
||||||
|
|
||||||
@get:Rule val rule = RetryTestRule(3)
|
@get:Rule val rule = RetryTestRule(3)
|
||||||
|
|
|
@ -52,6 +52,7 @@ import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
|
@Ignore
|
||||||
class SASTest : InstrumentedTest {
|
class SASTest : InstrumentedTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.verification.qrcode
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import org.amshove.kluent.shouldBe
|
import org.amshove.kluent.shouldBe
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.MethodSorters
|
import org.junit.runners.MethodSorters
|
||||||
|
@ -41,6 +42,7 @@ import kotlin.coroutines.resume
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
@Ignore
|
||||||
class VerificationTest : InstrumentedTest {
|
class VerificationTest : InstrumentedTest {
|
||||||
|
|
||||||
data class ExpectedResult(
|
data class ExpectedResult(
|
||||||
|
|
|
@ -20,6 +20,7 @@ import androidx.test.filters.LargeTest
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.amshove.kluent.internal.assertEquals
|
import org.amshove.kluent.internal.assertEquals
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.JUnit4
|
import org.junit.runners.JUnit4
|
||||||
|
@ -38,6 +39,7 @@ import org.matrix.android.sdk.common.TestConstants
|
||||||
@RunWith(JUnit4::class)
|
@RunWith(JUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
@LargeTest
|
@LargeTest
|
||||||
|
@Ignore
|
||||||
class TimelineSimpleBackPaginationTest : InstrumentedTest {
|
class TimelineSimpleBackPaginationTest : InstrumentedTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -22,6 +22,7 @@ import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.JUnit4
|
import org.junit.runners.JUnit4
|
||||||
|
@ -98,6 +99,7 @@ class SpaceCreationTest : InstrumentedTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Ignore
|
||||||
fun testJoinSimplePublicSpace() = runSessionTest(context()) { commonTestHelper ->
|
fun testJoinSimplePublicSpace() = runSessionTest(context()) { commonTestHelper ->
|
||||||
|
|
||||||
val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
|
val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
|
||||||
|
|
|
@ -65,16 +65,14 @@ interface LoginWizard {
|
||||||
* [resetPasswordMailConfirmed] is successfully called.
|
* [resetPasswordMailConfirmed] is successfully called.
|
||||||
*
|
*
|
||||||
* @param email an email previously associated to the account the user wants the password to be reset.
|
* @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(
|
suspend fun resetPassword(email: String)
|
||||||
email: String,
|
|
||||||
newPassword: String
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Confirm the new password, once the user has checked their email
|
* Confirm the new password, once the user has checked their email
|
||||||
* When this method succeed, tha account password will be effectively modified.
|
* 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.
|
* Append a new line and then the provided string.
|
||||||
*/
|
*/
|
||||||
fun StringBuilder.appendNl(str: String) = append("\n").append(str)
|
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>>
|
): 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(
|
fun getFilteredPagedRoomSummariesLive(
|
||||||
queryParams: RoomSummaryQueryParams,
|
queryParams: RoomSummaryQueryParams,
|
||||||
pagedListConfig: PagedList.Config = defaultPagedListConfig,
|
pagedListConfig: PagedList.Config = defaultPagedListConfig,
|
||||||
sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY
|
sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY,
|
||||||
|
getFlattenParents: Boolean = false,
|
||||||
): UpdatableLivePageResult
|
): UpdatableLivePageResult
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -103,7 +103,7 @@ internal class DefaultLoginWizard(
|
||||||
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
|
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun resetPassword(email: String, newPassword: String) {
|
override suspend fun resetPassword(email: String) {
|
||||||
val param = RegisterAddThreePidTask.Params(
|
val param = RegisterAddThreePidTask.Params(
|
||||||
RegisterThreePid.Email(email),
|
RegisterThreePid.Email(email),
|
||||||
pendingSessionData.clientSecret,
|
pendingSessionData.clientSecret,
|
||||||
|
@ -117,18 +117,16 @@ internal class DefaultLoginWizard(
|
||||||
authAPI.resetPassword(AddThreePidRegistrationParams.from(param))
|
authAPI.resetPassword(AddThreePidRegistrationParams.from(param))
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingSessionData = pendingSessionData.copy(resetPasswordData = ResetPasswordData(newPassword, result))
|
pendingSessionData = pendingSessionData.copy(resetPasswordData = ResetPasswordData(result))
|
||||||
.also { pendingSessionStore.savePendingSessionData(it) }
|
.also { pendingSessionStore.savePendingSessionData(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun resetPasswordMailConfirmed() {
|
override suspend fun resetPasswordMailConfirmed(newPassword: String) {
|
||||||
val safeResetPasswordData = pendingSessionData.resetPasswordData
|
val resetPasswordData = pendingSessionData.resetPasswordData ?: throw IllegalStateException("Developer error - Must call resetPassword first")
|
||||||
?: throw IllegalStateException("developer error, no reset password in progress")
|
|
||||||
|
|
||||||
val param = ResetPasswordMailConfirmed.create(
|
val param = ResetPasswordMailConfirmed.create(
|
||||||
pendingSessionData.clientSecret,
|
pendingSessionData.clientSecret,
|
||||||
safeResetPasswordData.addThreePidRegistrationResponse.sid,
|
resetPasswordData.addThreePidRegistrationResponse.sid,
|
||||||
safeResetPasswordData.newPassword
|
newPassword
|
||||||
)
|
)
|
||||||
|
|
||||||
executeRequest(null) {
|
executeRequest(null) {
|
||||||
|
|
|
@ -24,6 +24,5 @@ import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistration
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class ResetPasswordData(
|
internal data class ResetPasswordData(
|
||||||
val newPassword: String,
|
|
||||||
val addThreePidRegistrationResponse: AddThreePidRegistrationResponse
|
val addThreePidRegistrationResponse: AddThreePidRegistrationResponse
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.auth.version
|
package org.matrix.android.sdk.internal.auth.version
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.extensions.ensureNotEmpty
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Values will take the form "rX.Y.Z".
|
* Values will take the form "rX.Y.Z".
|
||||||
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-versions
|
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-versions
|
||||||
|
@ -38,14 +40,14 @@ internal data class HomeServerVersion(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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? {
|
internal fun parse(value: String): HomeServerVersion? {
|
||||||
val result = pattern.matchEntire(value) ?: return null
|
val result = pattern.matchEntire(value) ?: return null
|
||||||
return HomeServerVersion(
|
return HomeServerVersion(
|
||||||
major = result.groupValues[1].toInt(),
|
major = result.groupValues[1].toInt(),
|
||||||
minor = result.groupValues[2].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(
|
override fun getFilteredPagedRoomSummariesLive(
|
||||||
queryParams: RoomSummaryQueryParams,
|
queryParams: RoomSummaryQueryParams,
|
||||||
pagedListConfig: PagedList.Config,
|
pagedListConfig: PagedList.Config,
|
||||||
sortOrder: RoomSortOrder
|
sortOrder: RoomSortOrder,
|
||||||
|
getFlattenParents: Boolean
|
||||||
): UpdatableLivePageResult {
|
): UpdatableLivePageResult {
|
||||||
return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder, getFlattenedParents = true)
|
return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder, getFlattenParents)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRoomCountLive(queryParams: RoomSummaryQueryParams): LiveData<Int> {
|
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"
|
buildConfigField "boolean", "ENABLE_STRICT_MODE_LOGS", "false"
|
||||||
|
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
|
testCoverageEnabled true
|
||||||
}
|
}
|
||||||
|
|
||||||
release {
|
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 {
|
enum class OutboundSessionKeySharingStrategy {
|
||||||
/**
|
/**
|
||||||
* Keys will be sent for the first time when the first message is sent.
|
* 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,
|
WhenSendingEvent,
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,7 @@ import im.vector.app.features.matrixto.MatrixToBottomSheet
|
||||||
import im.vector.app.features.matrixto.OriginOfMatrixTo
|
import im.vector.app.features.matrixto.OriginOfMatrixTo
|
||||||
import im.vector.app.features.navigation.Navigator
|
import im.vector.app.features.navigation.Navigator
|
||||||
import im.vector.app.features.notifications.NotificationDrawerManager
|
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.NavigationInterceptor
|
||||||
import im.vector.app.features.permalink.PermalinkHandler
|
import im.vector.app.features.permalink.PermalinkHandler
|
||||||
import im.vector.app.features.permalink.PermalinkHandler.Companion.MATRIX_TO_CUSTOM_SCHEME_URL_BASE
|
import im.vector.app.features.permalink.PermalinkHandler.Companion.MATRIX_TO_CUSTOM_SCHEME_URL_BASE
|
||||||
|
@ -91,7 +92,7 @@ import javax.inject.Inject
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class HomeActivityArgs(
|
data class HomeActivityArgs(
|
||||||
val clearNotification: Boolean,
|
val clearNotification: Boolean,
|
||||||
val accountCreation: Boolean,
|
val authenticationDescription: AuthenticationDescription? = null,
|
||||||
val hasExistingSession: Boolean = false,
|
val hasExistingSession: Boolean = false,
|
||||||
val inviteNotificationRoomId: String? = null
|
val inviteNotificationRoomId: String? = null
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
@ -612,13 +613,13 @@ class HomeActivity :
|
||||||
fun newIntent(
|
fun newIntent(
|
||||||
context: Context,
|
context: Context,
|
||||||
clearNotification: Boolean = false,
|
clearNotification: Boolean = false,
|
||||||
accountCreation: Boolean = false,
|
authenticationDescription: AuthenticationDescription? = null,
|
||||||
existingSession: Boolean = false,
|
existingSession: Boolean = false,
|
||||||
inviteNotificationRoomId: String? = null
|
inviteNotificationRoomId: String? = null
|
||||||
): Intent {
|
): Intent {
|
||||||
val args = HomeActivityArgs(
|
val args = HomeActivityArgs(
|
||||||
clearNotification = clearNotification,
|
clearNotification = clearNotification,
|
||||||
accountCreation = accountCreation,
|
authenticationDescription = authenticationDescription,
|
||||||
hasExistingSession = existingSession,
|
hasExistingSession = existingSession,
|
||||||
inviteNotificationRoomId = inviteNotificationRoomId
|
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.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
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.analytics.store.AnalyticsStore
|
||||||
import im.vector.app.features.login.ReAuthHelper
|
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.ElementWellKnown
|
||||||
import im.vector.app.features.raw.wellknown.getElementWellknown
|
import im.vector.app.features.raw.wellknown.getElementWellknown
|
||||||
import im.vector.app.features.raw.wellknown.isSecureBackupRequired
|
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.session.coroutineScope
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onCompletion
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.takeWhile
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
import org.matrix.android.sdk.api.auth.UIABaseAuth
|
||||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
|
@ -72,7 +80,8 @@ class HomeActivityViewModel @AssistedInject constructor(
|
||||||
private val reAuthHelper: ReAuthHelper,
|
private val reAuthHelper: ReAuthHelper,
|
||||||
private val analyticsStore: AnalyticsStore,
|
private val analyticsStore: AnalyticsStore,
|
||||||
private val lightweightSettingsStorage: LightweightSettingsStorage,
|
private val lightweightSettingsStorage: LightweightSettingsStorage,
|
||||||
private val vectorPreferences: VectorPreferences
|
private val vectorPreferences: VectorPreferences,
|
||||||
|
private val analyticsTracker: AnalyticsTracker
|
||||||
) : VectorViewModel<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(initialState) {
|
) : VectorViewModel<HomeActivityViewState, HomeActivityViewActions, HomeActivityViewEvents>(initialState) {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
@ -84,7 +93,7 @@ class HomeActivityViewModel @AssistedInject constructor(
|
||||||
override fun initialState(viewModelContext: ViewModelContext): HomeActivityViewState? {
|
override fun initialState(viewModelContext: ViewModelContext): HomeActivityViewState? {
|
||||||
val activity: HomeActivity = viewModelContext.activity()
|
val activity: HomeActivity = viewModelContext.activity()
|
||||||
val args: HomeActivityArgs? = activity.intent.getParcelableExtra(Mavericks.KEY_ARG)
|
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)
|
?: super.initialState(viewModelContext)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,9 +122,32 @@ class HomeActivityViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.launchIn(viewModelScope)
|
.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() {
|
private fun cleanupFiles() {
|
||||||
// Mitigation: delete all cached decrypted files each time the application is started.
|
// Mitigation: delete all cached decrypted files each time the application is started.
|
||||||
activeSessionHolder.getSafeActiveSession()?.fileService()?.clearDecryptedCache()
|
activeSessionHolder.getSafeActiveSession()?.fileService()?.clearDecryptedCache()
|
||||||
|
@ -134,9 +166,8 @@ class HomeActivityViewModel @AssistedInject constructor(
|
||||||
.onEach { info ->
|
.onEach { info ->
|
||||||
val isVerified = info.getOrNull()?.isTrusted() ?: false
|
val isVerified = info.getOrNull()?.isTrusted() ?: false
|
||||||
if (!isVerified && onceTrusted) {
|
if (!isVerified && onceTrusted) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
rawService.withElementWellKnown(viewModelScope, safeActiveSession.sessionParams) {
|
||||||
val elementWellKnown = rawService.getElementWellknown(safeActiveSession.sessionParams)
|
sessionHasBeenUnverified(it)
|
||||||
sessionHasBeenUnverified(elementWellKnown)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onceTrusted = isVerified
|
onceTrusted = isVerified
|
||||||
|
@ -285,7 +316,7 @@ class HomeActivityViewModel @AssistedInject constructor(
|
||||||
val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired() ?: false
|
val isSecureBackupRequired = elementWellKnown?.isSecureBackupRequired() ?: false
|
||||||
|
|
||||||
// In case of account creation, it is already done before
|
// In case of account creation, it is already done before
|
||||||
if (initialState.accountCreation) {
|
if (initialState.authenticationDescription is AuthenticationDescription.Register) {
|
||||||
if (isSecureBackupRequired) {
|
if (isSecureBackupRequired) {
|
||||||
_viewEvents.post(HomeActivityViewEvents.StartRecoverySetupFlow)
|
_viewEvents.post(HomeActivityViewEvents.StartRecoverySetupFlow)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -17,9 +17,10 @@
|
||||||
package im.vector.app.features.home
|
package im.vector.app.features.home
|
||||||
|
|
||||||
import com.airbnb.mvrx.MavericksState
|
import com.airbnb.mvrx.MavericksState
|
||||||
|
import im.vector.app.features.onboarding.AuthenticationDescription
|
||||||
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
|
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
|
||||||
|
|
||||||
data class HomeActivityViewState(
|
data class HomeActivityViewState(
|
||||||
val syncStatusServiceStatus: SyncStatusService.Status = SyncStatusService.Status.Idle,
|
val syncStatusServiceStatus: SyncStatusService.Status = SyncStatusService.Status.Idle,
|
||||||
val accountCreation: Boolean = false
|
val authenticationDescription: AuthenticationDescription? = null
|
||||||
) : MavericksState
|
) : MavericksState
|
||||||
|
|
|
@ -29,7 +29,6 @@ import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import im.vector.app.AppStateHandler
|
import im.vector.app.AppStateHandler
|
||||||
import im.vector.app.BuildConfig
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
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.location.LocationSharingServiceConnection
|
||||||
import im.vector.app.features.notifications.NotificationDrawerManager
|
import im.vector.app.features.notifications.NotificationDrawerManager
|
||||||
import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
|
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.session.coroutineScope
|
||||||
import im.vector.app.features.settings.VectorDataStore
|
import im.vector.app.features.settings.VectorDataStore
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
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.MatrixPatterns
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
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.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
@ -118,6 +120,7 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
private val vectorDataStore: VectorDataStore,
|
private val vectorDataStore: VectorDataStore,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
|
private val rawService: RawService,
|
||||||
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
|
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
|
||||||
private val stickerPickerActionHandler: StickerPickerActionHandler,
|
private val stickerPickerActionHandler: StickerPickerActionHandler,
|
||||||
private val typingHelper: TypingHelper,
|
private val typingHelper: TypingHelper,
|
||||||
|
@ -196,8 +199,13 @@ class TimelineViewModel @AssistedInject constructor(
|
||||||
chatEffectManager.delegate = this
|
chatEffectManager.delegate = this
|
||||||
|
|
||||||
// Ensure to share the outbound session keys with all members
|
// Ensure to share the outbound session keys with all members
|
||||||
if (OutboundSessionKeySharingStrategy.WhenEnteringRoom == BuildConfig.outboundSessionKeySharingStrategy && room.roomCryptoService().isEncrypted()) {
|
if (room.roomCryptoService().isEncrypted()) {
|
||||||
prepareForEncryption()
|
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
|
// 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) {
|
private fun handleComposerFocusChange(action: RoomDetailAction.ComposerFocusChange) {
|
||||||
// Ensure outbound session keys
|
// Ensure outbound session keys
|
||||||
if (OutboundSessionKeySharingStrategy.WhenTyping == BuildConfig.outboundSessionKeySharingStrategy && room.roomCryptoService().isEncrypted()) {
|
if (room.roomCryptoService().isEncrypted()) {
|
||||||
if (action.focused) {
|
rawService.withElementWellKnown(viewModelScope, session.sessionParams) {
|
||||||
// Should we add some rate limit here, or do it only once per model lifecycle?
|
val strategy = it.getOutboundSessionKeySharingStrategyOrDefault()
|
||||||
prepareForEncryption()
|
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 ->
|
{ qpm ->
|
||||||
val name = stringProvider.getString(R.string.bottom_action_rooms)
|
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)
|
onUpdatable(updatableFilterLivePageResult)
|
||||||
|
|
||||||
val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
|
val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
|
||||||
|
|
|
@ -332,7 +332,7 @@ class RoomListSectionBuilderSpace(
|
||||||
},
|
},
|
||||||
{ queryParams ->
|
{ queryParams ->
|
||||||
val name = stringProvider.getString(R.string.bottom_action_rooms)
|
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)
|
onUpdatable(updatableFilterLivePageResult)
|
||||||
|
|
||||||
val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
|
val itemCountFlow = updatableFilterLivePageResult.livePagedList.asFlow()
|
||||||
|
|
|
@ -207,7 +207,7 @@ class RoomSummaryItemFactory @Inject constructor(
|
||||||
|
|
||||||
private fun getSearchResultSubtitle(roomSummary: RoomSummary): String {
|
private fun getSearchResultSubtitle(roomSummary: RoomSummary): String {
|
||||||
val userId = roomSummary.directUserId
|
val userId = roomSummary.directUserId
|
||||||
val spaceName = roomSummary.spaceParents?.firstOrNull()?.roomSummary?.name
|
val spaceName = roomSummary.flattenParents.lastOrNull()?.name
|
||||||
val canonicalAlias = roomSummary.canonicalAlias
|
val canonicalAlias = roomSummary.canonicalAlias
|
||||||
|
|
||||||
return (userId ?: spaceName ?: canonicalAlias).orEmpty()
|
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.home.HomeActivity
|
||||||
import im.vector.app.features.login.terms.LoginTermsFragment
|
import im.vector.app.features.login.terms.LoginTermsFragment
|
||||||
import im.vector.app.features.login.terms.LoginTermsFragmentArgument
|
import im.vector.app.features.login.terms.LoginTermsFragmentArgument
|
||||||
|
import im.vector.app.features.onboarding.AuthenticationDescription
|
||||||
import im.vector.app.features.pin.UnlockedActivity
|
import im.vector.app.features.pin.UnlockedActivity
|
||||||
import org.matrix.android.sdk.api.auth.registration.FlowResult
|
import org.matrix.android.sdk.api.auth.registration.FlowResult
|
||||||
import org.matrix.android.sdk.api.auth.registration.Stage
|
import org.matrix.android.sdk.api.auth.registration.Stage
|
||||||
|
@ -218,10 +219,8 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), UnlockedA
|
||||||
// change the screen name
|
// change the screen name
|
||||||
analyticsScreenName = MobileScreen.ScreenName.Register
|
analyticsScreenName = MobileScreen.ScreenName.Register
|
||||||
}
|
}
|
||||||
val intent = HomeActivity.newIntent(
|
val authDescription = inferAuthDescription(loginViewState)
|
||||||
this,
|
val intent = HomeActivity.newIntent(this, authenticationDescription = authDescription)
|
||||||
accountCreation = loginViewState.signMode == SignMode.SignUp
|
|
||||||
)
|
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
finish()
|
finish()
|
||||||
return
|
return
|
||||||
|
@ -231,6 +230,13 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), UnlockedA
|
||||||
views.loginLoading.isVisible = loginViewState.isLoading()
|
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) {
|
private fun onWebLoginError(onWebLoginError: LoginViewEvents.OnWebLoginError) {
|
||||||
// Pop the backstack
|
// Pop the backstack
|
||||||
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
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.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
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.Failure
|
||||||
import org.matrix.android.sdk.api.failure.MatrixError
|
import org.matrix.android.sdk.api.failure.MatrixError
|
||||||
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
||||||
|
@ -202,11 +203,11 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
|
||||||
views.loginSocialLoginContainer.isVisible = true
|
views.loginSocialLoginContainer.isVisible = true
|
||||||
views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted()
|
views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted()
|
||||||
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
||||||
override fun onProviderSelected(id: String?) {
|
override fun onProviderSelected(provider: SsoIdentityProvider?) {
|
||||||
loginViewModel.getSsoUrl(
|
loginViewModel.getSsoUrl(
|
||||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||||
deviceId = state.deviceId,
|
deviceId = state.deviceId,
|
||||||
providerId = id
|
providerId = provider?.id
|
||||||
)
|
)
|
||||||
?.let { openInCustomTab(it) }
|
?.let { openInCustomTab(it) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import com.airbnb.mvrx.withState
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.toReducedUrl
|
import im.vector.app.core.extensions.toReducedUrl
|
||||||
import im.vector.app.databinding.FragmentLoginSignupSigninSelectionBinding
|
import im.vector.app.databinding.FragmentLoginSignupSigninSelectionBinding
|
||||||
|
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,11 +75,11 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOLogi
|
||||||
views.loginSignupSigninSignInSocialLoginContainer.isVisible = true
|
views.loginSignupSigninSignInSocialLoginContainer.isVisible = true
|
||||||
views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders()?.sorted()
|
views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders()?.sorted()
|
||||||
views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
||||||
override fun onProviderSelected(id: String?) {
|
override fun onProviderSelected(provider: SsoIdentityProvider?) {
|
||||||
loginViewModel.getSsoUrl(
|
loginViewModel.getSsoUrl(
|
||||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||||
deviceId = state.deviceId,
|
deviceId = state.deviceId,
|
||||||
providerId = id
|
providerId = provider?.id
|
||||||
)
|
)
|
||||||
?.let { openInCustomTab(it) }
|
?.let { openInCustomTab(it) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -413,7 +413,8 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
copy(
|
copy(
|
||||||
asyncResetPassword = Uninitialized,
|
asyncResetPassword = Uninitialized,
|
||||||
asyncResetMailConfirmed = Uninitialized,
|
asyncResetMailConfirmed = Uninitialized,
|
||||||
resetPasswordEmail = null
|
resetPasswordEmail = null,
|
||||||
|
resetPasswordNewPassword = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -488,7 +489,7 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
currentJob = viewModelScope.launch {
|
currentJob = viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
safeLoginWizard.resetPassword(action.email, action.newPassword)
|
safeLoginWizard.resetPassword(action.email)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
|
@ -501,7 +502,8 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
asyncResetPassword = Success(Unit),
|
asyncResetPassword = Success(Unit),
|
||||||
resetPasswordEmail = action.email
|
resetPasswordEmail = action.email,
|
||||||
|
resetPasswordNewPassword = action.newPassword
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,24 +531,35 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
currentJob = viewModelScope.launch {
|
currentJob = viewModelScope.launch {
|
||||||
try {
|
val state = awaitState()
|
||||||
safeLoginWizard.resetPasswordMailConfirmed()
|
|
||||||
} catch (failure: Throwable) {
|
if (state.resetPasswordNewPassword == null) {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
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
|
@PersistState
|
||||||
val resetPasswordEmail: String? = null,
|
val resetPasswordEmail: String? = null,
|
||||||
@PersistState
|
@PersistState
|
||||||
|
val resetPasswordNewPassword: String? = null,
|
||||||
|
@PersistState
|
||||||
val homeServerUrlFromUser: String? = null,
|
val homeServerUrlFromUser: String? = null,
|
||||||
|
|
||||||
// Can be modified after a Wellknown request
|
// Can be modified after a Wellknown request
|
||||||
|
|
|
@ -31,7 +31,7 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs:
|
||||||
LinearLayout(context, attrs, defStyle) {
|
LinearLayout(context, attrs, defStyle) {
|
||||||
|
|
||||||
fun interface InteractionListener {
|
fun interface InteractionListener {
|
||||||
fun onProviderSelected(id: String?)
|
fun onProviderSelected(provider: SsoIdentityProvider?)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Mode {
|
enum class Mode {
|
||||||
|
@ -113,7 +113,7 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs:
|
||||||
button.text = getButtonTitle(identityProvider.name)
|
button.text = getButtonTitle(identityProvider.name)
|
||||||
button.setTag(R.id.loginSignupSigninSocialLoginButtons, identityProvider.id)
|
button.setTag(R.id.loginSignupSigninSocialLoginButtons, identityProvider.id)
|
||||||
button.setOnClickListener {
|
button.setOnClickListener {
|
||||||
listener?.onProviderSelected(identityProvider.id)
|
listener?.onProviderSelected(identityProvider)
|
||||||
}
|
}
|
||||||
addView(button)
|
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.mode = mode
|
||||||
this.ssoIdentityProviders = ssoProviders?.sorted()
|
this.ssoIdentityProviders = ssoProviders?.sorted()
|
||||||
this.listener = SocialLoginButtonsView.InteractionListener { listener(it) }
|
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.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
||||||
import reactivecircus.flowbinding.android.widget.textChanges
|
import reactivecircus.flowbinding.android.widget.textChanges
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -96,11 +97,11 @@ class LoginFragmentSignupUsername2 @Inject constructor() : AbstractSSOLoginFragm
|
||||||
views.loginSocialLoginContainer.isVisible = true
|
views.loginSocialLoginContainer.isVisible = true
|
||||||
views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted()
|
views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted()
|
||||||
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
||||||
override fun onProviderSelected(id: String?) {
|
override fun onProviderSelected(provider: SsoIdentityProvider?) {
|
||||||
loginViewModel.getSsoUrl(
|
loginViewModel.getSsoUrl(
|
||||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||||
deviceId = state.deviceId,
|
deviceId = state.deviceId,
|
||||||
providerId = id
|
providerId = provider?.id
|
||||||
)
|
)
|
||||||
?.let { openInCustomTab(it) }
|
?.let { openInCustomTab(it) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
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.Failure
|
||||||
import org.matrix.android.sdk.api.failure.MatrixError
|
import org.matrix.android.sdk.api.failure.MatrixError
|
||||||
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
import org.matrix.android.sdk.api.failure.isInvalidPassword
|
||||||
|
@ -123,11 +124,11 @@ class LoginFragmentToAny2 @Inject constructor() : AbstractSSOLoginFragment2<Frag
|
||||||
views.loginSocialLoginContainer.isVisible = true
|
views.loginSocialLoginContainer.isVisible = true
|
||||||
views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted()
|
views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders?.sorted()
|
||||||
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
||||||
override fun onProviderSelected(id: String?) {
|
override fun onProviderSelected(provider: SsoIdentityProvider?) {
|
||||||
loginViewModel.getSsoUrl(
|
loginViewModel.getSsoUrl(
|
||||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||||
deviceId = state.deviceId,
|
deviceId = state.deviceId,
|
||||||
providerId = id
|
providerId = provider?.id
|
||||||
)
|
)
|
||||||
?.let { openInCustomTab(it) }
|
?.let { openInCustomTab(it) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -392,7 +392,8 @@ class LoginViewModel2 @AssistedInject constructor(
|
||||||
LoginAction2.ResetResetPassword -> {
|
LoginAction2.ResetResetPassword -> {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
resetPasswordEmail = null
|
resetPasswordEmail = null,
|
||||||
|
resetPasswordNewPassword = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -443,7 +444,7 @@ class LoginViewModel2 @AssistedInject constructor(
|
||||||
|
|
||||||
currentJob = viewModelScope.launch {
|
currentJob = viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
safeLoginWizard.resetPassword(action.email, action.newPassword)
|
safeLoginWizard.resetPassword(action.email)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
_viewEvents.post(LoginViewEvents2.Failure(failure))
|
_viewEvents.post(LoginViewEvents2.Failure(failure))
|
||||||
setState { copy(isLoading = false) }
|
setState { copy(isLoading = false) }
|
||||||
|
@ -453,7 +454,8 @@ class LoginViewModel2 @AssistedInject constructor(
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
resetPasswordEmail = action.email
|
resetPasswordEmail = action.email,
|
||||||
|
resetPasswordNewPassword = action.newPassword
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,7 +474,8 @@ class LoginViewModel2 @AssistedInject constructor(
|
||||||
|
|
||||||
currentJob = viewModelScope.launch {
|
currentJob = viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
safeLoginWizard.resetPasswordMailConfirmed()
|
val state = awaitState()
|
||||||
|
safeLoginWizard.resetPasswordMailConfirmed(state.resetPasswordNewPassword!!)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
_viewEvents.post(LoginViewEvents2.Failure(failure))
|
_viewEvents.post(LoginViewEvents2.Failure(failure))
|
||||||
setState { copy(isLoading = false) }
|
setState { copy(isLoading = false) }
|
||||||
|
@ -481,7 +484,8 @@ class LoginViewModel2 @AssistedInject constructor(
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
resetPasswordEmail = null
|
resetPasswordEmail = null,
|
||||||
|
resetPasswordNewPassword = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,8 @@ data class LoginViewState2(
|
||||||
@PersistState
|
@PersistState
|
||||||
val resetPasswordEmail: String? = null,
|
val resetPasswordEmail: String? = null,
|
||||||
@PersistState
|
@PersistState
|
||||||
|
val resetPasswordNewPassword: String? = null,
|
||||||
|
@PersistState
|
||||||
val homeServerUrlFromUser: String? = null,
|
val homeServerUrlFromUser: String? = null,
|
||||||
|
|
||||||
// Can be modified after a Wellknown request
|
// 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 ->
|
is LoginViewEvents2.OnLoginModeNotSupported ->
|
||||||
onLoginModeNotSupported(event.supportedTypes)
|
onLoginModeNotSupported(event.supportedTypes)
|
||||||
is LoginViewEvents2.OnSessionCreated -> handleOnSessionCreated(event)
|
is LoginViewEvents2.OnSessionCreated -> handleOnSessionCreated(event)
|
||||||
is LoginViewEvents2.Finish -> terminate(true)
|
is LoginViewEvents2.Finish -> terminate()
|
||||||
is LoginViewEvents2.CancelRegistration -> handleCancelRegistration()
|
is LoginViewEvents2.CancelRegistration -> handleCancelRegistration()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -296,14 +296,13 @@ class Login2Variant(
|
||||||
option = commonOption
|
option = commonOption
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
terminate(false)
|
terminate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun terminate(newAccount: Boolean) {
|
private fun terminate() {
|
||||||
val intent = HomeActivity.newIntent(
|
val intent = HomeActivity.newIntent(
|
||||||
activity,
|
activity
|
||||||
accountCreation = newAccount
|
|
||||||
)
|
)
|
||||||
activity.startActivity(intent)
|
activity.startActivity(intent)
|
||||||
activity.finish()
|
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.AuthenticationService
|
||||||
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
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.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.login.LoginWizard
|
||||||
import org.matrix.android.sdk.api.auth.registration.FlowResult
|
import org.matrix.android.sdk.api.auth.registration.FlowResult
|
||||||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||||
|
@ -127,7 +128,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
val isRegistrationStarted: Boolean
|
val isRegistrationStarted: Boolean
|
||||||
get() = authenticationService.isRegistrationStarted()
|
get() = authenticationService.isRegistrationStarted()
|
||||||
|
|
||||||
private val loginWizard: LoginWizard?
|
private val loginWizard: LoginWizard
|
||||||
get() = authenticationService.getLoginWizard()
|
get() = authenticationService.getLoginWizard()
|
||||||
|
|
||||||
private var loginConfig: LoginConfig? = null
|
private var loginConfig: LoginConfig? = null
|
||||||
|
@ -245,21 +246,15 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
private fun handleLoginWithToken(action: OnboardingAction.LoginWithToken) {
|
private fun handleLoginWithToken(action: OnboardingAction.LoginWithToken) {
|
||||||
val safeLoginWizard = loginWizard
|
val safeLoginWizard = loginWizard
|
||||||
|
setState { copy(isLoading = true) }
|
||||||
|
|
||||||
if (safeLoginWizard == null) {
|
currentJob = viewModelScope.launch {
|
||||||
setState { copy(isLoading = false) }
|
try {
|
||||||
_viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration")))
|
val result = safeLoginWizard.loginWithToken(action.loginToken)
|
||||||
} else {
|
onSessionCreated(result, authenticationDescription = AuthenticationDescription.Login)
|
||||||
setState { copy(isLoading = true) }
|
} catch (failure: Throwable) {
|
||||||
|
setState { copy(isLoading = false) }
|
||||||
currentJob = viewModelScope.launch {
|
_viewEvents.post(OnboardingViewEvents.Failure(failure))
|
||||||
try {
|
|
||||||
val result = safeLoginWizard.loginWithToken(action.loginToken)
|
|
||||||
onSessionCreated(result, isAccountCreated = false)
|
|
||||||
} catch (failure: Throwable) {
|
|
||||||
setState { copy(isLoading = false) }
|
|
||||||
_viewEvents.post(OnboardingViewEvents.Failure(failure))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -289,7 +284,11 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
else -> when (it) {
|
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.NextStep -> onFlowResponse(it.flowResult, onNextRegistrationStepAction)
|
||||||
is RegistrationResult.SendEmailSuccess -> _viewEvents.post(OnboardingViewEvents.OnSendEmailSuccess(it.email))
|
is RegistrationResult.SendEmailSuccess -> _viewEvents.post(OnboardingViewEvents.OnSendEmailSuccess(it.email))
|
||||||
is RegistrationResult.Error -> _viewEvents.post(OnboardingViewEvents.Failure(it.cause))
|
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 OnboardingViewState.hasSelectedMatrixOrg() = selectedHomeserver.userFacingUrl == matrixOrgUrl
|
||||||
|
|
||||||
private fun handleRegisterWith(action: AuthenticateAction.Register) {
|
private fun handleRegisterWith(action: AuthenticateAction.Register) {
|
||||||
|
setState {
|
||||||
|
val authDescription = AuthenticationDescription.Register(AuthenticationDescription.AuthenticationType.Password)
|
||||||
|
copy(selectedAuthenticationState = SelectedAuthenticationState(authDescription))
|
||||||
|
}
|
||||||
reAuthHelper.data = action.password
|
reAuthHelper.data = action.password
|
||||||
handleRegisterAction(
|
handleRegisterAction(
|
||||||
RegisterAction.CreateAccount(
|
RegisterAction.CreateAccount(
|
||||||
|
@ -368,7 +371,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
resetPasswordEmail = null
|
resetState = ResetState()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -438,59 +441,52 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
private fun handleResetPassword(action: OnboardingAction.ResetPassword) {
|
private fun handleResetPassword(action: OnboardingAction.ResetPassword) {
|
||||||
val safeLoginWizard = loginWizard
|
val safeLoginWizard = loginWizard
|
||||||
|
setState { copy(isLoading = true) }
|
||||||
if (safeLoginWizard == null) {
|
currentJob = viewModelScope.launch {
|
||||||
setState { copy(isLoading = false) }
|
runCatching { safeLoginWizard.resetPassword(action.email) }.fold(
|
||||||
_viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration")))
|
onSuccess = {
|
||||||
} else {
|
setState {
|
||||||
setState { copy(isLoading = true) }
|
copy(
|
||||||
|
isLoading = false,
|
||||||
currentJob = viewModelScope.launch {
|
resetState = ResetState(email = action.email, newPassword = action.newPassword)
|
||||||
try {
|
)
|
||||||
safeLoginWizard.resetPassword(action.email, action.newPassword)
|
}
|
||||||
} catch (failure: Throwable) {
|
_viewEvents.post(OnboardingViewEvents.OnResetPasswordSendThreePidDone)
|
||||||
setState { copy(isLoading = false) }
|
},
|
||||||
_viewEvents.post(OnboardingViewEvents.Failure(failure))
|
onFailure = {
|
||||||
return@launch
|
setState { copy(isLoading = false) }
|
||||||
}
|
_viewEvents.post(OnboardingViewEvents.Failure(it))
|
||||||
|
}
|
||||||
setState {
|
)
|
||||||
copy(
|
|
||||||
isLoading = false,
|
|
||||||
resetPasswordEmail = action.email
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
_viewEvents.post(OnboardingViewEvents.OnResetPasswordSendThreePidDone)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleResetPasswordMailConfirmed() {
|
private fun handleResetPasswordMailConfirmed() {
|
||||||
val safeLoginWizard = loginWizard
|
setState { copy(isLoading = true) }
|
||||||
|
currentJob = viewModelScope.launch {
|
||||||
if (safeLoginWizard == null) {
|
val resetState = awaitState().resetState
|
||||||
setState { copy(isLoading = false) }
|
when (val newPassword = resetState.newPassword) {
|
||||||
_viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration")))
|
null -> {
|
||||||
} else {
|
|
||||||
setState { copy(isLoading = false) }
|
|
||||||
|
|
||||||
currentJob = viewModelScope.launch {
|
|
||||||
try {
|
|
||||||
safeLoginWizard.resetPasswordMailConfirmed()
|
|
||||||
} catch (failure: Throwable) {
|
|
||||||
setState { copy(isLoading = false) }
|
setState { copy(isLoading = false) }
|
||||||
_viewEvents.post(OnboardingViewEvents.Failure(failure))
|
_viewEvents.post(OnboardingViewEvents.Failure(IllegalStateException("Developer error - No new password has been set")))
|
||||||
return@launch
|
|
||||||
}
|
}
|
||||||
setState {
|
else -> {
|
||||||
copy(
|
runCatching { loginWizard.resetPasswordMailConfirmed(newPassword) }.fold(
|
||||||
isLoading = false,
|
onSuccess = {
|
||||||
resetPasswordEmail = null
|
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) }
|
setState { copy(isLoading = true) }
|
||||||
currentJob = viewModelScope.launch {
|
currentJob = viewModelScope.launch {
|
||||||
directLoginUseCase.execute(action, homeServerConnectionConfig).fold(
|
directLoginUseCase.execute(action, homeServerConnectionConfig).fold(
|
||||||
onSuccess = { onSessionCreated(it, isAccountCreated = false) },
|
onSuccess = { onSessionCreated(it, authenticationDescription = AuthenticationDescription.Login) },
|
||||||
onFailure = {
|
onFailure = {
|
||||||
setState { copy(isLoading = false) }
|
setState { copy(isLoading = false) }
|
||||||
_viewEvents.post(OnboardingViewEvents.Failure(it))
|
_viewEvents.post(OnboardingViewEvents.Failure(it))
|
||||||
|
@ -510,25 +506,19 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
private fun handleLogin(action: AuthenticateAction.Login) {
|
private fun handleLogin(action: AuthenticateAction.Login) {
|
||||||
val safeLoginWizard = loginWizard
|
val safeLoginWizard = loginWizard
|
||||||
|
setState { copy(isLoading = true) }
|
||||||
if (safeLoginWizard == null) {
|
currentJob = viewModelScope.launch {
|
||||||
setState { copy(isLoading = false) }
|
try {
|
||||||
_viewEvents.post(OnboardingViewEvents.Failure(Throwable("Bad configuration")))
|
val result = safeLoginWizard.login(
|
||||||
} else {
|
action.username,
|
||||||
setState { copy(isLoading = true) }
|
action.password,
|
||||||
currentJob = viewModelScope.launch {
|
action.initialDeviceName
|
||||||
try {
|
)
|
||||||
val result = safeLoginWizard.login(
|
reAuthHelper.data = action.password
|
||||||
action.username,
|
onSessionCreated(result, authenticationDescription = AuthenticationDescription.Login)
|
||||||
action.password,
|
} catch (failure: Throwable) {
|
||||||
action.initialDeviceName
|
setState { copy(isLoading = false) }
|
||||||
)
|
_viewEvents.post(OnboardingViewEvents.Failure(failure))
|
||||||
reAuthHelper.data = action.password
|
|
||||||
onSessionCreated(result, isAccountCreated = false)
|
|
||||||
} catch (failure: Throwable) {
|
|
||||||
setState { copy(isLoading = false) }
|
|
||||||
_viewEvents.post(OnboardingViewEvents.Failure(failure))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -553,7 +543,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
internalRegisterAction(RegisterAction.RegisterDummy, onNextRegistrationStepAction)
|
internalRegisterAction(RegisterAction.RegisterDummy, onNextRegistrationStepAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun onSessionCreated(session: Session, isAccountCreated: Boolean) {
|
private suspend fun onSessionCreated(session: Session, authenticationDescription: AuthenticationDescription) {
|
||||||
val state = awaitState()
|
val state = awaitState()
|
||||||
state.useCase?.let { useCase ->
|
state.useCase?.let { useCase ->
|
||||||
session.vectorStore(applicationContext).setUseCase(useCase)
|
session.vectorStore(applicationContext).setUseCase(useCase)
|
||||||
|
@ -564,15 +554,15 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
authenticationService.reset()
|
authenticationService.reset()
|
||||||
session.configureAndStart(applicationContext)
|
session.configureAndStart(applicationContext)
|
||||||
|
|
||||||
when (isAccountCreated) {
|
when (authenticationDescription) {
|
||||||
true -> {
|
is AuthenticationDescription.Register -> {
|
||||||
val personalizationState = createPersonalizationState(session, state)
|
val personalizationState = createPersonalizationState(session, state)
|
||||||
setState {
|
setState {
|
||||||
copy(isLoading = false, personalizationState = personalizationState)
|
copy(isLoading = false, personalizationState = personalizationState)
|
||||||
}
|
}
|
||||||
_viewEvents.post(OnboardingViewEvents.OnAccountCreated)
|
_viewEvents.post(OnboardingViewEvents.OnAccountCreated)
|
||||||
}
|
}
|
||||||
false -> {
|
AuthenticationDescription.Login -> {
|
||||||
setState { copy(isLoading = false) }
|
setState { copy(isLoading = false) }
|
||||||
_viewEvents.post(OnboardingViewEvents.OnAccountSignedIn)
|
_viewEvents.post(OnboardingViewEvents.OnAccountSignedIn)
|
||||||
}
|
}
|
||||||
|
@ -603,7 +593,7 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
currentJob = viewModelScope.launch {
|
currentJob = viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
val result = authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials)
|
val result = authenticationService.createSessionFromSso(homeServerConnectionConfigFinal, action.credentials)
|
||||||
onSessionCreated(result, isAccountCreated = false)
|
onSessionCreated(result, authenticationDescription = AuthenticationDescription.Login)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
setState { copy(isLoading = false) }
|
setState { copy(isLoading = false) }
|
||||||
}
|
}
|
||||||
|
@ -745,8 +735,12 @@ class OnboardingViewModel @AssistedInject constructor(
|
||||||
return loginConfig?.homeServerUrl
|
return loginConfig?.homeServerUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? {
|
fun fetchSsoUrl(redirectUrl: String, deviceId: String?, provider: SsoIdentityProvider?): String? {
|
||||||
return authenticationService.getSsoUrl(redirectUrl, deviceId, providerId)
|
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? {
|
fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String? {
|
||||||
|
|
|
@ -39,7 +39,7 @@ data class OnboardingViewState(
|
||||||
@PersistState
|
@PersistState
|
||||||
val signMode: SignMode = SignMode.Unknown,
|
val signMode: SignMode = SignMode.Unknown,
|
||||||
@PersistState
|
@PersistState
|
||||||
val resetPasswordEmail: String? = null,
|
val resetState: ResetState = ResetState(),
|
||||||
|
|
||||||
// For SSO session recovery
|
// For SSO session recovery
|
||||||
@PersistState
|
@PersistState
|
||||||
|
@ -51,6 +51,9 @@ data class OnboardingViewState(
|
||||||
@PersistState
|
@PersistState
|
||||||
val selectedHomeserver: SelectedHomeserverState = SelectedHomeserverState(),
|
val selectedHomeserver: SelectedHomeserverState = SelectedHomeserverState(),
|
||||||
|
|
||||||
|
@PersistState
|
||||||
|
val selectedAuthenticationState: SelectedAuthenticationState = SelectedAuthenticationState(),
|
||||||
|
|
||||||
@PersistState
|
@PersistState
|
||||||
val personalizationState: PersonalizationState = PersonalizationState()
|
val personalizationState: PersonalizationState = PersonalizationState()
|
||||||
) : MavericksState
|
) : MavericksState
|
||||||
|
@ -80,3 +83,14 @@ data class PersonalizationState(
|
||||||
|
|
||||||
fun supportsPersonalization() = supportsChangingDisplayName || supportsChangingProfilePicture
|
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 ->
|
final override fun invalidate() = withState(viewModel) { state ->
|
||||||
// True when email is sent with success to the homeserver
|
// True when email is sent with success to the homeserver
|
||||||
isResetPasswordStarted = state.resetPasswordEmail.isNullOrBlank().not()
|
isResetPasswordStarted = state.resetState.email.isNullOrBlank().not()
|
||||||
|
|
||||||
updateWithState(state)
|
updateWithState(state)
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,10 +90,10 @@ abstract class AbstractSSOFtueAuthFragment<VB : ViewBinding> : AbstractFtueAuthF
|
||||||
withState(viewModel) { state ->
|
withState(viewModel) { state ->
|
||||||
if (state.selectedHomeserver.preferredLoginMode.hasSso() && state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders().isNullOrEmpty()) {
|
if (state.selectedHomeserver.preferredLoginMode.hasSso() && state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders().isNullOrEmpty()) {
|
||||||
// in this case we can prefetch (not other cases for privacy concerns)
|
// in this case we can prefetch (not other cases for privacy concerns)
|
||||||
viewModel.getSsoUrl(
|
viewModel.fetchSsoUrl(
|
||||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||||
deviceId = state.deviceId,
|
deviceId = state.deviceId,
|
||||||
providerId = null
|
provider = null
|
||||||
)
|
)
|
||||||
?.let { prefetchUrl(it) }
|
?.let { prefetchUrl(it) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,10 +131,10 @@ class FtueAuthCombinedLoginFragment @Inject constructor(
|
||||||
views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true
|
views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true
|
||||||
views.ssoButtonsHeader.isVisible = views.ssoGroup.isVisible && views.loginEntryGroup.isVisible
|
views.ssoButtonsHeader.isVisible = views.ssoGroup.isVisible && views.loginEntryGroup.isVisible
|
||||||
views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id ->
|
views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id ->
|
||||||
viewModel.getSsoUrl(
|
viewModel.fetchSsoUrl(
|
||||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||||
deviceId = deviceId,
|
deviceId = deviceId,
|
||||||
providerId = id
|
provider = id
|
||||||
)?.let { openInCustomTab(it) }
|
)?.let { openInCustomTab(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,11 +164,11 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
|
||||||
|
|
||||||
private fun renderSsoProviders(deviceId: String?, ssoProviders: List<SsoIdentityProvider>?) {
|
private fun renderSsoProviders(deviceId: String?, ssoProviders: List<SsoIdentityProvider>?) {
|
||||||
views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true
|
views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true
|
||||||
views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id ->
|
views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { provider ->
|
||||||
viewModel.getSsoUrl(
|
viewModel.fetchSsoUrl(
|
||||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||||
deviceId = deviceId,
|
deviceId = deviceId,
|
||||||
providerId = id
|
provider = provider
|
||||||
)?.let { openInCustomTab(it) }
|
)?.let { openInCustomTab(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
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.isInvalidPassword
|
||||||
import org.matrix.android.sdk.api.failure.isInvalidUsername
|
import org.matrix.android.sdk.api.failure.isInvalidUsername
|
||||||
import org.matrix.android.sdk.api.failure.isLoginEmailUnknown
|
import org.matrix.android.sdk.api.failure.isLoginEmailUnknown
|
||||||
|
@ -216,11 +217,11 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
|
||||||
views.loginSocialLoginContainer.isVisible = true
|
views.loginSocialLoginContainer.isVisible = true
|
||||||
views.loginSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders?.sorted()
|
views.loginSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders?.sorted()
|
||||||
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
||||||
override fun onProviderSelected(id: String?) {
|
override fun onProviderSelected(provider: SsoIdentityProvider?) {
|
||||||
viewModel.getSsoUrl(
|
viewModel.fetchSsoUrl(
|
||||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||||
deviceId = state.deviceId,
|
deviceId = state.deviceId,
|
||||||
providerId = id
|
provider = provider
|
||||||
)
|
)
|
||||||
?.let { openInCustomTab(it) }
|
?.let { openInCustomTab(it) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ class FtueAuthResetPasswordMailConfirmationFragment @Inject constructor() : Abst
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupUi(state: OnboardingViewState) {
|
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() {
|
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.login.ssoIdentityProviders
|
||||||
import im.vector.app.features.onboarding.OnboardingAction
|
import im.vector.app.features.onboarding.OnboardingAction
|
||||||
import im.vector.app.features.onboarding.OnboardingViewState
|
import im.vector.app.features.onboarding.OnboardingViewState
|
||||||
|
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -81,11 +82,11 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF
|
||||||
views.loginSignupSigninSignInSocialLoginContainer.isVisible = true
|
views.loginSignupSigninSignInSocialLoginContainer.isVisible = true
|
||||||
views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders()?.sorted()
|
views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders()?.sorted()
|
||||||
views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
|
||||||
override fun onProviderSelected(id: String?) {
|
override fun onProviderSelected(provider: SsoIdentityProvider?) {
|
||||||
viewModel.getSsoUrl(
|
viewModel.fetchSsoUrl(
|
||||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||||
deviceId = state.deviceId,
|
deviceId = state.deviceId,
|
||||||
providerId = id
|
provider = provider
|
||||||
)
|
)
|
||||||
?.let { openInCustomTab(it) }
|
?.let { openInCustomTab(it) }
|
||||||
}
|
}
|
||||||
|
@ -123,10 +124,10 @@ class FtueAuthSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOF
|
||||||
|
|
||||||
private fun submit() = withState(viewModel) { state ->
|
private fun submit() = withState(viewModel) { state ->
|
||||||
if (state.selectedHomeserver.preferredLoginMode is LoginMode.Sso) {
|
if (state.selectedHomeserver.preferredLoginMode is LoginMode.Sso) {
|
||||||
viewModel.getSsoUrl(
|
viewModel.fetchSsoUrl(
|
||||||
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
|
||||||
deviceId = state.deviceId,
|
deviceId = state.deviceId,
|
||||||
providerId = null
|
provider = null
|
||||||
)
|
)
|
||||||
?.let { openInCustomTab(it) }
|
?.let { openInCustomTab(it) }
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -216,7 +216,7 @@ class FtueAuthVariant(
|
||||||
is OnboardingViewEvents.OnAccountCreated -> onAccountCreated()
|
is OnboardingViewEvents.OnAccountCreated -> onAccountCreated()
|
||||||
OnboardingViewEvents.OnAccountSignedIn -> onAccountSignedIn()
|
OnboardingViewEvents.OnAccountSignedIn -> onAccountSignedIn()
|
||||||
OnboardingViewEvents.OnChooseDisplayName -> onChooseDisplayName()
|
OnboardingViewEvents.OnChooseDisplayName -> onChooseDisplayName()
|
||||||
OnboardingViewEvents.OnTakeMeHome -> navigateToHome(createdAccount = true)
|
OnboardingViewEvents.OnTakeMeHome -> navigateToHome()
|
||||||
OnboardingViewEvents.OnChooseProfilePicture -> onChooseProfilePicture()
|
OnboardingViewEvents.OnChooseProfilePicture -> onChooseProfilePicture()
|
||||||
OnboardingViewEvents.OnPersonalizationComplete -> onPersonalizationComplete()
|
OnboardingViewEvents.OnPersonalizationComplete -> onPersonalizationComplete()
|
||||||
OnboardingViewEvents.OnBack -> activity.popBackstack()
|
OnboardingViewEvents.OnBack -> activity.popBackstack()
|
||||||
|
@ -467,7 +467,7 @@ class FtueAuthVariant(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onAccountSignedIn() {
|
private fun onAccountSignedIn() {
|
||||||
navigateToHome(createdAccount = false)
|
navigateToHome()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onAccountCreated() {
|
private fun onAccountCreated() {
|
||||||
|
@ -479,10 +479,12 @@ class FtueAuthVariant(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateToHome(createdAccount: Boolean) {
|
private fun navigateToHome() {
|
||||||
val intent = HomeActivity.newIntent(activity, accountCreation = createdAccount)
|
withState(onboardingViewModel) {
|
||||||
activity.startActivity(intent)
|
val intent = HomeActivity.newIntent(activity, authenticationDescription = it.selectedAuthenticationState.description)
|
||||||
activity.finish()
|
activity.startActivity(intent)
|
||||||
|
activity.finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onChooseDisplayName() {
|
private fun onChooseDisplayName() {
|
||||||
|
|
|
@ -65,7 +65,14 @@ data class E2EWellKnownConfig(
|
||||||
* clients should fallback to the default value of: ["key", "passphrase"].
|
* clients should fallback to the default value of: ["key", "passphrase"].
|
||||||
*/
|
*/
|
||||||
@Json(name = "secure_backup_setup_methods")
|
@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)
|
@JsonClass(generateAdapter = true)
|
||||||
|
|
|
@ -16,6 +16,11 @@
|
||||||
|
|
||||||
package im.vector.app.features.raw.wellknown
|
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.MatrixPatterns.getServerName
|
||||||
import org.matrix.android.sdk.api.auth.data.SessionParams
|
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
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.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
|
fun ElementWellKnown.isSecureBackupRequired() = elementE2E?.secureBackupRequired
|
||||||
?: riotE2E?.secureBackupRequired
|
?: riotE2E?.secureBackupRequired
|
||||||
?: false
|
?: false
|
||||||
|
|
|
@ -151,12 +151,14 @@ class AudioWaveformView @JvmOverloads constructor(
|
||||||
|
|
||||||
private fun handleNewFftList(fftList: List<FFT>) {
|
private fun handleNewFftList(fftList: List<FFT>) {
|
||||||
val maxVisibleBarCount = getMaxVisibleBarCount()
|
val maxVisibleBarCount = getMaxVisibleBarCount()
|
||||||
|
|
||||||
fftList.forEach { fft ->
|
fftList.forEach { fft ->
|
||||||
rawFftList.add(fft)
|
rawFftList.add(fft)
|
||||||
val barHeight = max(fft.value / MAX_FFT * (height - verticalPadding * 2), barMinHeight)
|
val barHeight = max(fft.value / MAX_FFT * (height - verticalPadding * 2), barMinHeight)
|
||||||
visibleBarHeights.add(FFT(barHeight, fft.color))
|
visibleBarHeights.add(FFT(barHeight, fft.color))
|
||||||
|
|
||||||
if (visibleBarHeights.size > maxVisibleBarCount) {
|
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_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:textColor="?vctr_content_primary"
|
android:textColor="?vctr_content_primary"
|
||||||
android:textStyle="bold"
|
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.FakeDirectLoginUseCase
|
||||||
import im.vector.app.test.fakes.FakeHomeServerConnectionConfigFactory
|
import im.vector.app.test.fakes.FakeHomeServerConnectionConfigFactory
|
||||||
import im.vector.app.test.fakes.FakeHomeServerHistoryService
|
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.FakeRegisterActionHandler
|
||||||
import im.vector.app.test.fakes.FakeRegistrationWizard
|
import im.vector.app.test.fakes.FakeRegistrationWizard
|
||||||
import im.vector.app.test.fakes.FakeSession
|
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 const val A_HOMESERVER_URL = "https://edited-homeserver.org"
|
||||||
private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(FakeUri().instance)
|
private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(FakeUri().instance)
|
||||||
private val SELECTED_HOMESERVER_STATE = SelectedHomeserverState(preferredLoginMode = LoginMode.Password)
|
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 {
|
class OnboardingViewModelTest {
|
||||||
|
|
||||||
|
@ -85,6 +88,7 @@ class OnboardingViewModelTest {
|
||||||
private val fakeHomeServerConnectionConfigFactory = FakeHomeServerConnectionConfigFactory()
|
private val fakeHomeServerConnectionConfigFactory = FakeHomeServerConnectionConfigFactory()
|
||||||
private val fakeStartAuthenticationFlowUseCase = FakeStartAuthenticationFlowUseCase()
|
private val fakeStartAuthenticationFlowUseCase = FakeStartAuthenticationFlowUseCase()
|
||||||
private val fakeHomeServerHistoryService = FakeHomeServerHistoryService()
|
private val fakeHomeServerHistoryService = FakeHomeServerHistoryService()
|
||||||
|
private val fakeLoginWizard = FakeLoginWizard()
|
||||||
|
|
||||||
private var initialState = OnboardingViewState()
|
private var initialState = OnboardingViewState()
|
||||||
private lateinit var viewModel: OnboardingViewModel
|
private lateinit var viewModel: OnboardingViewModel
|
||||||
|
@ -466,6 +470,43 @@ class OnboardingViewModelTest {
|
||||||
.finish()
|
.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) {
|
private fun viewModelWith(state: OnboardingViewState) {
|
||||||
OnboardingViewModel(
|
OnboardingViewModel(
|
||||||
state,
|
state,
|
||||||
|
|
|
@ -23,6 +23,7 @@ import io.mockk.mockk
|
||||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
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.HomeServerConnectionConfig
|
||||||
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
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.registration.RegistrationWizard
|
||||||
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
|
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
|
||||||
|
|
||||||
|
@ -36,6 +37,10 @@ class FakeAuthenticationService : AuthenticationService by mockk() {
|
||||||
every { isRegistrationStarted() } returns started
|
every { isRegistrationStarted() } returns started
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun givenLoginWizard(loginWizard: LoginWizard) {
|
||||||
|
every { getLoginWizard() } returns loginWizard
|
||||||
|
}
|
||||||
|
|
||||||
fun givenLoginFlow(config: HomeServerConnectionConfig, result: LoginFlowResult) {
|
fun givenLoginFlow(config: HomeServerConnectionConfig, result: LoginFlowResult) {
|
||||||
coEvery { getLoginFlow(config) } returns result
|
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