Merge branch 'develop' into feature/aris/thread_live_thread_list

# Conflicts:
#	matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt
This commit is contained in:
ariskotsomitopoulos 2022-03-03 13:56:59 +02:00
commit e4282e5f29
212 changed files with 5434 additions and 32625 deletions

View File

@ -20,6 +20,10 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
target: [ Gplay, Fdroid ] target: [ Gplay, Fdroid ]
# Allow all jobs on develop. Just one per PR.
concurrency:
group: ${{ github.ref == 'refs/heads/develop' && format('integration-tests-develop-{0}-{1}', matrix.target, github.sha) || format('build-debug-{0}-{1}', matrix.target, github.ref) }}
cancel-in-progress: true
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/cache@v2 - uses: actions/cache@v2
@ -43,6 +47,7 @@ jobs:
name: Build unsigned GPlay APKs name: Build unsigned GPlay APKs
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' if: github.ref == 'refs/heads/main'
# Only runs on main, no concurrency.
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/cache@v2 - uses: actions/cache@v2

View File

@ -5,6 +5,7 @@ jobs:
validation: validation:
name: "Validation" name: "Validation"
runs-on: ubuntu-latest runs-on: ubuntu-latest
# No concurrency required, this is a prerequisite to other actions and should run every time.
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1 - uses: gradle/wrapper-validation-action@v1

View File

@ -1,200 +0,0 @@
name: Integration Tests
on:
pull_request: { }
push:
branches: [ main, develop ]
# Enrich gradle.properties for CI/CD
env:
CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx2g
-Porg.gradle.parallel=false
jobs:
# Build Android Tests [Matrix SDK]
build-android-test-matrix-sdk:
name: Matrix SDK - Build Android Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build Android Tests for matrix-sdk-android
run: ./gradlew clean matrix-sdk-android:assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace -PallWarningsAsErrors=false
# Build Android Tests [Matrix APP]
build-android-test-app:
name: App - Build Android Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build Android Tests for vector
run: ./gradlew clean vector:assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace -PallWarningsAsErrors=false
# Run Android Tests
integration-tests:
name: Matrix SDK - Running Integration Tests
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
api-level: [ 28 ]
steps:
- uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: 11
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Start synapse server
run: |
pip install matrix-synapse
curl https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh -o start.sh
chmod 777 start.sh
./start.sh --no-rate-limit
# package: org.matrix.android.sdk.session
- name: Run integration tests for Matrix SDK [org.matrix.android.sdk.session] API[${{ matrix.api-level }}]
continue-on-error: true
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: ./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]
continue-on-error: true
id: get-comment-body-session
run: |
body="$(cat ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml | grep "<testsuite" | sed "s@.*tests=\(.*\)time=.*@\1@")"
echo "::set-output name=session::passed=$body"
# package: org.matrix.android.sdk.account
- name: Run integration tests for Matrix SDK [org.matrix.android.sdk.account] API[${{ matrix.api-level }}]
continue-on-error: true
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: ./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]
continue-on-error: true
id: get-comment-body-account
run: |
body="$(cat ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml | grep "<testsuite" | sed "s@.*tests=\(.*\)time=.*@\1@")"
echo "::set-output name=account::passed=$body"
# package: org.matrix.android.sdk.internal
- name: Run integration tests for Matrix SDK [org.matrix.android.sdk.internal] API[${{ matrix.api-level }}]
continue-on-error: true
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: ./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]
continue-on-error: true
id: get-comment-body-internal
run: |
body="$(cat ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml | grep "<testsuite" | sed "s@.*tests=\(.*\)time=.*@\1@")"
echo "::set-output name=internal::passed=$body"
# package: org.matrix.android.sdk.ordering
- name: Run integration tests for Matrix SDK [org.matrix.android.sdk.ordering] API[${{ matrix.api-level }}]
continue-on-error: true
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: ./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]
continue-on-error: true
id: get-comment-body-ordering
run: |
body="$(cat ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml | grep "<testsuite" | sed "s@.*tests=\(.*\)time=.*@\1@")"
echo "::set-output name=ordering::passed=$body"
# package: class PermalinkParserTest
- name: Run integration tests for Matrix SDK class [org.matrix.android.sdk.PermalinkParserTest] API[${{ matrix.api-level }}]
continue-on-error: true
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: ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.class='org.matrix.android.sdk.PermalinkParserTest' matrix-sdk-android:connectedDebugAndroidTest
- name: Read Results [org.matrix.android.sd.PermalinkParserTest]
continue-on-error: true
id: get-comment-body-permalink
run: |
body="$(cat ./matrix-sdk-android/build/outputs/androidTest-results/connected/*.xml | grep "<testsuite" | sed "s@.*tests=\(.*\)time=.*@\1@")"
echo "::set-output name=permalink::passed=$body"
- name: Find Comment
if: github.event_name == 'pull_request'
uses: peter-evans/find-comment@v1
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: github.event_name == 'pull_request'
uses: peter-evans/create-or-update-comment@v1
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
## Useful commands
# script: ./integration_tests_script.sh
# script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.package='org.matrix.android.sdk.session' matrix-sdk-android:connectedDebugAndroidTest --info
# script: ./gradlew $CI_GRADLE_ARG_PROPERTIES matrix-sdk-android:connectedAndroidTest --info
# script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedCheck --stacktrace
# script: ./gradlew $CI_GRADLE_ARG_PROPERTIES -Pandroid.testInstrumentationRunnerArguments.class=org.matrix.android.sdk.session.room.timeline.ChunkEntityTest matrix-sdk-android:connectedAndroidTest --info

329
.github/workflows/nightly.yml vendored Normal file
View File

@ -0,0 +1,329 @@
name: Nightly Tests
on:
push:
branches: [ release/* ]
schedule:
# At 20:00 every day UTC
- cron: '0 20 * * *'
workflow_dispatch:
# Enrich gradle.properties for CI/CD
env:
CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx4g
-Porg.gradle.parallel=false
-PallWarningsAsErrors=false
jobs:
# Build Android Tests [Matrix SDK]
build-android-test-matrix-sdk:
name: Matrix SDK - Build Android Tests
runs-on: macos-latest
# No concurrency required, runs every time on a schedule.
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: 11
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build Android Tests for matrix-sdk-android
run: ./gradlew clean matrix-sdk-android:assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace
# Build Android Tests [Matrix APP]
build-android-test-app:
name: App - Build Android Tests
runs-on: macos-latest
# No concurrency required, runs every time on a schedule.
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: 11
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build Android Tests for vector
run: ./gradlew clean vector:assembleAndroidTest $CI_GRADLE_ARG_PROPERTIES --stacktrace
# Run Android Tests
integration-tests:
name: Matrix SDK - Running Integration Tests
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
api-level: [ 28 ]
# No concurrency required, runs every time on a schedule.
steps:
- uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v2
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@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Start synapse server
run: |
pip install matrix-synapse
curl https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh -o start.sh
chmod 777 start.sh
./start.sh --no-rate-limit
# 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@v1
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@v1
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@v2
if: always()
with:
name: integrationtest-error-results
path: |
emulator-permalink.log
emulator-internal.log
emulator-ordering.log
emulator-account.log
emulator-session.log
ui-tests:
name: UI Tests (Synapse)
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
api-level: [ 28 ]
# No concurrency required, runs every time on a schedule.
steps:
- uses: actions/checkout@v2
with:
ref: develop
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.8
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Start synapse server
run: |
pip install matrix-synapse
curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh \
| sed s/127.0.0.1/0.0.0.0/g | sed 's/http:\/\/localhost/http:\/\/10.0.2.2/g' | bash -s -- --no-rate-limit
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: '11'
- name: Run sanity tests on 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 # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
script: |
adb root
adb logcat -c
touch emulator.log
chmod 777 emulator.log
adb logcat >> emulator.log &
./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || (adb pull storage/emulated/0/Pictures/failure_screenshots && exit 1 )
- name: Upload Test Report Log
uses: actions/upload-artifact@v2
if: always()
with:
name: uitest-error-results
path: |
emulator.log
failure_screenshots/
# Notify the channel about scheduled runs, do not notify for manually triggered runs
notify:
runs-on: ubuntu-latest
needs:
- integration-tests
- ui-tests
if: always() && github.event_name != 'workflow_dispatch'
# No concurrency required, runs every time on a schedule.
steps:
- uses: michaelkaye/matrix-hookshot-action@v0.3.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
matrix_access_token: ${{ secrets.ELEMENT_ANDROID_NOTIFICATION_ACCESS_TOKEN }}
matrix_room_id: ${{ secrets.ELEMENT_ANDROID_INTERNAL_ROOM_ID }}
text_template: "Nightly test run: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}}, {{/if}}{{/with}}{{/each}}"
html_template: "Nightly test run results: {{#each job_statuses }}{{#with this }}{{#if completed }}<br />{{name}} {{conclusion}} at {{completed_at}} <a href=\"{{html_url}}\">[details]</a>{{/if}}{{/with}}{{/each}}"

View File

@ -18,6 +18,10 @@ jobs:
ktlint: ktlint:
name: Kotlin Linter name: Kotlin Linter
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Allow all jobs on main and develop. Just one per PR.
concurrency:
group: ${{ github.ref == 'refs/heads/main' && format('ktlint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('ktlint-develop-{0}', github.sha) || format('ktlint-{0}', github.ref) }}
cancel-in-progress: true
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Run ktlint - name: Run ktlint
@ -87,6 +91,10 @@ jobs:
android-lint: android-lint:
name: Android Linter name: Android Linter
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Allow all jobs on main and develop. Just one per PR.
concurrency:
group: ${{ github.ref == 'refs/heads/main' && format('android-lint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('android-lint-develop-{0}', github.sha) || format('android-lint-{0}', github.ref) }}
cancel-in-progress: true
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/cache@v2 - uses: actions/cache@v2
@ -116,6 +124,10 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
target: [ Gplay, Fdroid ] target: [ Gplay, Fdroid ]
# Allow all jobs on develop. Just one per PR.
concurrency:
group: ${{ github.ref == 'refs/heads/develop' && format('apk-lint-develop-{0}-{1}', matrix.target, github.sha) || format('apk-lint-{0}-{1}', matrix.target, github.ref) }}
cancel-in-progress: true
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/cache@v2 - uses: actions/cache@v2

View File

@ -1,84 +0,0 @@
name: Sanity Test
on:
schedule:
# At 20:00 every day UTC
- cron: '0 20 * * *'
# Enrich gradle.properties for CI/CD
env:
CI_GRADLE_ARG_PROPERTIES: >
-Porg.gradle.jvmargs=-Xmx4g
-Porg.gradle.parallel=false
jobs:
integration-tests:
name: Sanity Tests (Synapse)
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
api-level: [ 28 ]
steps:
- uses: actions/checkout@v2
with:
ref: develop
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Start synapse server
run: |
pip install matrix-synapse
curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh \
| sed s/127.0.0.1/0.0.0.0/g | sed 's/http:\/\/localhost/http:\/\/10.0.2.2/g' | bash -s -- --no-rate-limit
- uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: '11'
- name: Run sanity tests on 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 # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
script: |
adb root
adb logcat -c
touch emulator.log
chmod 777 emulator.log
adb logcat >> emulator.log &
./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || (adb pull storage/emulated/0/Pictures/failure_screenshots && exit 1 )
- name: Upload Test Report Log
uses: actions/upload-artifact@v2
if: always()
with:
name: sanity-error-results
path: |
emulator.log
failure_screenshots/
notify:
runs-on: ubuntu-latest
needs: integration-tests
if: always()
steps:
- uses: michaelkaye/matrix-hookshot-action@v0.2.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
matrix_access_token: ${{ secrets.ELEMENT_ANDROID_NOTIFICATION_ACCESS_TOKEN }}
matrix_room_id: ${{ secrets.ELEMENT_ANDROID_INTERNAL_ROOM_ID }}
text_template: "Sanity test run: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}} {{html_url}}{{/if}}{{/with}}{{/each}}"
html_template: "CI Sanity test run results: {{#each job_statuses }}{{#with this }}{{#if completed }} {{name}} {{conclusion}} at {{completed_at}} <a href=\"{{html_url}}\">[details]</a>{{/if}}{{/with}}{{/each}}"

View File

@ -9,10 +9,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Skip in forks # Skip in forks
if: github.repository == 'vector-im/element-android' if: github.repository == 'vector-im/element-android'
# No concurrency required, runs every time on a schedule.
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.8 - name: Set up Python 3.8
uses: actions/setup-python@v2 uses: actions/setup-python@v3
with: with:
python-version: 3.8 python-version: 3.8
- name: Install Prerequisite dependencies - name: Install Prerequisite dependencies
@ -35,10 +36,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Skip in forks # Skip in forks
if: github.repository == 'vector-im/element-android' if: github.repository == 'vector-im/element-android'
# No concurrency required, runs every time on a schedule.
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.8 - name: Set up Python 3.8
uses: actions/setup-python@v2 uses: actions/setup-python@v3
with: with:
python-version: 3.8 python-version: 3.8
- name: Install Prerequisite dependencies - name: Install Prerequisite dependencies
@ -60,6 +62,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Skip in forks # Skip in forks
if: github.repository == 'vector-im/element-android' if: github.repository == 'vector-im/element-android'
# No concurrency required, runs every time on a schedule.
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Run analytics import script - name: Run analytics import script

View File

@ -15,6 +15,10 @@ jobs:
unit-tests: unit-tests:
name: Run Unit Tests name: Run Unit Tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Allow all jobs on main and develop. Just one per PR.
concurrency:
group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('unit-tests-{0}', github.ref) }}
cancel-in-progress: true
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/cache@v2 - uses: actions/cache@v2

View File

@ -14,7 +14,7 @@ It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-androi
[<img src="resources/img/google-play-badge.png" alt="Get it on Google Play" height="60">](https://play.google.com/store/apps/details?id=im.vector.app) [<img src="resources/img/google-play-badge.png" alt="Get it on Google Play" height="60">](https://play.google.com/store/apps/details?id=im.vector.app)
[<img src="resources/img/f-droid-badge.png" alt="Get it on F-Droid" height="60">](https://f-droid.org/app/im.vector.app) [<img src="resources/img/f-droid-badge.png" alt="Get it on F-Droid" height="60">](https://f-droid.org/app/im.vector.app)
Nightly build: [![Buildkite](https://badge.buildkite.com/ad0065c1b70f557cd3b1d3d68f9c2154010f83c4d6f71706a9.svg?branch=develop)](https://buildkite.com/matrix-dot-org/element-android/builds?branch=develop) Nighly sanity test status: [![allScreensTest](https://github.com/vector-im/element-android/actions/workflows/sanity_test.yml/badge.svg)](https://github.com/vector-im/element-android/actions/workflows/sanity_test.yml) Nightly build: [![Buildkite](https://badge.buildkite.com/ad0065c1b70f557cd3b1d3d68f9c2154010f83c4d6f71706a9.svg?branch=develop)](https://buildkite.com/matrix-dot-org/element-android/builds?branch=develop) Nighly test status: [![allScreensTest](https://github.com/vector-im/element-android/actions/workflows/nightly.yml/badge.svg)](https://github.com/vector-im/element-android/actions/workflows/nightly.yml)
# New Android SDK # New Android SDK

View File

@ -19,8 +19,8 @@ buildscript {
classpath libs.gradle.hiltPlugin classpath libs.gradle.hiltPlugin
classpath 'com.google.gms:google-services:4.3.10' classpath 'com.google.gms:google-services:4.3.10'
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3' classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3'
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.4' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5'
classpath "com.likethesalad.android:string-reference:1.2.2" classpath "com.likethesalad.android:stem-plugin:2.0.0"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
@ -144,11 +144,6 @@ project(":library:diff-match-patch") {
} }
} }
// Global configurations across all modules
ext {
isThreadingEnabled = true
}
//project(":matrix-sdk-android") { //project(":matrix-sdk-android") {
// sonarqube { // sonarqube {
// properties { // properties {

1
changelog.d/4319.bugfix Normal file
View File

@ -0,0 +1 @@
Open direct message screen when clicking on DM button in the space members list

1
changelog.d/5005.feature Normal file
View File

@ -0,0 +1 @@
Add possibility to save media from Gallery + reorder choices in message context menu

1
changelog.d/5325.feature Normal file
View File

@ -0,0 +1 @@
Adds forceLoginFallback feature flag and usages to FTUE login and registration

1
changelog.d/5326.misc Normal file
View File

@ -0,0 +1 @@
[Export e2ee keys] use appName instead of element

1
changelog.d/5330.misc Normal file
View File

@ -0,0 +1 @@
Continue improving realm usage.

1
changelog.d/5330.sdk Normal file
View File

@ -0,0 +1 @@
Change name of getTimeLineEvent and getTimeLineEventLive methods to getTimelineEvent and getTimelineEventLive.

1
changelog.d/5348.misc Normal file
View File

@ -0,0 +1 @@
Upgrade the plugin which generate strings with template from 1.2.2 to 2.0.0

1
changelog.d/5352.misc Normal file
View File

@ -0,0 +1 @@
Remove about 700 unused strings and their translations

1
changelog.d/5361.misc Normal file
View File

@ -0,0 +1 @@
Creates dedicated VectorOverrides for forcing behaviour for local testing/development

1
changelog.d/5379.misc Normal file
View File

@ -0,0 +1 @@
Cleanup unused threads build configurations

1
changelog.d/5392.misc Normal file
View File

@ -0,0 +1 @@
Upgrades material dependency version from 1.4.0 to 1.5.0

1
changelog.d/5394.bugfix Normal file
View File

@ -0,0 +1 @@
Fix incorrect media cache size in settings

View File

@ -71,8 +71,7 @@ ext.libs = [
'espressoIntents' : "androidx.test.espresso:espresso-intents:$espresso" 'espressoIntents' : "androidx.test.espresso:espresso-intents:$espresso"
], ],
google : [ google : [
// TODO There is 1.6.0? 'material' : "com.google.android.material:material:1.5.0"
'material' : "com.google.android.material:material:1.4.0"
], ],
dagger : [ dagger : [
'dagger' : "com.google.dagger:dagger:$dagger", 'dagger' : "com.google.dagger:dagger:$dagger",

View File

@ -175,6 +175,7 @@ ext.groups = [
'org.sonatype.oss', 'org.sonatype.oss',
'org.testng', 'org.testng',
'org.threeten', 'org.threeten',
'org.webjars',
'ru.noties', 'ru.noties',
'xerces', 'xerces',
'xml-apis', 'xml-apis',

View File

@ -0,0 +1,2 @@
Hlavní změny v této verzi: Počáteční implementace vláken zpráv. Bubliny zpráv.
Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.4.0

View File

@ -0,0 +1,2 @@
Hlavní změny v této verzi: přidána podpora pro @room a tajné hlasování a mnoho dalších drobných změn
Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.4.2

View File

@ -0,0 +1,2 @@
Põhilised muutused selles versioonis: jutulõngade esmane lahendus ja jutumullid.
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.4.0

View File

@ -0,0 +1,2 @@
Põhilised muutused selles versioonis: @jututuba tugi, mitteavalikud küsitlused ning pisiparandused.
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.4.2

View File

@ -0,0 +1,2 @@
Fő változás ebben a verzióban: Üzenetszálak kezdeti implementációja. Buborék üzenetek.
Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.4.0

View File

@ -0,0 +1,2 @@
Fő változás ebben a verzióban: @room támogatás és nem nyilvános szavazások mellett kisebb változtatások.
Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.4.2

View File

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: Implementasi awal perpesanan utasan. Gelembung pesan.
Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.4.0

View File

@ -0,0 +1,2 @@
Perubahan utama dalam versi ini: tambahkan dukungan untuk @room dan pemungutan suara tertutup dan banyak perubahan kecil lainnya.
Catatan perubahan lanjutan: https://github.com/vector-im/element-android/releases/tag/v1.4.2

View File

@ -0,0 +1,2 @@
Modifiche principali in questa versione: prima implementazione delle discussioni. Messaggi a bolla.
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.4.0

View File

@ -0,0 +1,2 @@
Modifiche principali in questa versione: aggiunto supporto a @stanza e sondaggi non divulgati, molte altre modifiche.
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.4.2

View File

@ -0,0 +1,2 @@
Principais mudanças nesta versão: Implementação inicial de mensagens de thread. Bolhas de mensagem.
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.4.0

View File

@ -0,0 +1,2 @@
Principais mudanças nesta versão: adicionar suporte para @room e sondagens não-divulgadas entre muitas outras pequenas mudanças.
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.4.2

View File

@ -0,0 +1,2 @@
Hlavné zmeny v tejto verzii: Počiatočná implementácia správ vo vláknach. Správy v bublinách.
Úplný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.4.0

View File

@ -0,0 +1,2 @@
Hlavné zmeny v tejto verzii: pridanie podpory pre @miestnost a nezverejnené ankety a mnoho ďalších drobných zmien.
Úplný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.4.2

View File

@ -0,0 +1,2 @@
Основні зміни в цій версії: Початкова реалізація тредів повідомлень. Повідомлення бульбашки.
Вичерпний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.4.0

View File

@ -0,0 +1,2 @@
Основні зміни в цій версії: додано підтримку до @room і нерозкритих опитувань та багато інших незначних змін.
Вичерпний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.4.2

View File

@ -0,0 +1,2 @@
此版本中的主要變動:訊息討論串的初始實作。訊息泡泡。
完整的變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.4.0

View File

@ -0,0 +1,2 @@
此版本中的主要變動:新增對 @room 的支援與未公開的投票,以及其他許多小變動。
完整的變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.4.2

View File

@ -45,6 +45,8 @@ import kotlin.math.abs
abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventListener { abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventListener {
protected val rootView: View
get() = views.rootContainer
protected val pager2: ViewPager2 protected val pager2: ViewPager2
get() = views.attachmentPager get() = views.attachmentPager
protected val imageTransitionView: ImageView protected val imageTransitionView: ImageView
@ -298,10 +300,11 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
private fun createSwipeToDismissHandler(): SwipeToDismissHandler = private fun createSwipeToDismissHandler(): SwipeToDismissHandler =
SwipeToDismissHandler( SwipeToDismissHandler(
swipeView = views.dismissContainer, swipeView = views.dismissContainer,
shouldAnimateDismiss = { shouldAnimateDismiss() }, shouldAnimateDismiss = { shouldAnimateDismiss() },
onDismiss = { animateClose() }, onDismiss = { animateClose() },
onSwipeViewMove = ::handleSwipeViewMove) onSwipeViewMove = ::handleSwipeViewMove
)
private fun createSwipeDirectionDetector() = private fun createSwipeDirectionDetector() =
SwipeDirectionDetector(this) { swipeDirection = it } SwipeDirectionDetector(this) { swipeDirection = it }

View File

@ -59,9 +59,9 @@ class FlowRoom(private val room: Room) {
} }
fun liveTimelineEvent(eventId: String): Flow<Optional<TimelineEvent>> { fun liveTimelineEvent(eventId: String): Flow<Optional<TimelineEvent>> {
return room.getTimeLineEventLive(eventId).asFlow() return room.getTimelineEventLive(eventId).asFlow()
.startWith(room.coroutineDispatchers.io) { .startWith(room.coroutineDispatchers.io) {
room.getTimeLineEvent(eventId).toOptional() room.getTimelineEvent(eventId).toOptional()
} }
} }

View File

@ -38,8 +38,6 @@ android {
resValue "string", "git_sdk_revision_unix_date", "\"${gitRevisionUnixDate()}\"" resValue "string", "git_sdk_revision_unix_date", "\"${gitRevisionUnixDate()}\""
resValue "string", "git_sdk_revision_date", "\"${gitRevisionDate()}\"" resValue "string", "git_sdk_revision_date", "\"${gitRevisionDate()}\""
// Indicates whether or not threading support is enabled
buildConfigField "Boolean", "THREADING_ENABLED", "${isThreadingEnabled}"
defaultConfig { defaultConfig {
consumerProguardFiles 'proguard-rules.pro' consumerProguardFiles 'proguard-rules.pro'
} }
@ -169,7 +167,7 @@ dependencies {
implementation libs.apache.commonsImaging implementation libs.apache.commonsImaging
// Phone number https://github.com/google/libphonenumber // Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.43' implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.44'
testImplementation libs.tests.junit testImplementation libs.tests.junit
testImplementation 'org.robolectric:robolectric:4.7.3' testImplementation 'org.robolectric:robolectric:4.7.3'

View File

@ -95,7 +95,7 @@ class PreShareKeysTest : InstrumentedTest {
assertEquals(megolmSessionId, sentEvent.root.content.toModel<EncryptedEventContent>()?.sessionId, "Unexpected megolm session") assertEquals(megolmSessionId, sentEvent.root.content.toModel<EncryptedEventContent>()?.sessionId, "Unexpected megolm session")
testHelper.waitWithLatch { latch -> testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) { testHelper.retryPeriodicallyWithLatch(latch) {
bobSession.getRoom(e2eRoomID)?.getTimeLineEvent(sentEvent.eventId)?.root?.getClearType() == EventType.MESSAGE bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEvent.eventId)?.root?.getClearType() == EventType.MESSAGE
} }
} }

View File

@ -92,7 +92,7 @@ class KeyShareTests : InstrumentedTest {
val roomSecondSessionPOV = aliceSession2.getRoom(roomId) val roomSecondSessionPOV = aliceSession2.getRoom(roomId)
val receivedEvent = roomSecondSessionPOV?.getTimeLineEvent(sentEventId) val receivedEvent = roomSecondSessionPOV?.getTimelineEvent(sentEventId)
assertNotNull(receivedEvent) assertNotNull(receivedEvent)
assert(receivedEvent!!.isEncrypted()) assert(receivedEvent!!.isEncrypted())
@ -382,7 +382,7 @@ class KeyShareTests : InstrumentedTest {
commonTestHelper.sendTextMessage(roomAlicePov, "After", 1) commonTestHelper.sendTextMessage(roomAlicePov, "After", 1)
val roomRoomBobPov = aliceSession.getRoom(roomId) val roomRoomBobPov = aliceSession.getRoom(roomId)
val beforeJoin = roomRoomBobPov!!.getTimeLineEvent(secondEventId) val beforeJoin = roomRoomBobPov!!.getTimelineEvent(secondEventId)
var dRes = tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin!!.root, "") } var dRes = tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin!!.root, "") }

View File

@ -80,11 +80,11 @@ class WithHeldTests : InstrumentedTest {
// await for bob unverified session to get the message // await for bob unverified session to get the message
testHelper.waitWithLatch { latch -> testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) { testHelper.retryPeriodicallyWithLatch(latch) {
bobUnverifiedSession.getRoom(roomId)?.getTimeLineEvent(timelineEvent.eventId) != null bobUnverifiedSession.getRoom(roomId)?.getTimelineEvent(timelineEvent.eventId) != null
} }
} }
val eventBobPOV = bobUnverifiedSession.getRoom(roomId)?.getTimeLineEvent(timelineEvent.eventId)!! val eventBobPOV = bobUnverifiedSession.getRoom(roomId)?.getTimelineEvent(timelineEvent.eventId)!!
// ============================= // =============================
// ASSERT // ASSERT
@ -109,7 +109,7 @@ class WithHeldTests : InstrumentedTest {
testHelper.waitWithLatch { latch -> testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) { testHelper.retryPeriodicallyWithLatch(latch) {
val ev = bobUnverifiedSession.getRoom(roomId)?.getTimeLineEvent(secondEvent.eventId) val ev = bobUnverifiedSession.getRoom(roomId)?.getTimelineEvent(secondEvent.eventId)
// wait until it's decrypted // wait until it's decrypted
ev?.root?.getClearType() == EventType.MESSAGE ev?.root?.getClearType() == EventType.MESSAGE
} }
@ -157,12 +157,12 @@ class WithHeldTests : InstrumentedTest {
// await for bob session to get the message // await for bob session to get the message
testHelper.waitWithLatch { latch -> testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) { testHelper.retryPeriodicallyWithLatch(latch) {
bobSession.getRoom(testData.roomId)?.getTimeLineEvent(eventId) != null bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId) != null
} }
} }
// Previous message should still be undecryptable (partially withheld session) // Previous message should still be undecryptable (partially withheld session)
val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimeLineEvent(eventId) val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)
try { try {
// .. might need to wait a bit for stability? // .. might need to wait a bit for stability?
bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "") bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "")
@ -190,7 +190,7 @@ class WithHeldTests : InstrumentedTest {
// await for bob SecondSession session to get the message // await for bob SecondSession session to get the message
testHelper.waitWithLatch { latch -> testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) { testHelper.retryPeriodicallyWithLatch(latch) {
bobSecondSession.getRoom(testData.roomId)?.getTimeLineEvent(secondMessageId) != null bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(secondMessageId) != null
} }
} }
@ -231,7 +231,7 @@ class WithHeldTests : InstrumentedTest {
// await for bob SecondSession session to get the message // await for bob SecondSession session to get the message
testHelper.waitWithLatch { latch -> testHelper.waitWithLatch { latch ->
testHelper.retryPeriodicallyWithLatch(latch) { testHelper.retryPeriodicallyWithLatch(latch) {
val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimeLineEvent(eventId)?.also { val timeLineEvent = bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)?.also {
// try to decrypt and force key request // try to decrypt and force key request
tryOrNull { bobSecondSession.cryptoService().decryptEvent(it.root, "") } tryOrNull { bobSecondSession.cryptoService().decryptEvent(it.root, "") }
} }

View File

@ -22,6 +22,7 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
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
@ -31,6 +32,7 @@ import org.matrix.android.sdk.common.assertByteArrayNotEqual
import org.matrix.olm.OlmManager import org.matrix.olm.OlmManager
import org.matrix.olm.OlmPkDecryption import org.matrix.olm.OlmPkDecryption
@Ignore("Ignored in order to speed up test run time")
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM) @FixMethodOrder(MethodSorters.JVM)
class KeysBackupPasswordTest : InstrumentedTest { class KeysBackupPasswordTest : InstrumentedTest {

View File

@ -117,5 +117,5 @@ interface FileService {
/** /**
* Get size of cached files * Get size of cached files
*/ */
fun getCacheSize(): Int fun getCacheSize(): Long
} }

View File

@ -41,7 +41,7 @@ interface TimelineService {
* At the opposite of getTimeLineEventLive which will be updated when local echo event is synced, it will return null in this case. * At the opposite of getTimeLineEventLive which will be updated when local echo event is synced, it will return null in this case.
* @param eventId the eventId to get the TimelineEvent * @param eventId the eventId to get the TimelineEvent
*/ */
fun getTimeLineEvent(eventId: String): TimelineEvent? fun getTimelineEvent(eventId: String): TimelineEvent?
/** /**
* Creates a LiveData of Optional TimelineEvent event with eventId. * Creates a LiveData of Optional TimelineEvent event with eventId.
@ -49,7 +49,7 @@ interface TimelineService {
* In this case, makes sure to use the new synced eventId from the TimelineEvent class if you want to interact, as the local echo is removed from the SDK. * In this case, makes sure to use the new synced eventId from the TimelineEvent class if you want to interact, as the local echo is removed from the SDK.
* @param eventId the eventId to listen for TimelineEvent * @param eventId the eventId to listen for TimelineEvent
*/ */
fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>> fun getTimelineEventLive(eventId: String): LiveData<Optional<TimelineEvent>>
/** /**
* Returns a snapshot list of TimelineEvent with EventType.MESSAGE and MessageType.MSGTYPE_IMAGE or MessageType.MSGTYPE_VIDEO. * Returns a snapshot list of TimelineEvent with EventType.MESSAGE and MessageType.MSGTYPE_IMAGE or MessageType.MSGTYPE_VIDEO.

View File

@ -16,8 +16,11 @@
package org.matrix.android.sdk.internal.database.mapper package org.matrix.android.sdk.internal.database.mapper
import io.realm.Realm
import io.realm.RealmList
import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.model.ReadReceipt
import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.RealmSessionProvider
import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity
import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
@ -32,14 +35,22 @@ internal class ReadReceiptsSummaryMapper @Inject constructor(
return emptyList() return emptyList()
} }
val readReceipts = readReceiptsSummaryEntity.readReceipts val readReceipts = readReceiptsSummaryEntity.readReceipts
// Avoid opening a new realm if we already have one opened
return realmSessionProvider.withRealm { realm -> return if (readReceiptsSummaryEntity.isManaged) {
readReceipts map(readReceipts, readReceiptsSummaryEntity.realm)
.mapNotNull { } else {
val roomMember = RoomMemberSummaryEntity.where(realm, roomId = it.roomId, userId = it.userId).findFirst() realmSessionProvider.withRealm { realm ->
?: return@mapNotNull null map(readReceipts, realm)
ReadReceipt(roomMember.asDomain(), it.originServerTs.toLong()) }
}
} }
} }
private fun map(readReceipts: RealmList<ReadReceiptEntity>, realm: Realm): List<ReadReceipt> {
return readReceipts
.mapNotNull {
val roomMember = RoomMemberSummaryEntity.where(realm, roomId = it.roomId, userId = it.userId).findFirst()
?: return@mapNotNull null
ReadReceipt(roomMember.asDomain(), it.originServerTs.toLong())
}
}
} }

View File

@ -323,13 +323,13 @@ internal class DefaultFileService @Inject constructor(
return FileProvider.getUriForFile(context, authority, targetFile) return FileProvider.getUriForFile(context, authority, targetFile)
} }
override fun getCacheSize(): Int { override fun getCacheSize(): Long {
return downloadFolder.walkTopDown() return downloadFolder.walkTopDown()
.onEnter { .onEnter {
Timber.v("Get size of ${it.absolutePath}") Timber.v("Get size of ${it.absolutePath}")
true true
} }
.sumOf { it.length().toInt() } .sumOf { it.length() }
} }
override fun clearCache() { override fun clearCache() {

View File

@ -529,7 +529,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
private fun getPollEvent(roomId: String, eventId: String): TimelineEvent? { private fun getPollEvent(roomId: String, eventId: String): TimelineEvent? {
val session = sessionManager.getSessionComponent(sessionId)?.session() val session = sessionManager.getSessionComponent(sessionId)?.session()
return session?.getRoom(roomId)?.getTimeLineEvent(eventId) ?: return null.also { return session?.getRoom(roomId)?.getTimelineEvent(eventId) ?: return null.also {
Timber.v("## POLL target poll event $eventId not found in room $roomId") Timber.v("## POLL target poll event $eventId not found in room $roomId")
} }
} }

View File

@ -30,14 +30,13 @@ import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.NoOpCancellable import org.matrix.android.sdk.api.util.NoOpCancellable
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
import org.matrix.android.sdk.internal.session.room.timeline.TimelineEventDataSource
import org.matrix.android.sdk.internal.util.fetchCopyMap import org.matrix.android.sdk.internal.util.fetchCopyMap
import timber.log.Timber import timber.log.Timber
@ -48,7 +47,7 @@ internal class DefaultRelationService @AssistedInject constructor(
private val eventFactory: LocalEchoEventFactory, private val eventFactory: LocalEchoEventFactory,
private val findReactionEventForUndoTask: FindReactionEventForUndoTask, private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
private val fetchEditHistoryTask: FetchEditHistoryTask, private val fetchEditHistoryTask: FetchEditHistoryTask,
private val timelineEventMapper: TimelineEventMapper, private val timelineEventDataSource: TimelineEventDataSource,
@SessionDatabase private val monarchy: Monarchy @SessionDatabase private val monarchy: Monarchy
) : RelationService { ) : RelationService {
@ -58,14 +57,8 @@ internal class DefaultRelationService @AssistedInject constructor(
} }
override fun sendReaction(targetEventId: String, reaction: String): Cancelable { override fun sendReaction(targetEventId: String, reaction: String): Cancelable {
return if (monarchy val targetTimelineEvent = timelineEventDataSource.getTimelineEvent(roomId, targetEventId)
.fetchCopyMap( return if (targetTimelineEvent
{ realm ->
TimelineEventEntity.where(realm, roomId, targetEventId).findFirst()
},
{ entity, _ ->
timelineEventMapper.map(entity)
})
?.annotations ?.annotations
?.reactionsSummary ?.reactionsSummary
.orEmpty() .orEmpty()

View File

@ -21,37 +21,24 @@ import com.zhuinden.monarchy.Monarchy
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.realm.Sort
import io.realm.kotlin.where
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.session.events.model.isImageMessage
import org.matrix.android.sdk.api.session.events.model.isVideoMessage
import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.timeline.TimelineService
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.database.RealmSessionProvider
import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler
import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
import org.matrix.android.sdk.internal.task.TaskExecutor
internal class DefaultTimelineService @AssistedInject constructor( internal class DefaultTimelineService @AssistedInject constructor(
@Assisted private val roomId: String, @Assisted private val roomId: String,
@UserId private val userId: String,
@SessionDatabase private val monarchy: Monarchy, @SessionDatabase private val monarchy: Monarchy,
private val realmSessionProvider: RealmSessionProvider,
private val timelineInput: TimelineInput, private val timelineInput: TimelineInput,
private val taskExecutor: TaskExecutor,
private val contextOfEventTask: GetContextOfEventTask, private val contextOfEventTask: GetContextOfEventTask,
private val eventDecryptor: TimelineEventDecryptor, private val eventDecryptor: TimelineEventDecryptor,
private val paginationTask: PaginationTask, private val paginationTask: PaginationTask,
@ -62,7 +49,8 @@ internal class DefaultTimelineService @AssistedInject constructor(
private val threadsAwarenessHandler: ThreadsAwarenessHandler, private val threadsAwarenessHandler: ThreadsAwarenessHandler,
private val lightweightSettingsStorage: LightweightSettingsStorage, private val lightweightSettingsStorage: LightweightSettingsStorage,
private val readReceiptHandler: ReadReceiptHandler, private val readReceiptHandler: ReadReceiptHandler,
private val coroutineDispatchers: MatrixCoroutineDispatchers private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val timelineEventDataSource: TimelineEventDataSource
) : TimelineService { ) : TimelineService {
@AssistedFactory @AssistedFactory
@ -91,27 +79,15 @@ internal class DefaultTimelineService @AssistedInject constructor(
) )
} }
override fun getTimeLineEvent(eventId: String): TimelineEvent? { override fun getTimelineEvent(eventId: String): TimelineEvent? {
return realmSessionProvider.withRealm { realm -> return timelineEventDataSource.getTimelineEvent(roomId, eventId)
TimelineEventEntity.where(realm, roomId = roomId, eventId = eventId).findFirst()?.let {
timelineEventMapper.map(it)
}
}
} }
override fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>> { override fun getTimelineEventLive(eventId: String): LiveData<Optional<TimelineEvent>> {
return LiveTimelineEvent(monarchy, taskExecutor.executorScope, timelineEventMapper, roomId, eventId) return timelineEventDataSource.getTimelineEventLive(roomId, eventId)
} }
override fun getAttachmentMessages(): List<TimelineEvent> { override fun getAttachmentMessages(): List<TimelineEvent> {
// TODO pretty bad query.. maybe we should denormalize clear type in base? return timelineEventDataSource.getAttachmentMessages(roomId)
return realmSessionProvider.withRealm { realm ->
realm.where<TimelineEventEntity>()
.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
.findAll()
?.mapNotNull { timelineEventMapper.map(it).takeIf { it.root.isImageMessage() || it.root.isVideoMessage() } }
.orEmpty()
}
} }
} }

View File

@ -46,7 +46,7 @@ internal class RealmSendingEventsDataSource(
private val sendingTimelineEventsListener = RealmChangeListener<RealmList<TimelineEventEntity>> { events -> private val sendingTimelineEventsListener = RealmChangeListener<RealmList<TimelineEventEntity>> { events ->
uiEchoManager.onSentEventsInDatabase(events.map { it.eventId }) uiEchoManager.onSentEventsInDatabase(events.map { it.eventId })
frozenSendingTimelineEvents = sendingTimelineEvents?.freeze() updateFrozenResults(events)
onEventsUpdated(false) onEventsUpdated(false)
} }
@ -59,10 +59,17 @@ internal class RealmSendingEventsDataSource(
override fun stop() { override fun stop() {
sendingTimelineEvents?.removeChangeListener(sendingTimelineEventsListener) sendingTimelineEvents?.removeChangeListener(sendingTimelineEventsListener)
updateFrozenResults(null)
sendingTimelineEvents = null sendingTimelineEvents = null
roomEntity = null roomEntity = null
} }
private fun updateFrozenResults(sendingEvents: RealmList<TimelineEventEntity>?) {
// Makes sure to close the previous frozen realm
frozenSendingTimelineEvents?.realm?.close()
frozenSendingTimelineEvents = sendingEvents?.freeze()
}
override fun buildSendingEvents(): List<TimelineEvent> { override fun buildSendingEvents(): List<TimelineEvent> {
val builtSendingEvents = mutableListOf<TimelineEvent>() val builtSendingEvents = mutableListOf<TimelineEvent>()
uiEchoManager.getInMemorySendingEvents() uiEchoManager.getInMemorySendingEvents()

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) 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.session.room.timeline
import androidx.lifecycle.LiveData
import com.zhuinden.monarchy.Monarchy
import io.realm.Sort
import io.realm.kotlin.where
import org.matrix.android.sdk.api.session.events.model.isImageMessage
import org.matrix.android.sdk.api.session.events.model.isVideoMessage
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.database.RealmSessionProvider
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.task.TaskExecutor
import javax.inject.Inject
internal class TimelineEventDataSource @Inject constructor(private val realmSessionProvider: RealmSessionProvider,
private val timelineEventMapper: TimelineEventMapper,
private val taskExecutor: TaskExecutor,
@SessionDatabase private val monarchy: Monarchy) {
fun getTimelineEvent(roomId: String, eventId: String): TimelineEvent? {
return realmSessionProvider.withRealm { realm ->
TimelineEventEntity.where(realm, roomId = roomId, eventId = eventId).findFirst()?.let {
timelineEventMapper.map(it)
}
}
}
fun getTimelineEventLive(roomId: String, eventId: String): LiveData<Optional<TimelineEvent>> {
return LiveTimelineEvent(monarchy, taskExecutor.executorScope, timelineEventMapper, roomId, eventId)
}
fun getAttachmentMessages(roomId: String): List<TimelineEvent> {
// TODO pretty bad query.. maybe we should denormalize clear type in base?
return realmSessionProvider.withRealm { realm ->
realm.where<TimelineEventEntity>()
.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
.findAll()
?.mapNotNull { timelineEventMapper.map(it).takeIf { it.root.isImageMessage() || it.root.isVideoMessage() } }
.orEmpty()
}
}
}

View File

@ -27,7 +27,6 @@ import org.matrix.android.sdk.internal.di.WorkManagerProvider
import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.session.sync.SyncPresence import org.matrix.android.sdk.internal.session.sync.SyncPresence
import org.matrix.android.sdk.internal.session.sync.SyncTask import org.matrix.android.sdk.internal.session.sync.SyncTask
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
@ -58,7 +57,6 @@ internal class SyncWorker(context: Context, workerParameters: WorkerParameters,
) : SessionWorkerParams ) : SessionWorkerParams
@Inject lateinit var syncTask: SyncTask @Inject lateinit var syncTask: SyncTask
@Inject lateinit var taskExecutor: TaskExecutor
@Inject lateinit var workManagerProvider: WorkManagerProvider @Inject lateinit var workManagerProvider: WorkManagerProvider
override fun injectWith(injector: SessionComponent) { override fun injectWith(injector: SessionComponent) {

View File

@ -1,4 +1,5 @@
include ':vector' include ':vector'
include ':vector-config'
include ':matrix-sdk-android' include ':matrix-sdk-android'
include ':library:core-utils' include ':library:core-utils'
include ':library:ui-styles' include ':library:ui-styles'

45
tools/ci/render_test_output.py Executable file
View File

@ -0,0 +1,45 @@
#!/usr/bin/env python3
#
# Renders a list of xml files taken as arguments into GHA-styled messages in groups.
# Explicitly aims not to have any dependencies, to reduce installation load.
# Should just be able to run in the context of your standard github runner.
# Potentially rewrite as an independent action, use handlebars to template result
import sys
import xml.etree.ElementTree as ET
suitename = sys.argv[1]
xmlfiles = sys.argv[2:]
print(f"Arguments: {sys.argv}")
for xmlfile in xmlfiles:
print(f"Handling: {xmlfile}")
tree = ET.parse(xmlfile)
root = tree.getroot()
name = root.attrib['name']
time = root.attrib['time']
tests = int(root.attrib['tests'])
skipped = int(root.attrib['skipped'])
errors = int(root.attrib['errors'])
failures = int(root.attrib['failures'])
success = tests - failures - errors - skipped
total = tests - skipped
print(f"::group::{name} {success}/{total} ({skipped} skipped) in {time}")
for testcase in root:
if testcase.tag != "testcase":
continue
testname = testcase.attrib['classname']
message = testcase.attrib['name']
time = testcase.attrib['time']
child = testcase.find("failure")
if child is None:
print(f"{message} in {time}s")
else:
print(f"::error file={testname}::{message} in {time}s")
print(child.text)
body = f"passed={success} failures={failures} errors={errors} skipped={skipped}"
print(f"::set-output name={suitename}::={body}")
print("::endgroup::")

1
vector-config/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,20 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdk versions.compileSdk
defaultConfig {
minSdk versions.minSdk
targetSdk versions.targetSdk
}
compileOptions {
sourceCompatibility versions.sourceCompat
targetCompatibility versions.targetCompat
}
kotlinOptions {
jvmTarget = "11"
}
}

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="im.vector.app.config" />

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- This file contains values to show or hide some settings, and default values for some settings
- boolean keys ending with "_visible" set the visibility of the setting
- boolean keys ending with "_default" set the default value of the setting
When a setting is hidden, the default value still applies
-->
<!-- Level 0: Root -->
<bool name="settings_root_general_visible">true</bool>
<bool name="settings_root_notification_visible">true</bool>
<bool name="settings_root_preferences_visible">true</bool>
<bool name="settings_root_voice_video_visible">true</bool>
<bool name="settings_root_ignored_users_visible">true</bool>
<bool name="settings_root_security_privacy_visible">true</bool>
<bool name="settings_root_labs_visible">true</bool>
<bool name="settings_root_advanced_visible">true</bool>
<bool name="settings_root_help_about_visible">true</bool>
<bool name="settings_root_legals_visible">true</bool>
<!-- Level 1: General -->
<!-- Level 1: Notifications -->
<!-- Level 1: Preferences -->
<bool name="settings_interface_bubble_visible">true</bool>
<bool name="settings_interface_bubble_default">false</bool>
<!-- Level 1: Voice and video -->
<!-- Level 1: Ignored Users -->
<!-- Level 1: Security and Privacy -->
<!-- Level 1: Labs -->
<!-- Level 1: Advcanced settings -->
<!-- Level 1: Help and about -->
<!-- Level 1: Legals -->
</resources>

View File

@ -5,7 +5,7 @@ apply plugin: 'com.google.android.gms.oss-licenses-plugin'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'placeholder-resolver' apply plugin: 'com.likethesalad.stem'
apply plugin: 'dagger.hilt.android.plugin' apply plugin: 'dagger.hilt.android.plugin'
kapt { kapt {
@ -132,16 +132,9 @@ android {
versionName "${versionMajor}.${versionMinor}.${versionPatch}-sonar" versionName "${versionMajor}.${versionMinor}.${versionPatch}-sonar"
buildConfigField "String", "GIT_REVISION", "\"${gitRevision()}\"" buildConfigField "String", "GIT_REVISION", "\"${gitRevision()}\""
resValue "string", "git_revision", "\"${gitRevision()}\""
buildConfigField "String", "GIT_REVISION_DATE", "\"${gitRevisionDate()}\"" buildConfigField "String", "GIT_REVISION_DATE", "\"${gitRevisionDate()}\""
resValue "string", "git_revision_date", "\"${gitRevisionDate()}\""
buildConfigField "String", "GIT_BRANCH_NAME", "\"${gitBranchName()}\"" buildConfigField "String", "GIT_BRANCH_NAME", "\"${gitBranchName()}\""
resValue "string", "git_branch_name", "\"${gitBranchName()}\""
buildConfigField "String", "BUILD_NUMBER", "\"${buildNumber}\"" buildConfigField "String", "BUILD_NUMBER", "\"${buildNumber}\""
resValue "string", "build_number", "\"${buildNumber}\""
buildConfigField "im.vector.app.features.VectorFeatures.OnboardingVariant", "ONBOARDING_VARIANT", "im.vector.app.features.VectorFeatures.OnboardingVariant.FTUE_AUTH" buildConfigField "im.vector.app.features.VectorFeatures.OnboardingVariant", "ONBOARDING_VARIANT", "im.vector.app.features.VectorFeatures.OnboardingVariant.FTUE_AUTH"
@ -153,9 +146,6 @@ android {
// This *must* only be set in trusted environments. // This *must* only be set in trusted environments.
buildConfigField "Boolean", "handleCallAssertedIdentityEvents", "false" buildConfigField "Boolean", "handleCallAssertedIdentityEvents", "false"
// Indicates whether or not threading support is enabled
buildConfigField "Boolean", "THREADING_ENABLED", "${isThreadingEnabled}"
buildConfigField "Boolean", "enableLocationSharing", "true" buildConfigField "Boolean", "enableLocationSharing", "true"
buildConfigField "String", "mapTilerKey", "\"fU3vlMsMn4Jb6dnEIFsx\"" buildConfigField "String", "mapTilerKey", "\"fU3vlMsMn4Jb6dnEIFsx\""
@ -233,7 +223,6 @@ android {
applicationIdSuffix ".debug" applicationIdSuffix ".debug"
resValue "string", "app_name", "Element dbg" resValue "string", "app_name", "Element dbg"
resValue "bool", "debug_mode", "true"
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false" buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
// Set to true if you want to enable strict mode in debug // Set to true if you want to enable strict mode in debug
buildConfigField "boolean", "ENABLE_STRICT_MODE_LOGS", "false" buildConfigField "boolean", "ENABLE_STRICT_MODE_LOGS", "false"
@ -244,7 +233,6 @@ android {
release { release {
resValue "string", "app_name", "Element" resValue "string", "app_name", "Element"
resValue "bool", "debug_mode", "false"
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false" buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
buildConfigField "boolean", "ENABLE_STRICT_MODE_LOGS", "false" buildConfigField "boolean", "ENABLE_STRICT_MODE_LOGS", "false"
@ -332,7 +320,7 @@ android {
} }
dependencies { dependencies {
implementation project(":vector-config")
implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android")
implementation project(":matrix-sdk-android-flow") implementation project(":matrix-sdk-android-flow")
implementation project(":library:jsonviewer") implementation project(":library:jsonviewer")
@ -376,7 +364,7 @@ dependencies {
implementation 'com.facebook.stetho:stetho:1.6.0' implementation 'com.facebook.stetho:stetho:1.6.0'
// Phone number https://github.com/google/libphonenumber // Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.43' implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.44'
// FlowBinding // FlowBinding
implementation libs.github.flowBinding implementation libs.github.flowBinding

View File

@ -6,6 +6,7 @@
<issue id="MissingTranslation" severity="ignore" /> <issue id="MissingTranslation" severity="ignore" />
<issue id="TypographyEllipsis" severity="error" /> <issue id="TypographyEllipsis" severity="error" />
<issue id="ImpliedQuantity" severity="warning" /> <issue id="ImpliedQuantity" severity="warning" />
<issue id="MissingQuantity" severity="warning" />
<issue id="UnusedQuantity" severity="error" /> <issue id="UnusedQuantity" severity="error" />
<issue id="IconXmlAndPng" severity="error" /> <issue id="IconXmlAndPng" severity="error" />
<issue id="IconDipSize" severity="error" /> <issue id="IconDipSize" severity="error" />
@ -14,6 +15,39 @@
<issue id="IconExpectedSize" severity="error" /> <issue id="IconExpectedSize" severity="error" />
<issue id="LocaleFolder" severity="error" /> <issue id="LocaleFolder" severity="error" />
<!-- String resource has to be used, for the rest we can ignore for now
TODO: stop ignoring warning at all those locations -->
<issue id="UnusedResources" severity="error">
<ignore path="**/build.gradle" />
<ignore path="**/anim/**" />
<ignore path="**/color/**" />
<ignore path="**/drawable*/**" />
<ignore path="**/layout/**" />
<ignore path="**/menu/**" />
<ignore path="**/raw/**" />
<ignore path="**/values/dimens.xml" />
<ignore path="**/values/colors.xml" />
<ignore path="**/values/palette.xml" />
<ignore path="**/values/palette_mobile.xml" />
<ignore path="**/values/donottranslate.xml" />
<ignore path="**/values/strings_login_v2.xml" />
<ignore path="**/library/ui-styles/src/main/res/values/**" />
<ignore path="**/xml/**" />
<!-- Following resources are for F-Droid variant only, so ignore for GPlay -->
<ignore regexp="settings_troubleshoot_test_service_boot_*" />
<ignore regexp="settings_troubleshoot_test_bg_restricted_*" />
<ignore regexp="settings_troubleshoot_test_battery_*" />
<!-- Following resources are for GPlay variant only, so ignore for F-Droid -->
<ignore regexp="settings_troubleshoot_test_play_services_" />
<ignore regexp="settings_troubleshoot_test_push_loop_" />
<ignore regexp="settings_troubleshoot_test_token_registration_" />
<ignore regexp="settings_troubleshoot_test_fcm_" />
<ignore regexp="no_valid_google_play_services_apk" />
<ignore regexp="sas_error_unknown" />
</issue>
<!-- UX --> <!-- UX -->
<issue id="ButtonOrder" severity="error" /> <issue id="ButtonOrder" severity="error" />
<issue id="TextFields" severity="error" /> <issue id="TextFields" severity="error" />
@ -75,14 +109,6 @@
<issue id="Typos" severity="error" /> <issue id="Typos" severity="error" />
<issue id="TypographyDashes" severity="error" /> <issue id="TypographyDashes" severity="error" />
<!-- Ignore lint issue in generated resource file from templates.
https://github.com/LikeTheSalad/android-string-reference generates string from the default language
if the translation is missing, and it can lead to typos. Lint should only check the string from the
original file, so with id starting by `template_`. -->
<issue id="all">
<ignore path="**/generated/resolved/**/resolved.xml" />
</issue>
<!-- DI --> <!-- DI -->
<issue id="JvmStaticProvidesInObjectDetector" severity="error" /> <issue id="JvmStaticProvidesInObjectDetector" severity="error" />
</lint> </lint>

View File

@ -23,8 +23,11 @@ import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import im.vector.app.features.DefaultVectorFeatures import im.vector.app.features.DefaultVectorFeatures
import im.vector.app.features.DefaultVectorOverrides
import im.vector.app.features.VectorFeatures import im.vector.app.features.VectorFeatures
import im.vector.app.features.VectorOverrides
import im.vector.app.features.debug.features.DebugVectorFeatures import im.vector.app.features.debug.features.DebugVectorFeatures
import im.vector.app.features.debug.features.DebugVectorOverrides
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
@Module @Module
@ -33,6 +36,9 @@ interface FeaturesModule {
@Binds @Binds
fun bindFeatures(debugFeatures: DebugVectorFeatures): VectorFeatures fun bindFeatures(debugFeatures: DebugVectorFeatures): VectorFeatures
@Binds
fun bindOverrides(debugOverrides: DebugVectorOverrides): VectorOverrides
companion object { companion object {
@Provides @Provides
@ -44,5 +50,15 @@ interface FeaturesModule {
fun providesDebugVectorFeatures(context: Context, defaultVectorFeatures: DefaultVectorFeatures): DebugVectorFeatures { fun providesDebugVectorFeatures(context: Context, defaultVectorFeatures: DefaultVectorFeatures): DebugVectorFeatures {
return DebugVectorFeatures(context, defaultVectorFeatures) return DebugVectorFeatures(context, defaultVectorFeatures)
} }
@Provides
fun providesDefaultVectorOverrides(): DefaultVectorOverrides {
return DefaultVectorOverrides()
}
@Provides
fun providesDebugVectorOverrides(context: Context): DebugVectorOverrides {
return DebugVectorOverrides(context)
}
} }
} }

View File

@ -53,7 +53,7 @@ class DebugFeaturesStateFactory @Inject constructor(
label = "FTUE Personalize profile", label = "FTUE Personalize profile",
key = DebugFeatureKeys.onboardingPersonalize, key = DebugFeatureKeys.onboardingPersonalize,
factory = VectorFeatures::isOnboardingPersonalizeEnabled factory = VectorFeatures::isOnboardingPersonalizeEnabled
) ),
)) ))
} }

View File

@ -0,0 +1,54 @@
/*
* 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.debug.features
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.preferencesDataStore
import im.vector.app.features.VectorOverrides
import kotlinx.coroutines.flow.map
import org.matrix.android.sdk.api.extensions.orFalse
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "vector_overrides")
private val keyForceDialPadDisplay = booleanPreferencesKey("force_dial_pad_display")
private val keyForceLoginFallback = booleanPreferencesKey("force_login_fallback")
class DebugVectorOverrides(private val context: Context) : VectorOverrides {
override val forceDialPad = context.dataStore.data.map { preferences ->
preferences[keyForceDialPadDisplay].orFalse()
}
override val forceLoginFallback = context.dataStore.data.map { preferences ->
preferences[keyForceLoginFallback].orFalse()
}
suspend fun setForceDialPadDisplay(force: Boolean) {
context.dataStore.edit { settings ->
settings[keyForceDialPadDisplay] = force
}
}
suspend fun setForceLoginFallback(force: Boolean) {
context.dataStore.edit { settings ->
settings[keyForceLoginFallback] = force
}
}
}

View File

@ -43,9 +43,13 @@ class DebugPrivateSettingsFragment : VectorBaseFragment<FragmentDebugPrivateSett
views.forceDialPadTabDisplay.setOnCheckedChangeListener { _, isChecked -> views.forceDialPadTabDisplay.setOnCheckedChangeListener { _, isChecked ->
viewModel.handle(DebugPrivateSettingsViewActions.SetDialPadVisibility(isChecked)) viewModel.handle(DebugPrivateSettingsViewActions.SetDialPadVisibility(isChecked))
} }
views.forceLoginFallback.setOnCheckedChangeListener { _, isChecked ->
viewModel.handle(DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled(isChecked))
}
} }
override fun invalidate() = withState(viewModel) { override fun invalidate() = withState(viewModel) {
views.forceDialPadTabDisplay.isChecked = it.dialPadVisible views.forceDialPadTabDisplay.isChecked = it.dialPadVisible
views.forceLoginFallback.isChecked = it.forceLoginFallback
} }
} }

View File

@ -20,4 +20,5 @@ import im.vector.app.core.platform.VectorViewModelAction
sealed class DebugPrivateSettingsViewActions : VectorViewModelAction { sealed class DebugPrivateSettingsViewActions : VectorViewModelAction {
data class SetDialPadVisibility(val force: Boolean) : DebugPrivateSettingsViewActions() data class SetDialPadVisibility(val force: Boolean) : DebugPrivateSettingsViewActions()
data class SetForceLoginFallbackEnabled(val force: Boolean) : DebugPrivateSettingsViewActions()
} }

View File

@ -24,12 +24,12 @@ 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.EmptyViewEvents import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.settings.VectorDataStore import im.vector.app.features.debug.features.DebugVectorOverrides
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class DebugPrivateSettingsViewModel @AssistedInject constructor( class DebugPrivateSettingsViewModel @AssistedInject constructor(
@Assisted initialState: DebugPrivateSettingsViewState, @Assisted initialState: DebugPrivateSettingsViewState,
private val vectorDataStore: VectorDataStore private val debugVectorOverrides: DebugVectorOverrides
) : VectorViewModel<DebugPrivateSettingsViewState, DebugPrivateSettingsViewActions, EmptyViewEvents>(initialState) { ) : VectorViewModel<DebugPrivateSettingsViewState, DebugPrivateSettingsViewActions, EmptyViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
@ -44,22 +44,32 @@ class DebugPrivateSettingsViewModel @AssistedInject constructor(
} }
private fun observeVectorDataStore() { private fun observeVectorDataStore() {
vectorDataStore.forceDialPadDisplayFlow.setOnEach { debugVectorOverrides.forceDialPad.setOnEach {
copy( copy(
dialPadVisible = it dialPadVisible = it
) )
} }
debugVectorOverrides.forceLoginFallback.setOnEach {
copy(forceLoginFallback = it)
}
} }
override fun handle(action: DebugPrivateSettingsViewActions) { override fun handle(action: DebugPrivateSettingsViewActions) {
when (action) { when (action) {
is DebugPrivateSettingsViewActions.SetDialPadVisibility -> handleSetDialPadVisibility(action) is DebugPrivateSettingsViewActions.SetDialPadVisibility -> handleSetDialPadVisibility(action)
is DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled -> handleSetForceLoginFallbackEnabled(action)
} }
} }
private fun handleSetDialPadVisibility(action: DebugPrivateSettingsViewActions.SetDialPadVisibility) { private fun handleSetDialPadVisibility(action: DebugPrivateSettingsViewActions.SetDialPadVisibility) {
viewModelScope.launch { viewModelScope.launch {
vectorDataStore.setForceDialPadDisplay(action.force) debugVectorOverrides.setForceDialPadDisplay(action.force)
}
}
private fun handleSetForceLoginFallbackEnabled(action: DebugPrivateSettingsViewActions.SetForceLoginFallbackEnabled) {
viewModelScope.launch {
debugVectorOverrides.setForceLoginFallback(action.force)
} }
} }
} }

View File

@ -19,5 +19,6 @@ package im.vector.app.features.debug.settings
import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksState
data class DebugPrivateSettingsViewState( data class DebugPrivateSettingsViewState(
val dialPadVisible: Boolean = false val dialPadVisible: Boolean = false,
val forceLoginFallback: Boolean = false,
) : MavericksState ) : MavericksState

View File

@ -25,6 +25,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Force DialPad tab display" /> android:text="Force DialPad tab display" />
<CheckBox
android:id="@+id/forceLoginFallback"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Force login and registration fallback" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@ -213,7 +213,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
try { try {
val session = activeSessionHolder.getSafeActiveSession() ?: return false val session = activeSessionHolder.getSafeActiveSession() ?: return false
val room = session.getRoom(roomId) ?: return false val room = session.getRoom(roomId) ?: return false
return room.getTimeLineEvent(eventId) != null return room.getTimelineEvent(eventId) != null
} catch (e: Exception) { } catch (e: Exception) {
Timber.tag(loggerTag.value).e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined") Timber.tag(loggerTag.value).e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined")
} }

View File

@ -99,6 +99,7 @@ import im.vector.app.features.matrixto.MatrixToRoomSpaceFragment
import im.vector.app.features.matrixto.MatrixToUserFragment import im.vector.app.features.matrixto.MatrixToUserFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthAccountCreatedFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthAccountCreatedFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthCaptchaFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthCaptchaFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseDisplayNameFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthGenericTextInputFormFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthGenericTextInputFormFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthLoginFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthLoginFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordFragment import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordFragment
@ -479,6 +480,11 @@ interface FragmentModule {
@FragmentKey(FtueAuthAccountCreatedFragment::class) @FragmentKey(FtueAuthAccountCreatedFragment::class)
fun bindFtueAuthAccountCreatedFragment(fragment: FtueAuthAccountCreatedFragment): Fragment fun bindFtueAuthAccountCreatedFragment(fragment: FtueAuthAccountCreatedFragment): Fragment
@Binds
@IntoMap
@FragmentKey(FtueAuthChooseDisplayNameFragment::class)
fun bindFtueAuthChooseDisplayNameFragment(fragment: FtueAuthChooseDisplayNameFragment): Fragment
@Binds @Binds
@IntoMap @IntoMap
@FragmentKey(UserListFragment::class) @FragmentKey(UserListFragment::class)

View File

@ -58,6 +58,7 @@ import im.vector.app.features.login.LoginViewModel
import im.vector.app.features.login2.LoginViewModel2 import im.vector.app.features.login2.LoginViewModel2
import im.vector.app.features.login2.created.AccountCreatedViewModel import im.vector.app.features.login2.created.AccountCreatedViewModel
import im.vector.app.features.matrixto.MatrixToBottomSheetViewModel import im.vector.app.features.matrixto.MatrixToBottomSheetViewModel
import im.vector.app.features.media.VectorAttachmentViewerViewModel
import im.vector.app.features.onboarding.OnboardingViewModel import im.vector.app.features.onboarding.OnboardingViewModel
import im.vector.app.features.poll.create.CreatePollViewModel import im.vector.app.features.poll.create.CreatePollViewModel
import im.vector.app.features.qrcode.QrCodeScannerViewModel import im.vector.app.features.qrcode.QrCodeScannerViewModel
@ -594,4 +595,9 @@ interface MavericksViewModelModule {
@IntoMap @IntoMap
@MavericksViewModelKey(LocationSharingViewModel::class) @MavericksViewModelKey(LocationSharingViewModel::class)
fun createLocationSharingViewModelFactory(factory: LocationSharingViewModel.Factory): MavericksAssistedViewModelFactory<*, *> fun createLocationSharingViewModelFactory(factory: LocationSharingViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
@Binds
@IntoMap
@MavericksViewModelKey(VectorAttachmentViewerViewModel::class)
fun vectorAttachmentViewerViewModelFactory(factory: VectorAttachmentViewerViewModel.Factory): MavericksAssistedViewModelFactory<*, *>
} }

View File

@ -169,22 +169,24 @@ const val POP_BACK_STACK_EXCLUSIVE = 0
fun Fragment.queryExportKeys(userId: String, activityResultLauncher: ActivityResultLauncher<Intent>) { fun Fragment.queryExportKeys(userId: String, activityResultLauncher: ActivityResultLauncher<Intent>) {
val timestamp = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date()) val timestamp = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date())
val appName = getString(R.string.app_name).replace(" ", "-")
selectTxtFileToWrite( selectTxtFileToWrite(
activity = requireActivity(), activity = requireActivity(),
activityResultLauncher = activityResultLauncher, activityResultLauncher = activityResultLauncher,
defaultFileName = "element-megolm-export-$userId-$timestamp.txt", defaultFileName = "$appName-megolm-export-$userId-$timestamp.txt",
chooserHint = getString(R.string.keys_backup_setup_step1_manual_export) chooserHint = getString(R.string.keys_backup_setup_step1_manual_export)
) )
} }
fun Activity.queryExportKeys(userId: String, activityResultLauncher: ActivityResultLauncher<Intent>) { fun Activity.queryExportKeys(userId: String, activityResultLauncher: ActivityResultLauncher<Intent>) {
val timestamp = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date()) val timestamp = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date())
val appName = getString(R.string.app_name).replace(" ", "-")
selectTxtFileToWrite( selectTxtFileToWrite(
activity = this, activity = this,
activityResultLauncher = activityResultLauncher, activityResultLauncher = activityResultLauncher,
defaultFileName = "element-megolm-export-$userId-$timestamp.txt", defaultFileName = "$appName-megolm-export-$userId-$timestamp.txt",
chooserHint = getString(R.string.keys_backup_setup_step1_manual_export) chooserHint = getString(R.string.keys_backup_setup_step1_manual_export)
) )
} }

View File

@ -125,11 +125,11 @@ fun getFileExtension(fileUri: String): String? {
* Size * Size
* ========================================================================================== */ * ========================================================================================== */
fun getSizeOfFiles(root: File): Int { fun getSizeOfFiles(root: File): Long {
return root.walkTopDown() return root.walkTopDown()
.onEnter { .onEnter {
Timber.v("Get size of ${it.absolutePath}") Timber.v("Get size of ${it.absolutePath}")
true true
} }
.sumOf { it.length().toInt() } .sumOf { it.length() }
} }

View File

@ -0,0 +1,30 @@
/*
* 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
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
interface VectorOverrides {
val forceDialPad: Flow<Boolean>
val forceLoginFallback: Flow<Boolean>
}
class DefaultVectorOverrides : VectorOverrides {
override val forceDialPad = flowOf(false)
override val forceLoginFallback = flowOf(false)
}

View File

@ -40,6 +40,16 @@ data class Interaction(
) : VectorAnalyticsEvent { ) : VectorAnalyticsEvent {
enum class Name { enum class Name {
/**
* User tapped on Add to Home button on Room Details screen.
*/
MobileRoomAddHome,
/**
* User tapped on Leave Room button on Room Details screen.
*/
MobileRoomLeave,
/** /**
* User tapped the already selected space from the space list. * User tapped the already selected space from the space list.
*/ */
@ -52,8 +62,8 @@ data class Interaction(
SpacePanelSwitchSpace, SpacePanelSwitchSpace,
/** /**
* User clicked the create room button in the + context menu of the room * User clicked the create room button in the add existing room to space
* list header in Element Web/Desktop. * dialog in Element Web/Desktop.
*/ */
WebAddExistingToSpaceDialogCreateRoomButton, WebAddExistingToSpaceDialogCreateRoomButton,
@ -153,6 +163,12 @@ data class Interaction(
*/ */
WebRoomListHeaderPlusMenuCreateRoomItem, WebRoomListHeaderPlusMenuCreateRoomItem,
/**
* User clicked the explore rooms button in the + context menu of the
* room list header in Element Web/Desktop.
*/
WebRoomListHeaderPlusMenuExploreRoomsItem,
/** /**
* User adjusted their favourites using the context menu on a room tile * User adjusted their favourites using the context menu on a room tile
* in the room list in Element Web/Desktop. * in the room list in Element Web/Desktop.
@ -189,6 +205,12 @@ data class Interaction(
*/ */
WebRoomListRoomsSublistPlusMenuCreateRoomItem, WebRoomListRoomsSublistPlusMenuCreateRoomItem,
/**
* User clicked the explore rooms button in the + context menu of the
* rooms sublist in Element Web/Desktop.
*/
WebRoomListRoomsSublistPlusMenuExploreRoomsItem,
/** /**
* User interacted with leave action in the general tab of the room * User interacted with leave action in the general tab of the room
* settings dialog in Element Web/Desktop. * settings dialog in Element Web/Desktop.
@ -214,14 +236,26 @@ data class Interaction(
WebSettingsSidebarTabSpacesCheckbox, WebSettingsSidebarTabSpacesCheckbox,
/** /**
* User clicked the create room button in the + context menu of the room * User clicked the explore rooms button in the context menu of a space
* list header in Element Web/Desktop. * in Element Web/Desktop.
*/
WebSpaceContextMenuExploreRoomsItem,
/**
* User clicked the home button in the context menu of a space in
* Element Web/Desktop.
*/
WebSpaceContextMenuHomeItem,
/**
* User clicked the new room button in the context menu of a space in
* Element Web/Desktop.
*/ */
WebSpaceContextMenuNewRoomItem, WebSpaceContextMenuNewRoomItem,
/** /**
* User clicked the create room button in the + context menu of the room * User clicked the new room button in the context menu on the space
* list header in Element Web/Desktop. * home in Element Web/Desktop.
*/ */
WebSpaceHomeCreateRoomButton, WebSpaceHomeCreateRoomButton,

View File

@ -44,6 +44,11 @@ data class JoinedRoom(
) : VectorAnalyticsEvent { ) : VectorAnalyticsEvent {
enum class Trigger { enum class Trigger {
/**
* Room joined via an invite.
*/
Invite,
/** /**
* Room joined via a push/desktop notification. * Room joined via a push/desktop notification.
*/ */
@ -54,6 +59,16 @@ data class JoinedRoom(
*/ */
RoomDirectory, RoomDirectory,
/**
* Room joined via its preview.
*/
RoomPreview,
/**
* Room joined via the /join slash command.
*/
SlashCommand,
/** /**
* Room joined via the space hierarchy view. * Room joined via the space hierarchy view.
*/ */

View File

@ -105,6 +105,11 @@ data class MobileScreen(
*/ */
Room, Room,
/**
* The room addresses screen shown from the Room Details screen.
*/
RoomAddresses,
/** /**
* The screen shown when tapping the name of a room from the Room * The screen shown when tapping the name of a room from the Room
* screen. * screen.
@ -132,6 +137,16 @@ data class MobileScreen(
*/ */
RoomNotifications, RoomNotifications,
/**
* The roles permissions screen shown from the Room Details screen.
*/
RoomPermissions,
/**
* Screen that displays room preview if user hasn't joined yet
*/
RoomPreview,
/** /**
* The screen that allows you to search for messages/files in a specific * The screen that allows you to search for messages/files in a specific
* room. * room.
@ -180,21 +195,67 @@ data class MobileScreen(
*/ */
Settings, Settings,
/**
* The advanced settings screen (developer mode, rageshake, push
* notification rules)
*/
SettingsAdvanced,
/** /**
* The settings screen to change the default notification options. * The settings screen to change the default notification options.
*/ */
SettingsDefaultNotifications, SettingsDefaultNotifications,
/**
* The settings screen with general profile settings.
*/
SettingsGeneral,
/**
* The Help and About screen
*/
SettingsHelp,
/**
* The settings screen with list of the ignored users.
*/
SettingsIgnoredUsers,
/**
* The experimental features settings screen,
*/
SettingsLabs,
/**
* The settings screen with legals information
*/
SettingsLegals,
/** /**
* The settings screen to manage notification mentions and keywords. * The settings screen to manage notification mentions and keywords.
*/ */
SettingsMentionsAndKeywords, SettingsMentionsAndKeywords,
/**
* The notifications settings screen.
*/
SettingsNotifications,
/**
* The preferences screen (theme, language, editor preferences, etc.
*/
SettingsPreferences,
/** /**
* The global security settings screen. * The global security settings screen.
*/ */
SettingsSecurity, SettingsSecurity,
/**
* The calls settings screen.
*/
SettingsVoiceVideo,
/** /**
* The sidebar shown on mobile with spaces, settings etc. * The sidebar shown on mobile with spaces, settings etc.
*/ */

View File

@ -25,6 +25,18 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
* Triggered when the user changes rooms. * Triggered when the user changes rooms.
*/ */
data class ViewRoom( data class ViewRoom(
/**
* active space when user navigated to the room.
*/
val activeSpace: ActiveSpace? = null,
/**
* Whether the room is a DM.
*/
val isDM: Boolean? = null,
/**
* Whether the room is a Space.
*/
val isSpace: Boolean? = null,
/** /**
* The reason for the room change if known. * The reason for the room change if known.
*/ */
@ -179,10 +191,36 @@ data class ViewRoom(
Widget, Widget,
} }
enum class ActiveSpace {
/**
* Active space is Home.
*/
Home,
/**
* Active space is a meta space.
*/
Meta,
/**
* Active space is a private space.
*/
Private,
/**
* Active space is a public space.
*/
Public,
}
override fun getName() = "ViewRoom" override fun getName() = "ViewRoom"
override fun getProperties(): Map<String, Any>? { override fun getProperties(): Map<String, Any>? {
return mutableMapOf<String, Any>().apply { return mutableMapOf<String, Any>().apply {
activeSpace?.let { put("activeSpace", it.name) }
isDM?.let { put("isDM", it) }
isSpace?.let { put("isSpace", it) }
trigger?.let { put("trigger", it.name) } trigger?.let { put("trigger", it.name) }
viaKeyboard?.let { put("viaKeyboard", it) } viaKeyboard?.let { put("viaKeyboard", it) }
}.takeIf { it.isNotEmpty() } }.takeIf { it.isNotEmpty() }

View File

@ -28,6 +28,7 @@ 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.extensions.singletonEntryPoint import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.VectorOverrides
import im.vector.app.features.call.dialpad.DialPadLookup import im.vector.app.features.call.dialpad.DialPadLookup
import im.vector.app.features.call.lookup.CallProtocolsChecker import im.vector.app.features.call.lookup.CallProtocolsChecker
import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.call.webrtc.WebRtcCallManager
@ -67,7 +68,8 @@ class HomeDetailViewModel @AssistedInject constructor(
private val callManager: WebRtcCallManager, private val callManager: WebRtcCallManager,
private val directRoomHelper: DirectRoomHelper, private val directRoomHelper: DirectRoomHelper,
private val appStateHandler: AppStateHandler, private val appStateHandler: AppStateHandler,
private val autoAcceptInvites: AutoAcceptInvites private val autoAcceptInvites: AutoAcceptInvites,
private val vectorOverrides: VectorOverrides
) : VectorViewModel<HomeDetailViewState, HomeDetailAction, HomeDetailViewEvents>(initialState), ) : VectorViewModel<HomeDetailViewState, HomeDetailAction, HomeDetailViewEvents>(initialState),
CallProtocolsChecker.Listener { CallProtocolsChecker.Listener {
@ -106,8 +108,7 @@ class HomeDetailViewModel @AssistedInject constructor(
pushCounter = nbOfPush pushCounter = nbOfPush
) )
} }
vectorOverrides.forceDialPad.setOnEach { force ->
vectorDataStore.forceDialPadDisplayFlow.setOnEach { force ->
copy( copy(
forceDialPadTab = force forceDialPadTab = force
) )

View File

@ -92,7 +92,6 @@ sealed class RoomDetailAction : VectorViewModelAction {
data class UpdateJoinJitsiCallStatus(val conferenceEvent: ConferenceEvent) : RoomDetailAction() data class UpdateJoinJitsiCallStatus(val conferenceEvent: ConferenceEvent) : RoomDetailAction()
data class OpenOrCreateDm(val userId: String) : RoomDetailAction()
data class JumpToReadReceipt(val userId: String) : RoomDetailAction() data class JumpToReadReceipt(val userId: String) : RoomDetailAction()
object QuickActionInvitePeople : RoomDetailAction() object QuickActionInvitePeople : RoomDetailAction()
object QuickActionSetAvatar : RoomDetailAction() object QuickActionSetAvatar : RoomDetailAction()

View File

@ -17,7 +17,6 @@
package im.vector.app.features.home.room.detail package im.vector.app.features.home.room.detail
sealed class RoomDetailPendingAction { sealed class RoomDetailPendingAction {
data class OpenOrCreateDm(val userId: String) : RoomDetailPendingAction()
data class JumpToReadReceipt(val userId: String) : RoomDetailPendingAction() data class JumpToReadReceipt(val userId: String) : RoomDetailPendingAction()
data class MentionUser(val userId: String) : RoomDetailPendingAction() data class MentionUser(val userId: String) : RoomDetailPendingAction()
data class OpenRoom(val roomId: String, val closeCurrentRoom: Boolean = false) : RoomDetailPendingAction() data class OpenRoom(val roomId: String, val closeCurrentRoom: Boolean = false) : RoomDetailPendingAction()

View File

@ -1247,8 +1247,6 @@ class TimelineFragment @Inject constructor(
timelineViewModel.handle(RoomDetailAction.JumpToReadReceipt(roomDetailPendingAction.userId)) timelineViewModel.handle(RoomDetailAction.JumpToReadReceipt(roomDetailPendingAction.userId))
is RoomDetailPendingAction.MentionUser -> is RoomDetailPendingAction.MentionUser ->
insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId) insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId)
is RoomDetailPendingAction.OpenOrCreateDm ->
timelineViewModel.handle(RoomDetailAction.OpenOrCreateDm(roomDetailPendingAction.userId))
is RoomDetailPendingAction.OpenRoom -> is RoomDetailPendingAction.OpenRoom ->
handleOpenRoom(RoomDetailViewEvents.OpenRoom(roomDetailPendingAction.roomId, roomDetailPendingAction.closeCurrentRoom)) handleOpenRoom(RoomDetailViewEvents.OpenRoom(roomDetailPendingAction.roomId, roomDetailPendingAction.closeCurrentRoom))
}.exhaustive }.exhaustive

View File

@ -420,7 +420,6 @@ class TimelineViewModel @AssistedInject constructor(
is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId) is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId)
is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action) is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action)
is RoomDetailAction.CancelSend -> handleCancel(action) is RoomDetailAction.CancelSend -> handleCancel(action)
is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action)
is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action) is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action)
RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople() RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople()
RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar() RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar()
@ -500,20 +499,6 @@ class TimelineViewModel @AssistedInject constructor(
_viewEvents.post(RoomDetailViewEvents.OpenSetRoomAvatarDialog) _viewEvents.post(RoomDetailViewEvents.OpenSetRoomAvatarDialog)
} }
private fun handleOpenOrCreateDm(action: RoomDetailAction.OpenOrCreateDm) {
viewModelScope.launch {
val roomId = try {
directRoomHelper.ensureDMExists(action.userId)
} catch (failure: Throwable) {
_viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure))
return@launch
}
if (roomId != initialState.roomId) {
_viewEvents.post(RoomDetailViewEvents.OpenRoom(roomId = roomId))
}
}
}
private fun handleJumpToReadReceipt(action: RoomDetailAction.JumpToReadReceipt) { private fun handleJumpToReadReceipt(action: RoomDetailAction.JumpToReadReceipt) {
room.getUserReadReceipt(action.userId) room.getUserReadReceipt(action.userId)
?.let { handleNavigateToEvent(RoomDetailAction.NavigateToEvent(it, true)) } ?.let { handleNavigateToEvent(RoomDetailAction.NavigateToEvent(it, true)) }
@ -745,7 +730,7 @@ class TimelineViewModel @AssistedInject constructor(
} }
private fun handleRedactEvent(action: RoomDetailAction.RedactAction) { private fun handleRedactEvent(action: RoomDetailAction.RedactAction) {
val event = room.getTimeLineEvent(action.targetEventId) ?: return val event = room.getTimelineEvent(action.targetEventId) ?: return
room.redactEvent(event.root, action.reason) room.redactEvent(event.root, action.reason)
} }
@ -785,7 +770,7 @@ class TimelineViewModel @AssistedInject constructor(
} }
// We need to update this with the related m.replace also (to move read receipt) // We need to update this with the related m.replace also (to move read receipt)
action.event.annotations?.editSummary?.sourceEvents?.forEach { action.event.annotations?.editSummary?.sourceEvents?.forEach {
room.getTimeLineEvent(it)?.let { event -> room.getTimelineEvent(it)?.let { event ->
visibleEventsSource.post(RoomDetailAction.TimelineEventTurnsVisible(event)) visibleEventsSource.post(RoomDetailAction.TimelineEventTurnsVisible(event))
} }
} }
@ -813,7 +798,7 @@ class TimelineViewModel @AssistedInject constructor(
notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(initialState.roomId) } notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(initialState.roomId) }
viewModelScope.launch { viewModelScope.launch {
try { try {
session.leaveRoom(room.roomId) session.leaveRoom(room.roomId)
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
_viewEvents.post(RoomDetailViewEvents.Failure(throwable, showInDialog = true)) _viewEvents.post(RoomDetailViewEvents.Failure(throwable, showInDialog = true))
} }
@ -889,7 +874,7 @@ class TimelineViewModel @AssistedInject constructor(
private fun handleResendEvent(action: RoomDetailAction.ResendMessage) { private fun handleResendEvent(action: RoomDetailAction.ResendMessage) {
val targetEventId = action.eventId val targetEventId = action.eventId
room.getTimeLineEvent(targetEventId)?.let { room.getTimelineEvent(targetEventId)?.let {
// State must be UNDELIVERED or Failed // State must be UNDELIVERED or Failed
if (!it.root.sendState.hasFailed()) { if (!it.root.sendState.hasFailed()) {
Timber.e("Cannot resend message, it is not failed, Cancel first") Timber.e("Cannot resend message, it is not failed, Cancel first")
@ -907,7 +892,7 @@ class TimelineViewModel @AssistedInject constructor(
private fun handleRemove(action: RoomDetailAction.RemoveFailedEcho) { private fun handleRemove(action: RoomDetailAction.RemoveFailedEcho) {
val targetEventId = action.eventId val targetEventId = action.eventId
room.getTimeLineEvent(targetEventId)?.let { room.getTimelineEvent(targetEventId)?.let {
// State must be UNDELIVERED or Failed // State must be UNDELIVERED or Failed
if (!it.root.sendState.hasFailed()) { if (!it.root.sendState.hasFailed()) {
Timber.e("Cannot resend message, it is not failed, Cancel first") Timber.e("Cannot resend message, it is not failed, Cancel first")
@ -923,7 +908,7 @@ class TimelineViewModel @AssistedInject constructor(
return return
} }
val targetEventId = action.eventId val targetEventId = action.eventId
room.getTimeLineEvent(targetEventId)?.let { room.getTimelineEvent(targetEventId)?.let {
// State must be in one of the sending states // State must be in one of the sending states
if (!it.root.sendState.isSending()) { if (!it.root.sendState.isSending()) {
Timber.e("Cannot cancel message, it is not sending") Timber.e("Cannot cancel message, it is not sending")
@ -1049,14 +1034,14 @@ class TimelineViewModel @AssistedInject constructor(
private fun handleReRequestKeys(action: RoomDetailAction.ReRequestKeys) { private fun handleReRequestKeys(action: RoomDetailAction.ReRequestKeys) {
// Check if this request is still active and handled by me // Check if this request is still active and handled by me
room.getTimeLineEvent(action.eventId)?.let { room.getTimelineEvent(action.eventId)?.let {
session.cryptoService().reRequestRoomKeyForEvent(it.root) session.cryptoService().reRequestRoomKeyForEvent(it.root)
_viewEvents.post(RoomDetailViewEvents.ShowMessage(stringProvider.getString(R.string.e2e_re_request_encryption_key_dialog_content))) _viewEvents.post(RoomDetailViewEvents.ShowMessage(stringProvider.getString(R.string.e2e_re_request_encryption_key_dialog_content)))
} }
} }
private fun handleTapOnFailedToDecrypt(action: RoomDetailAction.TapOnFailedToDecrypt) { private fun handleTapOnFailedToDecrypt(action: RoomDetailAction.TapOnFailedToDecrypt) {
room.getTimeLineEvent(action.eventId)?.let { room.getTimelineEvent(action.eventId)?.let {
val code = when (it.root.mCryptoError) { val code = when (it.root.mCryptoError) {
MXCryptoError.ErrorType.KEYS_WITHHELD -> { MXCryptoError.ErrorType.KEYS_WITHHELD -> {
WithHeldCode.fromCode(it.root.mCryptoErrorReason) WithHeldCode.fromCode(it.root.mCryptoErrorReason)
@ -1072,7 +1057,7 @@ class TimelineViewModel @AssistedInject constructor(
// Do not allow to vote unsent local echo of the poll event // Do not allow to vote unsent local echo of the poll event
if (LocalEcho.isLocalEchoId(action.eventId)) return if (LocalEcho.isLocalEchoId(action.eventId)) return
// Do not allow to vote the same option twice // Do not allow to vote the same option twice
room.getTimeLineEvent(action.eventId)?.let { pollTimelineEvent -> room.getTimelineEvent(action.eventId)?.let { pollTimelineEvent ->
val currentVote = pollTimelineEvent.annotations?.pollResponseSummary?.aggregatedContent?.myVote val currentVote = pollTimelineEvent.annotations?.pollResponseSummary?.aggregatedContent?.myVote
if (currentVote != action.optionKey) { if (currentVote != action.optionKey) {
room.voteToPoll(action.eventId, action.optionKey) room.voteToPoll(action.eventId, action.optionKey)

View File

@ -143,7 +143,7 @@ class MessageComposerViewModel @AssistedInject constructor(
} }
private fun handleEnterEditMode(action: MessageComposerAction.EnterEditMode) { private fun handleEnterEditMode(action: MessageComposerAction.EnterEditMode) {
room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.Edit(timelineEvent, timelineEvent.getTextEditableContent())) } setState { copy(sendMode = SendMode.Edit(timelineEvent, timelineEvent.getTextEditableContent())) }
} }
} }
@ -175,13 +175,13 @@ class MessageComposerViewModel @AssistedInject constructor(
} }
private fun handleEnterQuoteMode(action: MessageComposerAction.EnterQuoteMode) { private fun handleEnterQuoteMode(action: MessageComposerAction.EnterQuoteMode) {
room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.Quote(timelineEvent, action.text)) } setState { copy(sendMode = SendMode.Quote(timelineEvent, action.text)) }
} }
} }
private fun handleEnterReplyMode(action: MessageComposerAction.EnterReplyMode) { private fun handleEnterReplyMode(action: MessageComposerAction.EnterReplyMode) {
room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> room.getTimelineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.Reply(timelineEvent, action.text)) } setState { copy(sendMode = SendMode.Reply(timelineEvent, action.text)) }
} }
} }
@ -479,7 +479,7 @@ class MessageComposerViewModel @AssistedInject constructor(
if (inReplyTo != null) { if (inReplyTo != null) {
// TODO check if same content? // TODO check if same content?
room.getTimeLineEvent(inReplyTo)?.let { room.getTimelineEvent(inReplyTo)?.let {
room.editReply(state.sendMode.timelineEvent, it, action.text.toString()) room.editReply(state.sendMode.timelineEvent, it, action.text.toString())
} }
} else { } else {
@ -555,17 +555,17 @@ class MessageComposerViewModel @AssistedInject constructor(
sendMode = when (currentDraft) { sendMode = when (currentDraft) {
is UserDraft.Regular -> SendMode.Regular(currentDraft.content, false) is UserDraft.Regular -> SendMode.Regular(currentDraft.content, false)
is UserDraft.Quote -> { is UserDraft.Quote -> {
room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent -> room.getTimelineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
SendMode.Quote(timelineEvent, currentDraft.content) SendMode.Quote(timelineEvent, currentDraft.content)
} }
} }
is UserDraft.Reply -> { is UserDraft.Reply -> {
room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent -> room.getTimelineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
SendMode.Reply(timelineEvent, currentDraft.content) SendMode.Reply(timelineEvent, currentDraft.content)
} }
} }
is UserDraft.Edit -> { is UserDraft.Edit -> {
room.getTimeLineEvent(currentDraft.linkedEventId)?.let { timelineEvent -> room.getTimelineEvent(currentDraft.linkedEventId)?.let { timelineEvent ->
SendMode.Edit(timelineEvent, currentDraft.content) SendMode.Edit(timelineEvent, currentDraft.content)
} }
} }

View File

@ -343,24 +343,6 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
add(EventSharedAction.Edit(eventId, timelineEvent.root.getClearType())) add(EventSharedAction.Edit(eventId, timelineEvent.root.getClearType()))
} }
if (canRedact(timelineEvent, actionPermissions)) {
if (timelineEvent.root.getClearType() == EventType.POLL_START) {
add(EventSharedAction.Redact(
eventId,
askForReason = informationData.senderId != session.myUserId,
dialogTitleRes = R.string.delete_poll_dialog_title,
dialogDescriptionRes = R.string.delete_poll_dialog_content
))
} else {
add(EventSharedAction.Redact(
eventId,
askForReason = informationData.senderId != session.myUserId,
dialogTitleRes = R.string.delete_event_dialog_title,
dialogDescriptionRes = R.string.delete_event_dialog_content
))
}
}
if (canCopy(msgType)) { if (canCopy(msgType)) {
// TODO copy images? html? see ClipBoard // TODO copy images? html? see ClipBoard
add(EventSharedAction.Copy(messageContent!!.body)) add(EventSharedAction.Copy(messageContent!!.body))
@ -382,12 +364,30 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
add(EventSharedAction.ViewEditHistory(informationData)) add(EventSharedAction.ViewEditHistory(informationData))
} }
if (canSave(msgType) && messageContent is MessageWithAttachmentContent) {
add(EventSharedAction.Save(timelineEvent.eventId, messageContent))
}
if (canShare(msgType)) { if (canShare(msgType)) {
add(EventSharedAction.Share(timelineEvent.eventId, messageContent!!)) add(EventSharedAction.Share(timelineEvent.eventId, messageContent!!))
} }
if (canSave(msgType) && messageContent is MessageWithAttachmentContent) { if (canRedact(timelineEvent, actionPermissions)) {
add(EventSharedAction.Save(timelineEvent.eventId, messageContent)) if (timelineEvent.root.getClearType() == EventType.POLL_START) {
add(EventSharedAction.Redact(
eventId,
askForReason = informationData.senderId != session.myUserId,
dialogTitleRes = R.string.delete_poll_dialog_title,
dialogDescriptionRes = R.string.delete_poll_dialog_content
))
} else {
add(EventSharedAction.Redact(
eventId,
askForReason = informationData.senderId != session.myUserId,
dialogTitleRes = R.string.delete_event_dialog_title,
dialogDescriptionRes = R.string.delete_event_dialog_content
))
}
} }
} }

View File

@ -65,7 +65,7 @@ class VerificationItemFactory @Inject constructor(
?: return ignoredConclusion(params) ?: return ignoredConclusion(params)
// If we cannot find the referenced request we do not display the done event // If we cannot find the referenced request we do not display the done event
val refEvent = session.getRoom(event.root.roomId ?: "")?.getTimeLineEvent(refEventId) val refEvent = session.getRoom(event.root.roomId ?: "")?.getTimelineEvent(refEventId)
?: return ignoredConclusion(params) ?: return ignoredConclusion(params)
// If it's not a request ignore this event // If it's not a request ignore this event

View File

@ -86,7 +86,7 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted
annotationsSummary.reactionsSummary annotationsSummary.reactionsSummary
.flatMap { reactionsSummary -> .flatMap { reactionsSummary ->
reactionsSummary.sourceEvents.map { reactionsSummary.sourceEvents.map {
val event = room.getTimeLineEvent(it) val event = room.getTimelineEvent(it)
?: throw RuntimeException("Your eventId is not valid") ?: throw RuntimeException("Your eventId is not valid")
ReactionInfo( ReactionInfo(
event.root.eventId!!, event.root.eventId!!,

View File

@ -0,0 +1,25 @@
/*
* 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.media
interface AttachmentInteractionListener {
fun onDismiss()
fun onShare()
fun onDownload()
fun onPlayPause(play: Boolean)
fun videoSeekTo(percent: Int)
}

View File

@ -30,35 +30,33 @@ class AttachmentOverlayView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr), AttachmentEventListener { ) : ConstraintLayout(context, attrs, defStyleAttr), AttachmentEventListener {
var onShareCallback: (() -> Unit)? = null var interactionListener: AttachmentInteractionListener? = null
var onBack: (() -> Unit)? = null
var onPlayPause: ((play: Boolean) -> Unit)? = null
var videoSeekTo: ((progress: Int) -> Unit)? = null
val views: MergeImageAttachmentOverlayBinding val views: MergeImageAttachmentOverlayBinding
var isPlaying = false private var isPlaying = false
private var suspendSeekBarUpdate = false
var suspendSeekBarUpdate = false
init { init {
inflate(context, R.layout.merge_image_attachment_overlay, this) inflate(context, R.layout.merge_image_attachment_overlay, this)
views = MergeImageAttachmentOverlayBinding.bind(this) views = MergeImageAttachmentOverlayBinding.bind(this)
setBackgroundColor(Color.TRANSPARENT) setBackgroundColor(Color.TRANSPARENT)
views.overlayBackButton.setOnClickListener { views.overlayBackButton.setOnClickListener {
onBack?.invoke() interactionListener?.onDismiss()
} }
views.overlayShareButton.setOnClickListener { views.overlayShareButton.setOnClickListener {
onShareCallback?.invoke() interactionListener?.onShare()
}
views.overlayDownloadButton.setOnClickListener {
interactionListener?.onDownload()
} }
views.overlayPlayPauseButton.setOnClickListener { views.overlayPlayPauseButton.setOnClickListener {
onPlayPause?.invoke(!isPlaying) interactionListener?.onPlayPause(!isPlaying)
} }
views.overlaySeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { views.overlaySeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser) { if (fromUser) {
videoSeekTo?.invoke(progress) interactionListener?.videoSeekTo(progress)
} }
} }

View File

@ -49,14 +49,7 @@ abstract class BaseAttachmentProvider<Type>(
private val stringProvider: StringProvider private val stringProvider: StringProvider
) : AttachmentSourceProvider { ) : AttachmentSourceProvider {
interface InteractionListener { var interactionListener: AttachmentInteractionListener? = null
fun onDismissTapped()
fun onShareTapped()
fun onPlayPause(play: Boolean)
fun videoSeekTo(percent: Int)
}
var interactionListener: InteractionListener? = null
private var overlayView: AttachmentOverlayView? = null private var overlayView: AttachmentOverlayView? = null
@ -68,18 +61,7 @@ abstract class BaseAttachmentProvider<Type>(
if (position == -1) return null if (position == -1) return null
if (overlayView == null) { if (overlayView == null) {
overlayView = AttachmentOverlayView(context) overlayView = AttachmentOverlayView(context)
overlayView?.onBack = { overlayView?.interactionListener = interactionListener
interactionListener?.onDismissTapped()
}
overlayView?.onShareCallback = {
interactionListener?.onShareTapped()
}
overlayView?.onPlayPause = { play ->
interactionListener?.onPlayPause(play)
}
overlayView?.videoSeekTo = { percent ->
interactionListener?.videoSeekTo(percent)
}
} }
val timelineEvent = getTimelineEventAtPosition(position) val timelineEvent = getTimelineEventAtPosition(position)

View File

@ -81,7 +81,7 @@ class DataAttachmentRoomProvider(
override fun getTimelineEventAtPosition(position: Int): TimelineEvent? { override fun getTimelineEventAtPosition(position: Int): TimelineEvent? {
val item = getItem(position) val item = getItem(position)
return room?.getTimeLineEvent(item.eventId) return room?.getTimelineEvent(item.eventId)
} }
override suspend fun getFileForSharing(position: Int): File? { override suspend fun getFileForSharing(position: Int): File? {

View File

@ -0,0 +1,24 @@
/*
* 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.media
import im.vector.app.core.platform.VectorViewModelAction
import java.io.File
sealed class VectorAttachmentViewerAction : VectorViewModelAction {
data class DownloadMedia(val file: File) : VectorAttachmentViewerAction()
}

Some files were not shown because too many files have changed in this diff Show More